Import S928BXXU3AXH7 changes

This commit is contained in:
David Wronek 2024-10-06 16:20:51 +02:00
parent 4c31cc9fe8
commit dc0027c516
1372 changed files with 453751 additions and 323 deletions

View File

@ -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"

View File

@ -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"

1
arch/arm64/boot/dts/vendor Symbolic link
View File

@ -0,0 +1 @@
../../../../../sm8650-devicetrees/

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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 <linux/linkage.h>
#include <linux/uh.h>
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)

View File

@ -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

View File

@ -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

View File

@ -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

175
block/blk-sec-common.c Normal file
View File

@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Samsung specific module in block layer
*
* Copyright (C) 2021 Manjong Lee <mj0123.lee@samsung.com>
* Copyright (C) 2021 Junho Kim <junho89.kim@samsung.com>
* Copyright (C) 2021 Changheun Lee <nanich.lee@samsung.com>
* Copyright (C) 2021 Seunghwan Hyun <seunghwan.hyun@samsung.com>
* Copyright (C) 2021 Tran Xuan Nam <nam.tx2@samsung.com>
*/
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/part_stat.h>
#include <linux/sec_class.h>
#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 <nanich.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung specific module in block layer");
MODULE_VERSION("1.0");

353
block/blk-sec-stat-pio.c Normal file
View File

@ -0,0 +1,353 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Samsung Block Statistics
*
* Copyright (C) 2021 Manjong Lee <mj0123.lee@samsung.com>
* Copyright (C) 2021 Junho Kim <junho89.kim@samsung.com>
* Copyright (C) 2021 Changheun Lee <nanich.lee@samsung.com>
* Copyright (C) 2021 Seunghwan Hyun <seunghwan.hyun@samsung.com>
* Copyright (C) 2021 Tran Xuan Nam <nam.tx2@samsung.com>
*/
#include <linux/sysfs.h>
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#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);
}

View File

@ -0,0 +1,283 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Samsung Block Statistics
*
* Copyright (C) 2021 Manjong Lee <mj0123.lee@samsung.com>
* Copyright (C) 2021 Junho Kim <junho89.kim@samsung.com>
* Copyright (C) 2021 Changheun Lee <nanich.lee@samsung.com>
* Copyright (C) 2021 Seunghwan Hyun <seunghwan.hyun@samsung.com>
* Copyright (C) 2021 Tran Xuan Nam <nam.tx2@samsung.com>
*/
#include <linux/sysfs.h>
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <linux/cpufreq.h>
#include <linux/log2.h>
#include <linux/pm_qos.h>
#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);
}

201
block/blk-sec-stat.c Normal file
View File

@ -0,0 +1,201 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Samsung Block Statistics
*
* Copyright (C) 2021 Manjong Lee <mj0123.lee@samsung.com>
* Copyright (C) 2021 Junho Kim <junho89.kim@samsung.com>
* Copyright (C) 2021 Changheun Lee <nanich.lee@samsung.com>
* Copyright (C) 2021 Seunghwan Hyun <seunghwan.hyun@samsung.com>
* Copyright (C) 2021 Tran Xuan Nam <nam.tx2@samsung.com>
*/
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <linux/part_stat.h>
#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 <mj0123.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung block layer statistics module for various purposes");
MODULE_VERSION("1.0");

238
block/blk-sec-wb.c Normal file
View File

@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Samsung Block Write Booster
*
* Copyright (C) 2023 Jisoo Oh <jisoo2146.oh@samsung.com>
* Copyright (C) 2023 Changheun Lee <nanich.lee@samsung.com>
*/
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/part_stat.h>
#include <linux/timer.h>
#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 <jisoo2146.oh@samsung.com>");
MODULE_DESCRIPTION("Samsung write booster module in block layer");
MODULE_VERSION("1.0");

92
block/blk-sec.h Normal file
View File

@ -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

270
block/ssg-cgroup.c Normal file
View File

@ -0,0 +1,270 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Control Group of SamSung Generic I/O scheduler
*
* Copyright (C) 2021 Changheun Lee <nanich.lee@samsung.com>
*/
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#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);
}

900
block/ssg-iosched.c Normal file
View File

@ -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 <jisoo2146.oh@samsung.com>
* Copyright (C) 2021 Manjong Lee <mj0123.lee@samsung.com>
* Copyright (C) 2021 Changheun Lee <nanich.lee@samsung.com>
*/
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <linux/bio.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/compiler.h>
#include <linux/rbtree.h>
#include <linux/sbitmap.h>
#include <trace/events/block.h>
#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");

327
block/ssg-stat.c Normal file
View File

@ -0,0 +1,327 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Statistics of SamSung Generic I/O scheduler
*
* Copyright (C) 2021 Changheun Lee <nanich.lee@samsung.com>
*/
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <linux/sbitmap.h>
#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);
}

367
block/ssg-wb.c Normal file
View File

@ -0,0 +1,367 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Write Booster of SamSung Generic I/O scheduler
*
* Copyright (C) 2022 Jisoo Oh <jisoo2146.oh@samsung.com>
* Copyright (C) 2023 Changheun Lee <nanich.lee@samsung.com>
*/
#include <linux/blkdev.h>
#include <linux/sbitmap.h>
#include <linux/blk-mq.h>
#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));
}

191
block/ssg.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

348
drivers/adsp_factory/Kconfig Executable file
View File

@ -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

13
drivers/adsp_factory/Makefile Executable file
View File

@ -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

320
drivers/adsp_factory/adsp.h Executable file
View File

@ -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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
//#include <linux/sensors.h>
#include <linux/adsp/adsp_ft_common.h>
#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__ */

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/sec_class.h>
#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)");

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#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__);
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#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__);
}

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/notifier.h>
#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__);
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#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__);
}

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#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__);
}

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#include "adsp.h"
#ifdef CONFIG_SEC_VIB_NOTIFIER
#include <linux/vibrator/sec_vibrator_notifier.h>
#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__);
}

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#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__);
}

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#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__);
}

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#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__);
}

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#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__);
}

View File

@ -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 <linux/init.h>
#include <linux/module.h>
#include "adsp.h"
#if IS_ENABLED(CONFIG_SUPPORT_CONTROL_PROX_LED_GPIO)
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#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", &reg) != 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", &reg) != 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__);
}

1897
drivers/adsp_factory/ssc_core.c Executable file

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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);

View File

@ -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
*

View File

@ -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);

View File

@ -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.

View File

@ -0,0 +1,3 @@
obj-$(CONFIG_CHARGER_MAX77705) += max77705_charger.o
ccflags-y := -Wformat

View File

@ -0,0 +1,7 @@
ko_list = [
{
"ko_names" : [
"drivers/battery/charger/max77705_charger/max77705_charger.ko"
]
}
]

File diff suppressed because it is too large Load Diff

View File

@ -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 */
};

View File

@ -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 <linux/mfd/core.h>
#include <linux/mfd/max77705.h>
#include <linux/mfd/max77705-private.h>
#include <linux/regulator/machine.h>
#include <linux/pm_wakeup.h>
#include "../../common/sec_charging_common.h"
#include <linux/types.h>
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 */

View File

@ -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.

View File

@ -0,0 +1,3 @@
obj-$(CONFIG_CHARGER_MAX77775) += max77775_charger.o
ccflags-y := -Wformat

View File

@ -0,0 +1,7 @@
ko_list = [
{
"ko_names" : [
"drivers/battery/charger/max77775_charger/max77775_charger.ko"
]
}
]

File diff suppressed because it is too large Load Diff

View File

@ -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;
};

View File

@ -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 <linux/mfd/core.h>
#include <linux/mfd/max77775.h>
#include <linux/mfd/max77775-private.h>
#include <linux/regulator/machine.h>
#include <linux/pm_wakeup.h>
#include "../../common/sec_charging_common.h"
#include <linux/types.h>
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 */

View File

@ -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.

View File

@ -0,0 +1,3 @@
obj-$(CONFIG_CHARGER_PCA9481) += pca9481_charger.o
ccflags-y := -Wformat

View File

@ -0,0 +1,7 @@
ko_list = [
{
"ko_names" : [
"drivers/battery/charger/pca9481_charger/pca9481_charger.ko"
]
}
]

File diff suppressed because it is too large Load Diff

View File

@ -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 */

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <linux/version.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched/clock.h>
#include <linux/kernel.h>
#include <linux/security.h>
#include <linux/syscalls.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0))
#include <stdarg.h>
#else
#include <linux/stdarg.h>
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0))
#include <linux/time64.h>
#define SEC_TIMESPEC timespec64
#define SEC_GETTIMEOFDAY ktime_get_real_ts64
#define SEC_RTC_TIME_TO_TM rtc_time64_to_tm
#else
#include <linux/time.h>
#define SEC_TIMESPEC timeval
#define SEC_GETTIMEOFDAY do_gettimeofday
#define SEC_RTC_TIME_TO_TM rtc_time_to_tm
#endif
#include <linux/rtc.h>
#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);

View File

@ -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 */

View File

@ -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 <linux/slab.h>
#include <linux/battery/sb_sysfs.h>
#include <linux/battery/sb_notify.h>
#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);

View File

@ -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 <linux/err.h>
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 */

View File

@ -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 <linux/battery/sb_sysfs.h>
#include <linux/battery/sb_notify.h>
#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);

View File

@ -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 */

View File

@ -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 <linux/of.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#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);

View File

@ -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 */

View File

@ -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 <linux/slab.h>
#include <linux/mutex.h>
#include <linux/battery/sb_sysfs.h>
#include <linux/battery/sb_notify.h>
#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);

View File

@ -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 <linux/err.h>
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 */

View File

@ -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 <linux/slab.h>
#include <linux/mutex.h>
#include <linux/battery/sb_sysfs.h>
#include <linux/battery/sb_notify.h>
#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);

View File

@ -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 */

View File

@ -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 <linux/of.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/battery/sb_sysfs.h>
#include <linux/battery/sb_notify.h>
#include <linux/battery/sb_wireless.h>
#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");

View File

@ -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 <linux/version.h>
#include "sec_adc.h"
#define DEBUG
#if defined(CONFIG_SEC_KUNIT)
#include <kunit/mock.h>
#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);

View File

@ -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 <linux/iio/consumer.h>
#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 */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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 <linux/slab.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/power_supply.h>
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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 */

View File

@ -0,0 +1,353 @@
/*
* sec_battery_misc.c
* Samsung Mobile Battery Misc Driver
* Author: Yeongmi Ha <yeongmi86.ha@samsung.com>
* 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 <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/poll.h>
#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);

View File

@ -0,0 +1,48 @@
/*
* sec_battery_misc.h
* Samsung Mobile Battery Misc Driver
* Author: Yeongmi Ha <yeongmi86.ha@samsung.com>
* 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

File diff suppressed because it is too large Load Diff

View File

@ -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 */

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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 */

View File

@ -0,0 +1,593 @@
#include "sec_battery_vote.h"
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/debugfs.h>
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);

View File

@ -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

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More