| /* |
| * driver/muic/s2mu004.c - S2MU004 micro USB switch device driver |
| * |
| * Copyright (C) 2015 Samsung Electronics |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * This driver is based on max77843-muic-afc.c |
| * |
| */ |
| #define pr_fmt(fmt) "[MUIC] " fmt |
| |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #include <linux/i2c.h> |
| #include <linux/interrupt.h> |
| #include <linux/slab.h> |
| #include <linux/platform_device.h> |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| #include <linux/muic/muic.h> |
| #include <linux/mfd/samsung/s2mu004.h> |
| #include <linux/mfd/samsung/s2mu004-private.h> |
| #include <linux/muic/s2mu004-muic.h> |
| #include <linux/muic/s2mu004-muic-sysfs.h> |
| |
| #include <linux/battery/sec_charging_common.h> |
| #include <linux/power_supply.h> |
| #include <linux/ccic/usbpd-s2mu004.h> |
| #include <linux/muic/s2mu004-muic-hv.h> |
| |
| #include <linux/sec_debug.h> |
| #include <linux/sec_ext.h> |
| #include <linux/muic/muic_notifier.h> |
| #include <linux/ccic/ccic_notifier.h> |
| #include <linux/usb_notify.h> |
| #include <linux/muic/muic_interface.h> |
| #include <linux/sec_batt.h> |
| #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) |
| #include <linux/vbus_notifier.h> |
| #endif |
| #if IS_ENABLED(CONFIG_MUIC_UART_SWITCH) |
| #include <mach/pinctrl-samsung.h> |
| #endif |
| #if IS_ENABLED(CONFIG_CP_UART_NOTI) |
| #include <soc/samsung/exynos-modem-ctrl.h> |
| #endif |
| |
| #if IS_ENABLED(CONFIG_MUIC_SUPPORT_CCIC) |
| #include <linux/fb.h> |
| #endif |
| |
| #define ENUM_STR(x) {case(x): return #x; } |
| static const char *mode_to_str(enum s2mu004_muic_mode n) |
| { |
| switch (n) { |
| ENUM_STR(S2MU004_NONE_CABLE); |
| ENUM_STR(S2MU004_FIRST_ATTACH); |
| ENUM_STR(S2MU004_SECOND_ATTACH); |
| ENUM_STR(S2MU004_MUIC_DETACH); |
| ENUM_STR(S2MU004_MUIC_OTG); |
| ENUM_STR(S2MU004_MUIC_JIG); |
| default: |
| return "invalid"; |
| } |
| return "invalid"; |
| } |
| |
| static const char *dev_to_str(muic_attached_dev_t n) |
| { |
| switch (n) { |
| ENUM_STR(ATTACHED_DEV_NONE_MUIC); |
| ENUM_STR(ATTACHED_DEV_USB_MUIC); |
| ENUM_STR(ATTACHED_DEV_CDP_MUIC); |
| ENUM_STR(ATTACHED_DEV_OTG_MUIC); |
| ENUM_STR(ATTACHED_DEV_TA_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNOFFICIAL_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNOFFICIAL_TA_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNOFFICIAL_ID_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNOFFICIAL_ID_TA_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNOFFICIAL_ID_ANY_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNDEFINED_CHARGING_MUIC); |
| ENUM_STR(ATTACHED_DEV_DESKDOCK_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNKNOWN_VB_MUIC); |
| ENUM_STR(ATTACHED_DEV_DESKDOCK_VB_MUIC); |
| ENUM_STR(ATTACHED_DEV_CARDOCK_MUIC); |
| ENUM_STR(ATTACHED_DEV_JIG_UART_OFF_MUIC); |
| ENUM_STR(ATTACHED_DEV_JIG_UART_OFF_VB_MUIC); |
| ENUM_STR(ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC); |
| ENUM_STR(ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC); |
| ENUM_STR(ATTACHED_DEV_JIG_UART_ON_MUIC); |
| ENUM_STR(ATTACHED_DEV_JIG_UART_ON_VB_MUIC); |
| ENUM_STR(ATTACHED_DEV_JIG_USB_OFF_MUIC); |
| ENUM_STR(ATTACHED_DEV_JIG_USB_ON_MUIC); |
| ENUM_STR(ATTACHED_DEV_SMARTDOCK_MUIC); |
| ENUM_STR(ATTACHED_DEV_SMARTDOCK_VB_MUIC); |
| ENUM_STR(ATTACHED_DEV_SMARTDOCK_TA_MUIC); |
| ENUM_STR(ATTACHED_DEV_SMARTDOCK_USB_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNIVERSAL_MMDOCK_MUIC); |
| ENUM_STR(ATTACHED_DEV_AUDIODOCK_MUIC); |
| ENUM_STR(ATTACHED_DEV_MHL_MUIC); |
| ENUM_STR(ATTACHED_DEV_CHARGING_CABLE_MUIC); |
| ENUM_STR(ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC); |
| ENUM_STR(ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC); |
| ENUM_STR(ATTACHED_DEV_AFC_CHARGER_5V_MUIC); |
| ENUM_STR(ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC); |
| ENUM_STR(ATTACHED_DEV_AFC_CHARGER_9V_MUIC); |
| ENUM_STR(ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC); |
| ENUM_STR(ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC); |
| ENUM_STR(ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC); |
| ENUM_STR(ATTACHED_DEV_QC_CHARGER_5V_MUIC); |
| ENUM_STR(ATTACHED_DEV_QC_CHARGER_ERR_V_MUIC); |
| ENUM_STR(ATTACHED_DEV_QC_CHARGER_9V_MUIC); |
| ENUM_STR(ATTACHED_DEV_HV_ID_ERR_UNDEFINED_MUIC); |
| ENUM_STR(ATTACHED_DEV_HV_ID_ERR_UNSUPPORTED_MUIC); |
| ENUM_STR(ATTACHED_DEV_HV_ID_ERR_SUPPORTED_MUIC); |
| ENUM_STR(ATTACHED_DEV_HMT_MUIC); |
| ENUM_STR(ATTACHED_DEV_VZW_ACC_MUIC); |
| ENUM_STR(ATTACHED_DEV_VZW_INCOMPATIBLE_MUIC); |
| ENUM_STR(ATTACHED_DEV_USB_LANHUB_MUIC); |
| ENUM_STR(ATTACHED_DEV_TYPE2_CHG_MUIC); |
| ENUM_STR(ATTACHED_DEV_TYPE3_MUIC); |
| ENUM_STR(ATTACHED_DEV_TYPE3_MUIC_TA); |
| ENUM_STR(ATTACHED_DEV_TYPE3_ADAPTER_MUIC); |
| ENUM_STR(ATTACHED_DEV_TYPE3_CHARGER_MUIC); |
| ENUM_STR(ATTACHED_DEV_NONE_TYPE3_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNSUPPORTED_ID_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNSUPPORTED_ID_VB_MUIC); |
| ENUM_STR(ATTACHED_DEV_TIMEOUT_OPEN_MUIC); |
| ENUM_STR(ATTACHED_DEV_WIRELESS_PAD_MUIC); |
| #if IS_ENABLED(CONFIG_SEC_FACTORY) |
| ENUM_STR(ATTACHED_DEV_CARKIT_MUIC); |
| #endif |
| ENUM_STR(ATTACHED_DEV_POWERPACK_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNDEFINED_RANGE_MUIC); |
| ENUM_STR(ATTACHED_DEV_HICCUP_MUIC); |
| ENUM_STR(ATTACHED_DEV_CHK_WATER_REQ); |
| ENUM_STR(ATTACHED_DEV_CHK_WATER_DRY_REQ); |
| ENUM_STR(ATTACHED_DEV_GAMEPAD_MUIC); |
| ENUM_STR(ATTACHED_DEV_CHECK_OCP); |
| ENUM_STR(ATTACHED_DEV_RDU_TA_MUIC); |
| ENUM_STR(ATTACHED_DEV_AFC_CHARGER_9V_DUPLI_MUIC); |
| ENUM_STR(ATTACHED_DEV_UNKNOWN_MUIC); |
| ENUM_STR(ATTACHED_DEV_NUM); |
| default: |
| return "invalid"; |
| } |
| return "invalid"; |
| } |
| |
| static struct s2mu004_muic_data *static_data; |
| #if defined(CONFIG_CCIC_S2MU004) |
| static void s2mu004_muic_set_water_adc_ldo_wa(struct s2mu004_muic_data *muic_data, bool en); |
| #if !IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| static int s2mu004_muic_water_judge(struct s2mu004_muic_data *muic_data); |
| #endif |
| #endif |
| static int s2mu004_i2c_update_bit(struct i2c_client *client, |
| u8 reg, u8 mask, u8 shift, u8 value); |
| static int s2mu004_muic_set_com_sw(struct s2mu004_muic_data *muic_data, |
| u8 reg_val); |
| static void s2mu004_muic_set_dn_ready_for_killer(struct s2mu004_muic_data *muic_data); |
| #if defined(CONFIG_HV_MUIC_S2MU004_AFC) |
| int s2mu004_muic_check_afc_ready(struct s2mu004_muic_data *muic_data); |
| #endif |
| |
| #if IS_ENABLED(DEBUG_MUIC) |
| #define MAX_LOG 25 |
| #define READ 0 |
| #define WRITE 1 |
| |
| static u8 s2mu004_log_cnt; |
| static u8 s2mu004_log[MAX_LOG][3]; |
| |
| static void s2mu004_reg_log(u8 reg, u8 value, u8 rw) |
| { |
| s2mu004_log[s2mu004_log_cnt][0] = reg; |
| s2mu004_log[s2mu004_log_cnt][1] = value; |
| s2mu004_log[s2mu004_log_cnt][2] = rw; |
| s2mu004_log_cnt++; |
| if (s2mu004_log_cnt >= MAX_LOG) |
| s2mu004_log_cnt = 0; |
| } |
| |
| static void s2mu004_print_reg_log(void) |
| { |
| int i; |
| u8 reg, value, rw; |
| char mesg[256] = ""; |
| |
| for (i = 0; i < MAX_LOG; i++) { |
| reg = s2mu004_log[s2mu004_log_cnt][0]; |
| value = s2mu004_log[s2mu004_log_cnt][1]; |
| rw = s2mu004_log[s2mu004_log_cnt][2]; |
| s2mu004_log_cnt++; |
| |
| if (s2mu004_log_cnt >= MAX_LOG) |
| s2mu004_log_cnt = 0; |
| sprintf(mesg+strlen(mesg), "%x(%x)%x ", reg, value, rw); |
| } |
| pr_info("%s %s\n", __func__, mesg); |
| } |
| |
| void s2mu004_read_reg_dump(struct s2mu004_muic_data *muic, char *mesg) |
| { |
| u8 val; |
| |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_CTRL1, &val); |
| sprintf(mesg+strlen(mesg), "CTRL1:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_SW_CTRL, &val); |
| sprintf(mesg+strlen(mesg), "SW_CTRL:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_INT1_MASK, &val); |
| sprintf(mesg+strlen(mesg), "IM1:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_INT2_MASK, &val); |
| sprintf(mesg+strlen(mesg), "IM2:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_CHG_TYPE, &val); |
| sprintf(mesg+strlen(mesg), "CHG_T:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_DEVICE_APPLE, &val); |
| sprintf(mesg+strlen(mesg), "APPLE_DT:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_ADC, &val); |
| sprintf(mesg+strlen(mesg), "ADC:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_DEVICE_TYPE1, &val); |
| sprintf(mesg+strlen(mesg), "DT1:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_DEVICE_TYPE2, &val); |
| sprintf(mesg+strlen(mesg), "DT2:%x ", val); |
| s2mu004_read_reg(muic->i2c, S2MU004_REG_MUIC_DEVICE_TYPE3, &val); |
| sprintf(mesg+strlen(mesg), "DT3:%x ", val); |
| } |
| |
| void s2mu004_print_reg_dump(struct s2mu004_muic_data *muic_data) |
| { |
| char mesg[256] = ""; |
| |
| s2mu004_read_reg_dump(muic_data, mesg); |
| |
| pr_info("%s %s\n", __func__, mesg); |
| } |
| #endif |
| |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| /* interface functions */ |
| static int s2mu004_if_com_to_open_with_vbus(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| int ret = 0; |
| |
| mutex_lock(&muic_data->switch_mutex); |
| pr_info("%s\n", __func__); |
| ret = s2mu004_muic_com_to_open_with_vbus(muic_data); |
| mutex_unlock(&muic_data->switch_mutex); |
| |
| return ret; |
| } |
| |
| static int s2mu004_if_com_to_open(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| int ret = 0; |
| |
| mutex_lock(&muic_data->switch_mutex); |
| pr_info("%s\n", __func__); |
| ret = s2mu004_muic_com_to_open(muic_data); |
| mutex_unlock(&muic_data->switch_mutex); |
| |
| return ret; |
| } |
| |
| static int s2mu004_if_com_to_audio(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| int ret = 0; |
| |
| mutex_lock(&muic_data->switch_mutex); |
| pr_info("%s\n", __func__); |
| ret = s2mu004_muic_com_to_audio(muic_data); |
| mutex_unlock(&muic_data->switch_mutex); |
| |
| return ret; |
| } |
| |
| static int s2mu004_if_com_to_otg(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| int ret = 0; |
| |
| mutex_lock(&muic_data->switch_mutex); |
| pr_info("%s\n", __func__); |
| ret = s2mu004_muic_com_to_otg(muic_data); |
| mutex_unlock(&muic_data->switch_mutex); |
| |
| return ret; |
| } |
| |
| static int s2mu004_if_get_adc(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| int ret = 0; |
| |
| mutex_lock(&muic_data->switch_mutex); |
| pr_info("%s\n", __func__); |
| ret = s2mu004_muic_recheck_adc(muic_data); |
| mutex_unlock(&muic_data->switch_mutex); |
| |
| return ret & 0x1F; |
| } |
| |
| static int s2mu004_if_switch_to_usb(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| int ret = 0; |
| |
| mutex_lock(&muic_data->switch_mutex); |
| ret = s2mu004_muic_com_to_usb(muic_data); |
| if (ret) |
| pr_err("%s set_com_usb err\n", __func__); |
| mutex_unlock(&muic_data->switch_mutex); |
| |
| return ret; |
| } |
| |
| static int s2mu004_if_switch_to_uart(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| int ret = 0; |
| |
| mutex_lock(&muic_data->switch_mutex); |
| pr_info("%s\n", __func__); |
| ret = s2mu004_muic_com_to_uart(muic_data); |
| mutex_unlock(&muic_data->switch_mutex); |
| |
| return ret; |
| } |
| |
| static int s2mu004_if_get_vbus(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| return s2mu004_muic_get_vbus_state(muic_data); |
| } |
| |
| static void s2mu004_if_set_jig_state(void *mdata, bool val) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| mutex_lock(&muic_data->muic_mutex); |
| muic_data->jig_state = val; |
| pr_info("%s jig_state : (%d)\n", __func__, muic_data->jig_state); |
| mutex_unlock(&muic_data->muic_mutex); |
| } |
| #endif |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| static void s2mu004_if_set_water_det(void *mdata, bool val) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| mutex_lock(&muic_data->muic_mutex); |
| pr_info("%s water_status : (%d)\n", |
| __func__, muic_data->water_status); |
| if (muic_data->water_dry_status == S2MU004_WATER_DRY_MUIC_DET) { |
| if (val) |
| muic_data->water_dry_status = S2MU004_WATER_DRY_MUIC_CCIC_INVALID; |
| else |
| muic_data->water_dry_status = S2MU004_WATER_DRY_MUIC_CCIC_DET; |
| wake_up_interruptible(&muic_data->wait); |
| } else { |
| if (muic_data->water_status >= S2MU004_WATER_MUIC_DET) { |
| if (val) |
| muic_data->water_status = S2MU004_WATER_MUIC_CCIC_DET; |
| else |
| muic_data->water_status = S2MU004_WATER_MUIC_CCIC_INVALID; |
| pr_info("%s water_status : (%d)\n", |
| __func__, muic_data->water_status); |
| wake_up_interruptible(&muic_data->wait); |
| } else { |
| pr_err("%s wrong status\n", __func__); |
| } |
| } |
| mutex_unlock(&muic_data->muic_mutex); |
| } |
| |
| #ifndef CONFIG_SEC_FACTORY |
| static void s2mu004_if_set_water_det_from_boot(void *mdata, bool val) |
| { |
| u8 val_vbadc; |
| int ret; |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| if (val) { |
| muic_data->water_status = S2MU004_WATER_MUIC_CCIC_DET; |
| |
| s2mu004_i2c_update_bit(muic_data->i2c, |
| S2MU004_REG_LDOADC_VSETH, |
| LDOADC_VSETH_WAKE_HYS_MASK, |
| LDOADC_VSETH_WAKE_HYS_SHIFT, 0x1); |
| s2mu004_muic_set_water_adc_ldo_wa(muic_data, true); |
| muic_data->water_status = S2MU004_WATER_MUIC_CCIC_STABLE; |
| muic_data->water_dry_status = S2MU004_WATER_DRY_MUIC_IDLE; |
| |
| /* readback vbadc */ |
| ret = s2mu004_read_reg(muic_data->i2c, S2MU004_REG_AFC_STATUS, &val_vbadc); |
| if (ret) |
| pr_err("%s fail to read muic reg(%d)\n", __func__, ret); |
| |
| pr_info("%s, AFC_STATUS : 0x%x\n", __func__, val_vbadc); |
| |
| #if defined(CONFIG_HV_MUIC_S2MU004_AFC) |
| /* reset afc */ |
| s2mu004_muic_hv_update_reg(muic_data->i2c, S2MU004_REG_AFC_LOGIC_CTRL2, 0x0, 0x04, 0); |
| s2mu004_muic_hv_update_reg(muic_data->i2c, S2MU004_REG_AFC_CTRL1, 0x0a, 0x1e, 0); |
| #endif |
| |
| MUIC_SEND_NOTI_ATTACH(ATTACHED_DEV_UNDEFINED_RANGE_MUIC); |
| } |
| } |
| #endif |
| #endif |
| |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| static void s2mu004_if_set_cable_state(void *mdata, muic_attached_dev_t new_dev) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| struct muic_platform_data *pdata = muic_data->pdata; |
| |
| mutex_lock(&muic_data->muic_mutex); |
| |
| pr_info("%s new dev=%s\n", __func__, dev_to_str(new_dev)); |
| |
| switch (new_dev) { |
| case ATTACHED_DEV_JIG_USB_OFF_MUIC: |
| pdata->attached_dev = ATTACHED_DEV_JIG_USB_OFF_MUIC; |
| muic_data->jig_state = true; |
| break; |
| case ATTACHED_DEV_JIG_USB_ON_MUIC: |
| pdata->attached_dev = ATTACHED_DEV_JIG_USB_ON_MUIC; |
| muic_data->jig_state = true; |
| break; |
| case ATTACHED_DEV_JIG_UART_OFF_MUIC: |
| pdata->attached_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC; |
| muic_data->jig_state = true; |
| break; |
| case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: |
| pdata->attached_dev = ATTACHED_DEV_JIG_UART_OFF_VB_MUIC; |
| muic_data->jig_state = true; |
| break; |
| case ATTACHED_DEV_JIG_UART_ON_MUIC: |
| pdata->attached_dev = ATTACHED_DEV_JIG_UART_ON_MUIC; |
| muic_data->jig_state = true; |
| break; |
| case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: |
| pdata->attached_dev = ATTACHED_DEV_JIG_UART_ON_VB_MUIC; |
| muic_data->jig_state = true; |
| break; |
| case ATTACHED_DEV_OTG_MUIC: |
| pdata->attached_dev = ATTACHED_DEV_OTG_MUIC; |
| muic_data->attach_mode = S2MU004_MUIC_OTG; |
| /* enable vbus det for interrupt */ |
| s2mu004_muic_control_vbus_det(muic_data, true); |
| muic_data->jig_state = false; |
| pr_info("USB_OTG DETECTED\n"); |
| break; |
| case ATTACHED_DEV_USB_MUIC: |
| pdata->attached_dev = ATTACHED_DEV_USB_MUIC; |
| muic_data->attach_mode = S2MU004_SECOND_ATTACH; |
| muic_data->jig_state = false; |
| break; |
| case ATTACHED_DEV_NONE_MUIC: |
| if (pdata->attached_dev == ATTACHED_DEV_OTG_MUIC) { |
| /* disable vbus det for interrupt */ |
| s2mu004_muic_control_vbus_det(muic_data, false); |
| } else if (muic_data->jig_state) |
| muic_data->jig_state = false; |
| |
| if (muic_core_get_ccic_cable_state(muic_data->pdata)) { |
| pdata->attached_dev = ATTACHED_DEV_NONE_MUIC; |
| muic_core_handle_detach(muic_data->pdata); |
| } |
| |
| pdata->attached_dev = ATTACHED_DEV_NONE_MUIC; |
| muic_data->attach_mode = S2MU004_NONE_CABLE; |
| break; |
| default: |
| break; |
| } |
| |
| if (muic_data->jig_state == true) { |
| /* enable rid detect for waterproof */ |
| s2mu004_muic_control_rid_adc(muic_data, S2MU004_ENABLE); |
| } |
| |
| pm_relax(muic_data->dev); |
| mutex_unlock(&muic_data->muic_mutex); |
| } |
| |
| static void s2mu004_if_set_otg_detect_en(void *mdata, bool en) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| s2mu004_muic_control_vbus_det(muic_data, en); |
| } |
| |
| static void s2mu004_if_dcd_rescan(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| mutex_lock(&muic_data->muic_mutex); |
| wake_lock(&muic_data->wake_lock); |
| |
| muic_data->is_dcd_recheck = true; |
| s2mu004_muic_dcd_rescan(muic_data); |
| |
| wake_unlock(&muic_data->wake_lock); |
| mutex_unlock(&muic_data->muic_mutex); |
| } |
| |
| static int s2mu004_if_bcd_rescan(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| return s2mu004_muic_bcd_rescan(muic_data); |
| } |
| |
| static int s2mu004_if_control_rid_adc(void *mdata, bool enable) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| return s2mu004_muic_control_rid_adc(muic_data, enable); |
| } |
| #endif |
| |
| #if defined(CONFIG_HV_MUIC_S2MU004_AFC) |
| static int s2mu004_if_set_afc_reset(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| struct muic_interface_t *muic_if = muic_data->if_data; |
| |
| pr_info("%s\n", __func__); |
| muic_if->is_afc_reset = true; |
| |
| return 0; |
| } |
| |
| static muic_attached_dev_t s2mu004_if_check_id_err(void *mdata, |
| muic_attached_dev_t new_dev) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| return hv_muic_check_id_err(muic_data, new_dev); |
| } |
| |
| static int s2mu004_if_reset_hvcontrol_reg(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| s2mu004_hv_muic_reset_hvcontrol_reg(muic_data); |
| |
| return 0; |
| } |
| |
| static int s2mu004_if_check_afc_ready(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| return s2mu004_muic_check_afc_ready(muic_data); |
| } |
| |
| static int s2mu004_if_reset_afc_register(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| return s2mu004_muic_reset_afc_register(muic_data); |
| } |
| |
| static void s2mu004_if_set_afc_ready(void *mdata, bool en) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| pr_info("%s Enter en : %d\n", __func__, (int)en); |
| if (en) |
| s2mu004_muic_check_afc_ready(muic_data); |
| } |
| #endif |
| |
| int s2mu004_if_check_usb_killer(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| struct i2c_client *i2c = muic_data->i2c; |
| u8 reg_val = 0, muic_sw_ctrl = 0; |
| u8 afc_int_mask, afc_otp4, afc_ctrl1, afc_ctrl2, is_dnres, is_vdnmon; |
| int ret = MUIC_NORMAL_OTG; |
| |
| pr_info("%s, enter", __func__); |
| |
| /* muic path open */ |
| mutex_lock(&muic_data->switch_mutex); |
| muic_sw_ctrl = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_SW_CTRL); |
| s2mu004_muic_set_com_sw(muic_data, MANSW_OPEN); |
| |
| /* AFC Block Enable & INT Masking */ |
| afc_int_mask = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_INT_MASK); |
| reg_val = afc_int_mask | INTm_VDNMon_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_INT_MASK, reg_val); |
| |
| afc_ctrl1 = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_CTRL1); |
| reg_val = afc_ctrl1 | MUIC_AFC_CTRL1_AFC_EN_MASK | MUIC_AFC_CTRL1_DPDNVD_EN_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL1, reg_val); |
| |
| s2mu004_muic_set_dn_ready_for_killer(muic_data); |
| |
| /* 1st check */ |
| reg_val = (MUIC_AFC_CTRL1_VD_SEL_0_6V << MUIC_AFC_CTRL1_DPVD_SEL_SHIFT) | |
| MUIC_AFC_CTRL1_AFC_EN_MASK | MUIC_AFC_CTRL1_DPDNVD_EN_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL1, reg_val); |
| |
| mdelay(50); |
| |
| is_vdnmon = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_STATUS) |
| & MUIC_AFC_STATUS_VDNMON_MASK; |
| if (!is_vdnmon) { |
| pr_info("%s, 1st chk: Normal OTG.", __func__); |
| goto exit_chk; |
| } |
| |
| /* 2nd check */ |
| afc_otp4 = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_OTP4); |
| reg_val = afc_otp4 | MUIC_AFC_OTP4_CTRL_IDM_ON_REG_SELL_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_OTP4, reg_val); |
| |
| afc_ctrl2 = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_CTRL2); |
| reg_val = afc_ctrl2 | MUIC_AFC_CTRL2_DNRESEN_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL2, reg_val); |
| |
| reg_val = reg_val | MUIC_AFC_CTRL1_CTRL_IDM_ON_MASK | |
| MUIC_AFC_CTRL1_AFC_EN_MASK | MUIC_AFC_CTRL1_DPDNVD_EN_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL1, reg_val); |
| |
| msleep(50); |
| |
| reg_val = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_STATUS); |
| is_dnres = reg_val & MUIC_AFC_STATUS_DNRES_MASK; |
| |
| if (!is_dnres) { |
| pr_info("%s, USB Killer is detected.", __func__); |
| ret = MUIC_ABNORMAL_OTG; |
| } |
| |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_OTP4, afc_otp4); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL2, afc_ctrl2); |
| exit_chk: |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_INT_MASK, afc_int_mask); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL1, afc_ctrl1); |
| |
| /* restore muic path */ |
| s2mu004_muic_set_com_sw(muic_data, muic_sw_ctrl); |
| mutex_unlock(&muic_data->switch_mutex); |
| |
| return ret; |
| } |
| |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| static int s2mu004_if_jig_on(void *mdata) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| return s2mu004_muic_jig_on(muic_data); |
| } |
| |
| static int s2mu004_if_set_gpio_uart_sel(void *mdata, int uart_path) |
| { |
| struct s2mu004_muic_data *muic_data = |
| (struct s2mu004_muic_data *)mdata; |
| |
| return s2mu004_set_gpio_uart_sel(muic_data, uart_path); |
| } |
| #endif |
| |
| int s2mu004_i2c_read_byte(struct i2c_client *client, u8 command) |
| { |
| int ret = 0; |
| int retry = 0; |
| u8 data = 0; |
| |
| ret = s2mu004_read_reg(client, command, &data); |
| |
| while (ret < 0) { |
| pr_info("failed to read reg(0x%x) retry(%d)\n", command, retry); |
| if (retry > 10) { |
| pr_err("%s retry failed!!\n", __func__); |
| break; |
| } |
| msleep(100); |
| ret = s2mu004_read_reg(client, command, &data); |
| retry++; |
| } |
| |
| #if IS_ENABLED(DEBUG_MUIC) |
| s2mu004_reg_log(command, ret, retry << 1 | READ); |
| #endif |
| return data; |
| } |
| |
| int s2mu004_i2c_write_byte(struct i2c_client *client, |
| u8 command, u8 value) |
| { |
| int ret_r = 0; |
| int ret_w = 0; |
| int retry = 0; |
| u8 written = 0; |
| |
| ret_w = s2mu004_write_reg(client, command, value); |
| |
| while (ret_w < 0) { |
| pr_info("failed to write reg(0x%x) retry(%d)\n", command, retry); |
| ret_r = s2mu004_read_reg(client, command, &written); |
| if (ret_r < 0) |
| pr_err("%s reg(0x%x)\n", __func__, command); |
| msleep(100); |
| ret_w = s2mu004_write_reg(client, command, value); |
| retry++; |
| } |
| #if IS_ENABLED(DEBUG_MUIC) |
| s2mu004_reg_log(command, value, retry << 1 | WRITE); |
| #endif |
| return ret_w; |
| } |
| static int s2mu004_i2c_guaranteed_wbyte(struct i2c_client *client, |
| u8 command, u8 value) |
| { |
| int ret; |
| int retry = 0; |
| int written; |
| |
| ret = s2mu004_i2c_write_byte(client, command, value); |
| written = s2mu004_i2c_read_byte(client, command); |
| while (written != value) { |
| pr_info("reg(0x%x): written(0x%x) != value(0x%x)\n", |
| command, written, value); |
| if (retry > 10) { |
| pr_err("%s retry failed!!\n", __func__); |
| break; |
| } |
| msleep(100); |
| retry++; |
| ret = s2mu004_i2c_write_byte(client, command, value); |
| written = s2mu004_i2c_read_byte(client, command); |
| } |
| return ret; |
| } |
| |
| static int s2mu004_i2c_update_bit(struct i2c_client *i2c, |
| u8 reg, u8 mask, u8 shift, u8 value) |
| { |
| int ret; |
| u8 reg_val = 0; |
| |
| reg_val = s2mu004_i2c_read_byte(i2c, reg); |
| reg_val &= ~mask; |
| reg_val |= value << shift; |
| ret = s2mu004_i2c_write_byte(i2c, reg, reg_val); |
| pr_info("%s reg(0x%x) value(0x%x)\n", __func__, reg, reg_val); |
| if (ret < 0) |
| pr_err("%s Reg = 0x%X, mask = 0x%X, val = 0x%X write err : %d\n", |
| __func__, reg, mask, value, ret); |
| |
| return ret; |
| } |
| |
| static void s2mu004_muic_set_dn_ready_for_killer(struct s2mu004_muic_data *muic_data) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| u8 reg_val, is_vdnmon; |
| int i, vdnmon_gnd_cnt; |
| |
| usleep_range(10000, 11000); |
| is_vdnmon = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_STATUS) |
| & MUIC_AFC_STATUS_VDNMON_MASK; |
| |
| if (is_vdnmon) { |
| reg_val = MUIC_AFC_CTRL1_AFC_EN_MASK | MUIC_AFC_CTRL1_DPDNVD_EN_MASK | |
| (MUIC_AFC_CTRL1_VD_SEL_GND << MUIC_AFC_CTRL1_DPVD_SEL_SHIFT) | |
| (MUIC_AFC_CTRL1_VD_SEL_GND << MUIC_AFC_CTRL1_DNVD_SEL_SHIFT); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL1, reg_val); |
| |
| msleep(50); |
| |
| is_vdnmon = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_STATUS) |
| & MUIC_AFC_STATUS_VDNMON_MASK; |
| if (!is_vdnmon) { |
| pr_info("%s done(Line:%d)", __func__, __LINE__); |
| goto vdnmon_chk_done; |
| } |
| |
| vdnmon_gnd_cnt = 0; |
| for (i = 0; i < 10; i++) { |
| reg_val = MUIC_AFC_CTRL1_AFC_EN_MASK | MUIC_AFC_CTRL1_DPDNVD_EN_MASK | |
| (MUIC_AFC_CTRL1_VD_SEL_GND << MUIC_AFC_CTRL1_DPVD_SEL_SHIFT) | |
| (MUIC_AFC_CTRL1_VD_SEL_GND << MUIC_AFC_CTRL1_DNVD_SEL_SHIFT); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL1, reg_val); |
| |
| usleep_range(20000, 21000); |
| |
| reg_val &= ~((MUIC_AFC_CTRL1_VD_SEL_GND << MUIC_AFC_CTRL1_DNVD_SEL_SHIFT) | |
| (MUIC_AFC_CTRL1_VD_SEL_GND << MUIC_AFC_CTRL1_DPVD_SEL_SHIFT)); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL1, reg_val); |
| |
| usleep_range(10000, 11000); |
| is_vdnmon = s2mu004_i2c_read_byte(i2c, S2MU004_REG_AFC_STATUS) |
| & MUIC_AFC_STATUS_VDNMON_MASK; |
| if (!is_vdnmon) { |
| vdnmon_gnd_cnt++; |
| if (vdnmon_gnd_cnt >= 2) { |
| pr_info("%s done(Line:%d)", __func__, __LINE__); |
| goto vdnmon_chk_done; |
| } |
| } |
| } |
| pr_info("%s done(Line:%d)", __func__, __LINE__); |
| |
| vdnmon_chk_done: |
| reg_val &= ~(MUIC_AFC_CTRL1_VD_SEL_GND << MUIC_AFC_CTRL1_DNVD_SEL_SHIFT); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_AFC_CTRL1, reg_val); |
| } |
| } |
| |
| int s2mu004_muic_control_rid_adc(struct s2mu004_muic_data *muic_data, bool enable) |
| { |
| int data = 0; |
| |
| pr_info("%s (%s)\n", __func__, enable ? "Enable" : "Disable"); |
| |
| data = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_MUIC_RID_CTRL); |
| |
| if (enable) |
| data &= ~MUIC_CTRL2_ADC_OFF_MASK; |
| else |
| data |= MUIC_CTRL2_ADC_OFF_MASK; |
| |
| s2mu004_i2c_write_byte(muic_data->i2c, S2MU004_REG_MUIC_RID_CTRL, data); |
| |
| return 0; |
| } |
| |
| int s2mu004_muic_bcd_rescan(struct s2mu004_muic_data *muic_data) |
| { |
| int data = 0; |
| |
| pr_info("%s\n", __func__); |
| |
| /* start secondary dp dm detect */ |
| data = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_MUIC_BCD_RESCAN); |
| data |= MUIC_BCD_RESCAN_MASK; |
| s2mu004_i2c_write_byte(muic_data->i2c, S2MU004_REG_MUIC_BCD_RESCAN, data); |
| |
| return 0; |
| } |
| |
| #if IS_ENABLED(CONFIG_HV_MUIC_S2MU004_AFC) |
| int s2mu004_muic_reset_afc_register(struct s2mu004_muic_data *muic_data) |
| { |
| int data = 0; |
| |
| pr_info("%s\n", __func__); |
| |
| data = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_AFC_LOGIC_CTRL2); |
| data |= MUIC_AFC_LOGIC_CTRL2_AFC_RST_MASK; |
| s2mu004_i2c_write_byte(muic_data->i2c, S2MU004_REG_AFC_LOGIC_CTRL2, data); |
| |
| return 0; |
| } |
| |
| int s2mu004_muic_check_afc_ready(struct s2mu004_muic_data *muic_data) |
| { |
| #if !IS_ENABLED(CONFIG_SEC_FACTORY) |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| #if IS_ENABLED(CONFIG_CCIC_S2MU004) |
| struct muic_interface_t *muic_if = (struct muic_interface_t *)muic_data->if_data; |
| #endif |
| |
| pr_info("%s\n", __func__); |
| |
| /* check muic ready for afc */ |
| if (muic_pdata->afc_disable) |
| pr_info("%s AFC Disable(%d) by USER!\n", |
| __func__, muic_pdata->afc_disable); |
| #if IS_ENABLED(CONFIG_CCIC_S2MU004) |
| else if (muic_if->is_afc_pdic_ready == false) |
| pr_info("%s AFC Disable(%d) by PDIC\n", |
| __func__, muic_if->is_afc_pdic_ready); |
| #endif |
| else { |
| pr_info("%s ready:%d afc_check:%d\n", __func__, |
| muic_data->is_afc_muic_ready, muic_data->afc_check); |
| if (muic_data->is_afc_muic_ready == false && muic_data->afc_check) { |
| cancel_delayed_work(&muic_data->prepare_afc_charger); |
| schedule_delayed_work(&muic_data->prepare_afc_charger, |
| msecs_to_jiffies(200)); |
| } |
| } |
| #else |
| cancel_delayed_work(&muic_data->prepare_afc_charger); |
| schedule_delayed_work(&muic_data->prepare_afc_charger, msecs_to_jiffies(200)); |
| #endif |
| |
| return 0; |
| } |
| #endif |
| |
| void s2mu004_muic_control_vbus_det(struct s2mu004_muic_data *muic_data, bool enable) |
| { |
| int data = 0; |
| |
| pr_info("%s (%s)\n", __func__, enable ? "Enable" : "Disable"); |
| |
| /* enable vbus det for interrupt */ |
| data = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_AFC_OTP6); |
| |
| if (enable) |
| data |= MUIC_AFC_OTP6_EN_VBUS_DET_MUIC_MASK; |
| else |
| data &= ~MUIC_AFC_OTP6_EN_VBUS_DET_MUIC_MASK; |
| |
| s2mu004_i2c_write_byte(muic_data->i2c, S2MU004_REG_AFC_OTP6, data); |
| |
| } |
| |
| #if defined(GPIO_USB_SEL) |
| static int s2mu004_set_gpio_usb_sel(int uart_sel) |
| { |
| return 0; |
| } |
| #endif /* GPIO_USB_SEL */ |
| |
| int s2mu004_set_gpio_uart_sel(struct s2mu004_muic_data *muic_data, int uart_sel) |
| { |
| const char *mode; |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| #if !IS_ENABLED(CONFIG_MUIC_UART_SWITCH) |
| int uart_sel_gpio = muic_pdata->gpio_uart_sel; |
| int uart_sel_val; |
| int ret; |
| |
| ret = gpio_request(uart_sel_gpio, "GPIO_UART_SEL"); |
| if (ret) { |
| pr_err("failed to gpio_request GPIO_UART_SEL\n"); |
| return ret; |
| } |
| |
| uart_sel_val = gpio_get_value(uart_sel_gpio); |
| |
| switch (uart_sel) { |
| case MUIC_PATH_UART_AP: |
| mode = "AP_UART"; |
| if (gpio_is_valid(uart_sel_gpio)) |
| gpio_direction_output(uart_sel_gpio, 1); |
| break; |
| case MUIC_PATH_UART_CP: |
| mode = "CP_UART"; |
| if (gpio_is_valid(uart_sel_gpio)) |
| gpio_direction_output(uart_sel_gpio, 0); |
| break; |
| default: |
| mode = "Error"; |
| break; |
| } |
| |
| uart_sel_val = gpio_get_value(uart_sel_gpio); |
| |
| gpio_free(uart_sel_gpio); |
| |
| pr_info("%s, GPIO_UART_SEL(%d)=%c\n", |
| mode, uart_sel_gpio, (uart_sel_val == 0 ? 'L' : 'H')); |
| #else |
| switch (uart_sel) { |
| case MUIC_PATH_UART_AP: |
| mode = "AP_UART"; |
| pin_config_set(muic_pdata.uart_addr, muic_pdata.uart_rxd, |
| PINCFG_PACK(PINCFG_TYPE_FUNC, 0x2)); |
| pin_config_set(muic_pdata.uart_addr, muic_pdata.uart_txd, |
| PINCFG_PACK(PINCFG_TYPE_FUNC, 0x2)); |
| break; |
| case MUIC_PATH_UART_CP: |
| mode = "CP_UART"; |
| pin_config_set(muic_pdata.uart_addr, muic_pdata.uart_rxd, |
| PINCFG_PACK(PINCFG_TYPE_FUNC, 0x3)); |
| pin_config_set(muic_pdata.uart_addr, muic_pdata.uart_txd, |
| PINCFG_PACK(PINCFG_TYPE_FUNC, 0x3)); |
| break; |
| default: |
| mode = "Error"; |
| break; |
| } |
| |
| pr_info("%s %s\n", __func__, mode); |
| #endif/* CONFIG_MUIC_UART_SWITCH */ |
| return 0; |
| } |
| |
| #if IS_ENABLED(GPIO_DOC_SWITCH) |
| static int s2mu004_set_gpio_doc_switch(struct s2mu004_muic_data *muic_data, int val) |
| { |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| int doc_switch_gpio = muic_pdata->gpio_doc_switch; |
| int doc_switch_val; |
| int ret; |
| |
| ret = gpio_request(doc_switch_gpio, "GPIO_DOC_SWITCH"); |
| if (ret) { |
| pr_err("failed to gpio_request GPIO_DOC_SWITCH\n"); |
| return ret; |
| } |
| |
| doc_switch_val = gpio_get_value(doc_switch_gpio); |
| |
| if (gpio_is_valid(doc_switch_gpio)) |
| gpio_set_value(doc_switch_gpio, val); |
| doc_switch_val = gpio_get_value(doc_switch_gpio); |
| |
| gpio_free(doc_switch_gpio); |
| |
| pr_info("%s (%d)%c\n", __func__, |
| doc_switch_gpio, (doc_switch_val == 0 ? 'L' : 'H')); |
| |
| return 0; |
| } |
| #endif /* GPIO_DOC_SWITCH */ |
| |
| #if IS_ENABLED(CONFIG_SEC_FACTORY) |
| int s2mu004_muic_set_otg_reg(struct s2mu004_muic_data *muic_data, bool on) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| u8 reg_val; |
| int ret = 0; |
| |
| if (on) { |
| muic_data->attach_mode = S2MU004_MUIC_OTG; |
| /* enable vbus det for interrupt */ |
| s2mu004_muic_control_vbus_det(muic_data, true); |
| } else { |
| /* disable vbus det for interrupt */ |
| s2mu004_muic_control_vbus_det(muic_data, false); |
| } |
| |
| /* 0x1e : hidden register */ |
| ret = s2mu004_i2c_read_byte(i2c, 0x1e); |
| if (ret < 0) |
| pr_err("%s err read 0x1e reg(%d)\n", __func__, ret); |
| |
| /* Set 0x1e[5:4] bit to 0x11 or 0x01 */ |
| if (on) |
| reg_val = ret | (0x1 << 5); |
| else |
| reg_val = ret & ~(0x1 << 5); |
| |
| if (reg_val ^ ret) { |
| pr_info("%s 0x%x != 0x%x, update\n", __func__, reg_val, ret); |
| |
| ret = s2mu004_i2c_guaranteed_wbyte(i2c, 0x1e, reg_val); |
| if (ret < 0) |
| pr_err("%s err write(%d)\n", __func__, ret); |
| } else { |
| pr_info("%s 0x%x == 0x%x, just return\n", __func__, reg_val, ret); |
| return 0; |
| } |
| |
| ret = s2mu004_i2c_read_byte(i2c, 0x1e); |
| if (ret < 0) |
| pr_err("%s err read reg 0x1e(%d)\n", __func__, ret); |
| else |
| pr_info("%s after change(0x%x)\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int s2mu004_muic_init_otg_reg(struct s2mu004_muic_data *muic_data) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| u8 reg_val; |
| int ret = 0; |
| |
| /* 0x73 : check EVT0 or EVT1 */ |
| ret = s2mu004_i2c_read_byte(i2c, 0x73); |
| if (ret < 0) |
| pr_err("%s err read 'reg 0x73'(%d)\n", __func__, ret); |
| |
| if ((ret&0xF) > 0) |
| return 0; |
| |
| /* 0x89 : hidden register */ |
| ret = s2mu004_i2c_read_byte(i2c, 0x89); |
| if (ret < 0) |
| pr_err("%s err read 'reg 0x89'(%d)\n", __func__, ret); |
| |
| /* Set 0x89[1] bit : T_DET_VAL */ |
| reg_val = ret | (0x1 << 1); |
| |
| if (reg_val ^ ret) { |
| pr_info("%s 0x%x != 0x%x, update\n", __func__, reg_val, ret); |
| |
| ret = s2mu004_i2c_guaranteed_wbyte(i2c, 0x89, reg_val); |
| if (ret < 0) |
| pr_err("%s err write(%d)\n", __func__, ret); |
| } else { |
| pr_info("%s 0x%x == 0x%x, just return\n", __func__, reg_val, ret); |
| return 0; |
| } |
| |
| ret = s2mu004_i2c_read_byte(i2c, 0x89); |
| if (ret < 0) |
| pr_err("%s err read 'reg 0x89'(%d)\n", __func__, ret); |
| else |
| pr_info("%s after change(0x%x)\n", __func__, ret); |
| |
| /* 0x92 : hidden register */ |
| ret = s2mu004_i2c_read_byte(i2c, 0x92); |
| if (ret < 0) |
| pr_err("%s err read 'reg 0x92'(%d)\n", __func__, ret); |
| |
| /* Set 0x92[7] bit : EN_JIG_AP */ |
| reg_val = ret | (0x1 << 7); |
| |
| if (reg_val ^ ret) { |
| pr_info("%s 0x%x != 0x%x, update\n", __func__, reg_val, ret); |
| |
| ret = s2mu004_i2c_guaranteed_wbyte(i2c, 0x92, reg_val); |
| if (ret < 0) |
| pr_err("%s err write(%d)\n", __func__, ret); |
| } else { |
| pr_info("%s 0x%x == 0x%x, just return\n", __func__, reg_val, ret); |
| return 0; |
| } |
| |
| ret = s2mu004_i2c_read_byte(i2c, 0x92); |
| if (ret < 0) |
| pr_err("%s err read 'reg 0x92'(%d)\n", __func__, ret); |
| else |
| pr_info("%s after change(0x%x)\n", __func__, ret); |
| |
| return ret; |
| } |
| #endif /* CONFIG_SEC_FACTORY */ |
| |
| /* TODO: There is no needs to use JIGB pin by MUIC if CCIC is supported */ |
| int s2mu004_muic_jig_on(struct s2mu004_muic_data *muic_data) |
| { |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| bool en = muic_pdata->is_jig_on; |
| int reg = 0, ret = 0; |
| |
| pr_err("%s: %s\n", __func__, en ? "on" : "off"); |
| |
| reg = s2mu004_i2c_read_byte(muic_data->i2c, |
| S2MU004_REG_MUIC_SW_CTRL); |
| |
| if (en) |
| reg |= MANUAL_SW_JIG_EN; |
| else |
| reg &= ~(MANUAL_SW_JIG_EN); |
| |
| ret = s2mu004_i2c_write_byte(muic_data->i2c, |
| S2MU004_REG_MUIC_SW_CTRL, (u8)reg); |
| |
| return ret; |
| |
| } |
| |
| static int s2mu004_muic_set_ctrl_reg(struct s2mu004_muic_data *muic_data, int shift, bool on) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| u8 reg_val; |
| int ret = 0; |
| |
| ret = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_CTRL1); |
| if (ret < 0) |
| pr_err("%s err read CTRL(%d)\n", __func__, ret); |
| |
| if (on) |
| reg_val = ret | (0x1 << shift); |
| else |
| reg_val = ret & ~(0x1 << shift); |
| |
| if (reg_val ^ ret) { |
| pr_info("%s 0x%x != 0x%x, update\n", |
| __func__, reg_val, ret); |
| |
| ret = s2mu004_i2c_guaranteed_wbyte(i2c, S2MU004_REG_MUIC_CTRL1, |
| reg_val); |
| if (ret < 0) |
| pr_err("%s err write(%d)\n", |
| __func__, ret); |
| } else { |
| pr_info("%s 0x%x == 0x%x, just return\n", |
| __func__, reg_val, ret); |
| return 0; |
| } |
| |
| ret = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_CTRL1); |
| if (ret < 0) |
| pr_err("%s err read CTRL(%d)\n", __func__, ret); |
| else |
| pr_info("%s after change(0x%x)\n", |
| __func__, ret); |
| |
| return ret; |
| } |
| |
| static int s2mu004_muic_set_int_mask(struct s2mu004_muic_data *muic_data, bool on) |
| { |
| int shift = CTRL_INT_MASK_SHIFT; |
| int ret = 0; |
| |
| ret = s2mu004_muic_set_ctrl_reg(muic_data, shift, on); |
| |
| return ret; |
| } |
| |
| static int s2mu004_muic_set_com_sw(struct s2mu004_muic_data *muic_data, |
| u8 reg_val) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| int ret = 0; |
| int temp = 0; |
| |
| /* --- MANSW [7:5][4:2][1][0] : DM DP RSVD JIG --- */ |
| temp = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_SW_CTRL); |
| if (temp < 0) |
| pr_err("%s err read MANSW(0x%x)\n", __func__, temp); |
| |
| #if defined(CONFIG_CCIC_S2MU004) && defined(CONFIG_HICCUP_CHARGER) |
| if (muic_data->is_hiccup_mode && IS_WATER_STATUS(muic_data->water_status)) |
| reg_val = MANSW_HICCUP; |
| #endif |
| |
| if ((reg_val & MANUAL_SW_DM_DP_MASK) != (temp & MANUAL_SW_DM_DP_MASK)) { |
| pr_info("%s 0x%x != 0x%x, update\n", __func__, |
| (reg_val & MANUAL_SW_DM_DP_MASK), (temp & MANUAL_SW_DM_DP_MASK)); |
| |
| ret = s2mu004_i2c_guaranteed_wbyte(i2c, |
| S2MU004_REG_MUIC_SW_CTRL, ((reg_val & MANUAL_SW_DM_DP_MASK)|(temp & 0x03))); |
| if (ret < 0) |
| pr_err("%s err write MANSW(0x%x)\n", __func__, |
| (reg_val & MANUAL_SW_DM_DP_MASK) | (temp & 0x03)); |
| } else { |
| pr_info("%s MANSW reg(0x%x), just pass\n", __func__, reg_val); |
| } |
| |
| return ret; |
| } |
| |
| int s2mu004_muic_com_to_open_with_vbus(struct s2mu004_muic_data *muic_data) |
| { |
| u8 reg_val; |
| int ret = 0; |
| |
| reg_val = MANSW_OPEN_WITH_VBUS; |
| ret = s2mu004_muic_set_com_sw(muic_data, reg_val); |
| if (ret) |
| pr_err("%s set_com_sw err\n", __func__); |
| |
| return ret; |
| } |
| |
| int s2mu004_muic_com_to_open(struct s2mu004_muic_data *muic_data) |
| { |
| u8 reg_val; |
| int ret = 0; |
| u8 vbvolt; |
| |
| pr_info("%s\n", __func__); |
| |
| vbvolt = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_MUIC_DEVICE_APPLE); |
| vbvolt &= DEV_TYPE_APPLE_VBUS_WAKEUP; |
| if (vbvolt) { |
| reg_val = MANSW_OPEN_WITH_VBUS; |
| ret = s2mu004_muic_set_com_sw(muic_data, reg_val); |
| if (ret) |
| pr_err("%s s2mu004_muic_set_com_sw err\n", __func__); |
| } |
| |
| reg_val = MANSW_OPEN; |
| ret = s2mu004_muic_set_com_sw(muic_data, reg_val); |
| if (ret) |
| pr_err("%s set_com_sw err\n", __func__); |
| |
| return ret; |
| } |
| |
| int s2mu004_muic_com_to_usb(struct s2mu004_muic_data *muic_data) |
| { |
| u8 reg_val; |
| int ret = 0; |
| |
| reg_val = MANSW_USB; |
| ret = s2mu004_muic_set_com_sw(muic_data, reg_val); |
| if (ret) |
| pr_err("%s set_com_usb err\n", __func__); |
| |
| return ret; |
| } |
| |
| int s2mu004_muic_com_to_otg(struct s2mu004_muic_data *muic_data) |
| { |
| u8 reg_val; |
| int ret = 0; |
| |
| reg_val = MANSW_OTG; |
| ret = s2mu004_muic_set_com_sw(muic_data, reg_val); |
| if (ret) |
| pr_err("%s set_com_otg err\n", __func__); |
| |
| return ret; |
| } |
| |
| int s2mu004_muic_com_to_uart(struct s2mu004_muic_data *muic_data) |
| { |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| u8 reg_val; |
| int ret = 0; |
| |
| if (muic_data->pdata->is_rustproof) { |
| pr_info("%s rustproof mode\n", __func__); |
| return ret; |
| } |
| |
| if (muic_pdata->uart_path == MUIC_PATH_UART_AP) { |
| reg_val = MANSW_UART_AP; |
| #if IS_ENABLED(CONFIG_CP_UART_NOTI) |
| send_uart_noti_to_modem(MODEM_CTRL_UART_AP); |
| #endif |
| } else { |
| reg_val = MANSW_UART_CP; |
| #if IS_ENABLED(CONFIG_CP_UART_NOTI) |
| send_uart_noti_to_modem(MODEM_CTRL_UART_CP); |
| #endif |
| } |
| |
| ret = s2mu004_muic_set_com_sw(muic_data, reg_val); |
| if (ret) |
| pr_err("%s set_com_uart err\n", __func__); |
| |
| return ret; |
| } |
| |
| int s2mu004_muic_com_to_audio(struct s2mu004_muic_data *muic_data) |
| { |
| u8 reg_val; |
| int ret = 0; |
| |
| reg_val = MANSW_AUDIO; |
| ret = s2mu004_muic_set_com_sw(muic_data, reg_val); |
| if (ret) |
| pr_err("%s set_com_audio err\n", __func__); |
| |
| return ret; |
| } |
| |
| #if defined(CONFIG_HICCUP_CHARGER) |
| int s2mu004_muic_com_to_gnd(struct s2mu004_muic_data *muic_data) |
| { |
| u8 reg_val; |
| int ret = 0; |
| |
| reg_val = MANSW_HICCUP; |
| ret = s2mu004_muic_set_com_sw(muic_data, reg_val); |
| if (ret) |
| pr_err("%s set_com_audio err\n", __func__); |
| |
| return ret; |
| } |
| #endif |
| |
| static int s2mu004_muic_set_rid_adc_en(struct s2mu004_muic_data *muic_data, bool en) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| int ret = 0; |
| |
| pr_info("%s rid en : (%d)\n", __func__, en); |
| if (en) { |
| /* enable rid detection for muic dp dm detect */ |
| ret = s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_MUIC_RID_CTRL, RID_CTRL_ADC_OFF_MASK, RID_CTRL_ADC_OFF_SHIFT, 0x0); |
| } else { |
| /* disable rid detection for muic dp dm detect */ |
| ret = s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_MUIC_RID_CTRL, RID_CTRL_ADC_OFF_MASK, RID_CTRL_ADC_OFF_SHIFT, 0x1); |
| } |
| |
| return ret; |
| } |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| static void s2mu004_muic_set_rid_int_mask_en(struct s2mu004_muic_data *muic_data, bool en) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| u8 irq_reg[S2MU004_IRQ_GROUP_NR] = {0}; |
| |
| pr_info("%s en : %d\n", __func__, (int)en); |
| |
| if (en) { |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_MUIC_INT2_MASK, |
| INT_ADC_CHANGE_MASK | INT_RSRV_ATTACH_MASK, |
| 0, INT_ADC_CHANGE_MASK | INT_RSRV_ATTACH_MASK); |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_MUIC_INT1_MASK, |
| INT_DETACH_MASK | INT_ATTACH_MASK, |
| 0, INT_DETACH_MASK | INT_ATTACH_MASK); |
| } else { |
| s2mu004_bulk_read(i2c, S2MU004_REG_MUIC_INT1, |
| S2MU004_NUM_IRQ_MUIC_REGS, &irq_reg[MUIC_INT1]); |
| |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_MUIC_INT2_MASK, |
| INT_ADC_CHANGE_MASK | INT_RSRV_ATTACH_MASK, |
| 0, 0); |
| |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_MUIC_INT1_MASK, |
| INT_DETACH_MASK | INT_ATTACH_MASK, |
| 0, 0); |
| } |
| } |
| #endif |
| |
| int s2mu004_muic_recheck_adc(struct s2mu004_muic_data *muic_data) |
| { |
| int i = 0; |
| struct i2c_client *i2c = muic_data->i2c; |
| int adc = ADC_OPEN; |
| u8 chk_int; |
| |
| s2mu004_muic_set_rid_adc_en(muic_data, false); |
| usleep_range(10000, 12000); |
| s2mu004_read_reg(i2c, 0x6, &chk_int); |
| s2mu004_muic_set_rid_adc_en(muic_data, true); |
| usleep_range(20000, 21000); |
| |
| for (i = 0; i < 50; i++) { |
| usleep_range(1000, 1050); |
| adc = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_ADC) & ADC_MASK; |
| if (adc != ADC_OPEN) { |
| pr_info("%s, %d th try, adc : 0x%x\n", __func__, i, adc); |
| return adc; |
| } |
| } |
| |
| usleep_range(10000, 10500); |
| pr_info("%s, after delay\n", __func__); |
| return s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_ADC) & ADC_MASK; |
| } |
| |
| int s2mu004_muic_refresh_adc(struct s2mu004_muic_data *muic_data) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| int adc = 0; |
| u8 reg_data, b_Rid_en = 0; |
| |
| reg_data = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_RID_CTRL); |
| if (!(reg_data & 0x2)) { |
| b_Rid_en = 1; |
| } else { |
| pr_info("%s, enable the RID\n", __func__); |
| reg_data &= ~(0x01 << 1); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_RID_CTRL, reg_data); |
| msleep(35); |
| } |
| |
| adc = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_ADC); |
| pr_info("%s, adc : 0x%X\n", __func__, adc); |
| |
| if (!b_Rid_en) { |
| pr_info("%s, disable the RID\n", __func__); |
| reg_data |= (0x01 << 1); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_RID_CTRL, reg_data); |
| } |
| return adc; |
| } |
| |
| int s2mu004_muic_get_vbus_state(struct s2mu004_muic_data *muic_data) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| u8 reg_val = 0; |
| int vbus = 0; |
| |
| reg_val = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_DEVICE_APPLE); |
| vbus = !!(reg_val & DEV_TYPE_APPLE_VBUS_WAKEUP); |
| pr_info("%s vbus : (%d)\n", __func__, vbus); |
| return vbus; |
| } |
| static int s2mu004_muic_reg_init(struct s2mu004_muic_data *muic_data) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| int ret = 0, adc = 0; |
| int read_val[READ_VAL_MAX_NUM] = {0, }; |
| #if defined(CONFIG_CCIC_S2MU004) |
| u8 reg_data = 0; |
| #endif |
| #if !IS_ENABLED(CONFIG_MUIC_S2MU004_NON_USB_C_TYPE) |
| #if !IS_ENABLED(CONFIG_CCIC_S2MU004) |
| int data = 0; |
| #endif /* CONFIG_CCIC_S2MU004 */ |
| #endif /* CONFIG_MUIC_S2MU004_NON_USB_C_TYPE */ |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| u8 vbvolt; |
| #endif |
| |
| pr_info("%s\n", __func__); |
| |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| vbvolt = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_MUIC_DEVICE_APPLE); |
| vbvolt &= DEV_TYPE_APPLE_VBUS_WAKEUP; |
| #endif |
| |
| read_val[READ_VAL_DEVICE_TYPE1] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_DEVICE_TYPE1); |
| read_val[READ_VAL_DEVICE_TYPE2] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_DEVICE_TYPE2); |
| read_val[READ_VAL_DEVICE_TYPE3] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_DEVICE_TYPE3); |
| read_val[READ_VAL_ADC] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_ADC); |
| adc = read_val[READ_VAL_ADC]; |
| pr_info("dev[1:0x%x, 2:0x%x, 3:0x%x], adc:0x%x\n", read_val[READ_VAL_DEVICE_TYPE1], |
| read_val[READ_VAL_DEVICE_TYPE2], read_val[READ_VAL_DEVICE_TYPE3], adc); |
| |
| #if !IS_ENABLED(CONFIG_MUIC_S2MU004_NON_USB_C_TYPE) |
| pr_info("%s %s s2mu004 usb-c type\n", MFD_DEV_NAME, __func__); |
| #if IS_ENABLED(CONFIG_CCIC_S2MU004) |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_INT1_MASK, INT_PDIC_MASK1); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_INT2_MASK, INT_PDIC_MASK2); |
| |
| /* set dcd timer out to 0.6s */ |
| reg_data = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_TIMER_SET3); |
| reg_data &= ~TIMER_SET3_DCDTMRSET_MASK; |
| reg_data |= (TIMER_SET3_DCDTMRSET_MASK | 0x4); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_TIMER_SET3, reg_data); |
| |
| /* enable rid detect for waterproof */ |
| s2mu004_muic_control_rid_adc(muic_data, S2MU004_ENABLE); |
| #else |
| s2mu004_muic_control_rid_adc(muic_data, S2MU004_DISABLE); |
| |
| /* adc, RID int masking */ |
| data = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_INT1_MASK); |
| data |= (INTm_ATTACH_MASK | INTm_KP_MASK); |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_INT1_MASK, data); |
| |
| data = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_INT2_MASK); |
| data |= INTm_ADC_CHANGE_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_INT2_MASK, data); |
| #endif /* CONFIG_CCIC_S2MU004 */ |
| #endif /* CONFIG_MUIC_S2MU004_NON_USB_C_TYPE */ |
| |
| s2mu004_muic_control_vbus_det(muic_data, false); |
| ret = s2mu004_i2c_guaranteed_wbyte(i2c, |
| S2MU004_REG_MUIC_CTRL1, CTRL_MASK); |
| if (ret < 0) |
| pr_err("failed to write ctrl(%d)\n", ret); |
| |
| /* |
| * These registers represents the RID ADC LDO voltage control. |
| * Low / High LDO initialized to 3V, 2.7V each. |
| */ |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_LDOADC_VSETL, LDOADC_VSETH_MASK, 0, LDOADC_VSET_3V); |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_LDOADC_VSETH, LDOADC_VSETH_MASK, 0, LDOADC_VSET_2_7V); |
| |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_LDOADC_VSETH, |
| LDOADC_VSETH_WAKE_HYS_MASK, |
| LDOADC_VSETH_WAKE_HYS_SHIFT, 0x1); |
| |
| #if IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) && !IS_ENABLED(CONFIG_SEC_FACTORY) |
| reg_data = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_INT1_MASK); |
| reg_data |= INTm_RID_CHG_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_INT1_MASK, reg_data); |
| |
| reg_data = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_INT2_MASK); |
| reg_data |= INTm_ADC_CHANGE_MASK; |
| s2mu004_i2c_write_byte(i2c, S2MU004_REG_MUIC_INT2_MASK, reg_data); |
| #endif |
| |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| vbus_notifier_handle((!!vbvolt) ? STATUS_VBUS_HIGH : STATUS_VBUS_LOW); |
| #endif /* CONFIG_VBUS_NOTIFIER */ |
| |
| return ret; |
| } |
| #if !IS_ENABLED(CONFIG_SEC_FACTORY) |
| int s2mu004_muic_get_otg_state(void) |
| { |
| struct power_supply *psy_otg; |
| union power_supply_propval val; |
| int ret = 0; |
| |
| psy_otg = get_power_supply_by_name("otg"); |
| if (psy_otg) |
| ret = psy_otg->desc->get_property(psy_otg, POWER_SUPPLY_PROP_CHARGE_POWERED_OTG_CONTROL, &val); |
| else |
| pr_info("%s get psy otg failed\n", __func__); |
| |
| if (ret) { |
| pr_info("%s get prop fail\n", __func__); |
| } else { |
| pr_info("%s is ocp ?: %d\n", __func__, val.intval); |
| return val.intval; |
| } |
| return 0; |
| } |
| #endif |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| static int s2mu004_muic_detect_ccic_jig_cable(struct s2mu004_muic_data *muic_data, |
| int vbvolt, int *intr, muic_attached_dev_t *new_dev) |
| { |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| |
| if (vbvolt) { |
| if (muic_pdata->attached_dev == ATTACHED_DEV_JIG_UART_OFF_VB_MUIC || |
| muic_pdata->attached_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_VB_MUIC; |
| pr_info("MUIC JIG UART OFF VB OFF\n"); |
| return 0; |
| } else if (muic_pdata->attached_dev == ATTACHED_DEV_JIG_UART_ON_VB_MUIC || |
| muic_pdata->attached_dev == ATTACHED_DEV_JIG_UART_ON_MUIC) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_UART_ON_VB_MUIC; |
| pr_info("MUIC JIG UART ON VB OFF\n"); |
| return 0; |
| } else |
| return -1; |
| } else { |
| if (muic_pdata->attached_dev == ATTACHED_DEV_JIG_UART_OFF_VB_MUIC || |
| muic_pdata->attached_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC; |
| pr_info("MUIC JIG UART OFF VB OFF\n"); |
| return 0; |
| } else if (muic_pdata->attached_dev == ATTACHED_DEV_JIG_UART_ON_VB_MUIC || |
| muic_pdata->attached_dev == ATTACHED_DEV_JIG_UART_ON_MUIC) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_UART_ON_MUIC; |
| pr_info("MUIC JIG UART ON VB OFF\n"); |
| return 0; |
| } else |
| return -1; |
| } |
| } |
| |
| static void s2mu004_muic_detect_first_attach(struct s2mu004_muic_data *muic_data, int vbvolt) |
| { |
| struct muic_interface_t *muic_if = (struct muic_interface_t *)muic_data->if_data; |
| int check_adc = 0; |
| |
| muic_data->jig_state = false; |
| muic_data->re_detect = 0; |
| muic_data->afc_check = false; |
| |
| if (muic_data->is_dcd_recheck) |
| muic_data->is_dcd_recheck = false; |
| |
| s2mu004_muic_control_rid_adc(muic_data, S2MU004_ENABLE); |
| |
| msleep(100); |
| |
| check_adc = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_MUIC_ADC); |
| pr_info("%s : check adc at detach (%x)\n", __func__, check_adc); |
| |
| if (check_adc == 0) { |
| muic_data->attach_mode = S2MU004_FIRST_ATTACH; |
| muic_if->is_dcdtmr_intr = true; |
| MUIC_SEND_NOTI_TO_CCIC_ATTACH(ATTACHED_DEV_TYPE3_MUIC); |
| if (vbvolt) { |
| pr_info("%s change mode to second attach!\n", __func__); |
| schedule_delayed_work(&muic_data->dcd_recheck, 0); |
| |
| muic_data->attach_mode = S2MU004_SECOND_ATTACH; |
| } |
| } else { |
| muic_data->attach_mode = S2MU004_NONE_CABLE; |
| MUIC_SEND_NOTI_TO_CCIC_DETACH(muic_data->pdata->attached_dev); |
| } |
| |
| if (muic_data->otg_state) |
| muic_data->otg_state = false; |
| } |
| |
| static void s2mu004_muic_detect_second_attach(struct s2mu004_muic_data *muic_data) |
| { |
| pr_info("%s change mode to second attach!\n", __func__); |
| /* disable rid detection for muic dp dm detect */ |
| s2mu004_muic_control_rid_adc(muic_data, S2MU004_DISABLE); |
| msleep(100); |
| /* start secondary dp dm detect */ |
| s2mu004_muic_bcd_rescan(muic_data); |
| |
| muic_data->attach_mode = S2MU004_SECOND_ATTACH; |
| } |
| |
| static int s2mu004_muic_detect_array_jig_cable(struct s2mu004_muic_data *muic_data, |
| int adc, int vbvolt, int *intr, muic_attached_dev_t *new_dev) |
| { |
| int ret = 0; |
| |
| if (adc == ADC_JIG_UART_OFF) { |
| pr_info("ADC_JIG_UART_OFF\n"); |
| *intr = MUIC_INTR_ATTACH; |
| if (vbvolt) |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_VB_MUIC; |
| else |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC; |
| muic_data->jig_state = true; |
| ret = true; |
| } else if (adc == ADC_JIG_USB_ON || adc == ADC_JIG_USB_OFF || |
| adc == ADC_DESKDOCK) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_USB_ON_MUIC; |
| pr_info("ADC JIG_USB_ON DETECTED\n"); |
| muic_data->jig_state = true; |
| ret = true; |
| #if defined(CONFIG_SEC_FACTORY) |
| } else if (adc == ADC_CEA936ATYPE1_CHG) { |
| if (!vbvolt) |
| return -1; |
| else { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_CARKIT_MUIC; |
| pr_info("SMD DL 255k 200k charger disable\n"); |
| } |
| muic_data->jig_state = true; |
| ret = true; |
| #endif |
| } |
| return ret; |
| } |
| |
| static int s2mu004_muic_detect_with_ccic(struct s2mu004_muic_data *muic_data, |
| int adc, int vbvolt, int *intr, muic_attached_dev_t *new_dev) |
| { |
| struct muic_interface_t *muic_if = (struct muic_interface_t *)muic_data->if_data; |
| |
| if (muic_if == NULL) { |
| pr_err("%s if data NULL\n", __func__); |
| return S2MU004_DETECT_SKIP; |
| } |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| if (muic_data->attach_mode == S2MU004_MUIC_DETACH) { |
| /* FIXME: for VB on-off case */ |
| if ((adc == 0) && muic_data->jig_state) { |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| vbus_notifier_handle((!!vbvolt) ? STATUS_VBUS_HIGH : STATUS_VBUS_LOW); |
| #endif /* CONFIG_VBUS_NOTIFIER */ |
| muic_data->attach_mode = S2MU004_FIRST_ATTACH; |
| if (s2mu004_muic_detect_ccic_jig_cable(muic_data, vbvolt, intr, new_dev)) |
| return S2MU004_DETECT_SKIP; |
| else |
| return S2MU004_DETECT_JIG; |
| } else { |
| s2mu004_muic_detect_first_attach(muic_data, vbvolt); |
| } |
| } else if (muic_data->attach_mode == S2MU004_NONE_CABLE) { |
| #if IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| if (muic_if->opmode & OPMODE_MUIC) { |
| if (s2mu004_muic_detect_array_jig_cable(muic_data, adc, vbvolt, intr, new_dev)) |
| return S2MU004_DETECT_JIG; |
| } |
| #else |
| if (s2mu004_muic_detect_array_jig_cable(muic_data, adc, vbvolt, intr, new_dev)) |
| return S2MU004_DETECT_JIG; |
| #endif |
| /* |
| * opmode usage : |
| * When it's an array status, |
| * the attach mode should maintain the none cable type. |
| * Since the vbvolt can be set in case of the array, |
| * this first attach should be running only when it's not the array status. |
| */ |
| if ((muic_if->opmode & OPMODE_CCIC) && ((adc == ADC_GND) || vbvolt)) { |
| pr_info("%s change mode to first attach!\n", __func__); |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_TYPE3_MUIC; |
| muic_data->attach_mode = S2MU004_FIRST_ATTACH; |
| MUIC_SEND_NOTI_TO_CCIC_ATTACH(ATTACHED_DEV_TYPE3_MUIC); |
| if (vbvolt) { |
| if (muic_data->jig_state == true) { |
| pr_info("%s : not enter second attach for jig\n", __func__); |
| if (s2mu004_muic_detect_ccic_jig_cable(muic_data, |
| vbvolt, intr, new_dev)) |
| return S2MU004_DETECT_SKIP; |
| else |
| return S2MU004_DETECT_JIG; |
| } |
| s2mu004_muic_detect_second_attach(muic_data); |
| } |
| return S2MU004_DETECT_SKIP; |
| } |
| } else if (muic_data->attach_mode == S2MU004_FIRST_ATTACH) { |
| if (vbvolt) { |
| if (muic_data->jig_state == true) { |
| pr_info("%s : not enter second attach for jig\n", __func__); |
| if (s2mu004_muic_detect_ccic_jig_cable(muic_data, vbvolt, intr, new_dev)) |
| return S2MU004_DETECT_SKIP; |
| else |
| return S2MU004_DETECT_JIG; |
| } |
| MUIC_SEND_NOTI_TO_CCIC_ATTACH(ATTACHED_DEV_TYPE3_MUIC); |
| s2mu004_muic_detect_second_attach(muic_data); |
| } |
| return S2MU004_DETECT_SKIP; |
| } else if (!vbvolt) |
| return S2MU004_DETECT_SKIP; |
| |
| if (muic_data->attach_mode == S2MU004_MUIC_OTG && vbvolt) { |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| vbus_notifier_handle((!!vbvolt) ? STATUS_VBUS_HIGH : STATUS_VBUS_LOW); |
| #endif /* CONFIG_VBUS_NOTIFIER */ |
| return S2MU004_DETECT_SKIP; |
| } |
| #endif /* CONFIG_CCIC_S2MU004 */ |
| return S2MU004_DETECT_DONE; |
| } |
| #endif |
| |
| static void s2mu004_muic_get_detect_info(struct s2mu004_muic_data *muic_data, |
| int *read_val, int *adc, int *vbvolt, int *vmid) |
| { |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| struct i2c_client *i2c = muic_data->i2c; |
| |
| read_val[READ_VAL_DEVICE_TYPE1] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_DEVICE_TYPE1); |
| read_val[READ_VAL_DEVICE_TYPE2] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_DEVICE_TYPE2); |
| read_val[READ_VAL_DEVICE_TYPE3] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_DEVICE_TYPE3); |
| read_val[READ_VAL_REV_ID] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_REV_ID); |
| read_val[READ_VAL_ADC] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_ADC); |
| read_val[READ_VAL_DEVICE_APPLE] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_DEVICE_APPLE); |
| read_val[READ_VAL_CHG_TYPE] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_CHG_TYPE); |
| read_val[READ_VAL_SC_STATUS2] = s2mu004_i2c_read_byte(i2c, S2MU004_REG_SC_STATUS2); |
| |
| muic_pdata->adc = *adc = read_val[READ_VAL_ADC]; |
| muic_pdata->vbvolt = *vbvolt = !!(read_val[READ_VAL_DEVICE_APPLE] & DEV_TYPE_APPLE_VBUS_WAKEUP); |
| *vmid = !!(read_val[READ_VAL_SC_STATUS2] & 0x7); |
| |
| pr_info("dev[1:0x%02x, 2:0x%02x, 3:0x%02x]\n", |
| read_val[READ_VAL_DEVICE_TYPE1], |
| read_val[READ_VAL_DEVICE_TYPE2], |
| read_val[READ_VAL_DEVICE_TYPE3]); |
| pr_info("adc:0x%02x, vbvolt:0x%02x, apple:0x%02x\n", |
| *adc, *vbvolt, read_val[READ_VAL_DEVICE_APPLE]); |
| pr_info("chg_type:0x%02x, vmid:0x%02x, dev_id:0x%02x\n", |
| read_val[READ_VAL_CHG_TYPE], *vmid, read_val[READ_VAL_REV_ID]); |
| } |
| |
| static int s2mu004_muic_detect_by_dpdm(struct s2mu004_muic_data *muic_data, |
| int *read_val, int adc, int vbvolt, int *intr, muic_attached_dev_t *new_dev) |
| { |
| #if defined(CONFIG_MUIC_MANAGER) |
| struct muic_interface_t *muic_if = (struct muic_interface_t *)muic_data->if_data; |
| #endif |
| /* Attached */ |
| switch (read_val[READ_VAL_DEVICE_TYPE1]) { |
| case DEV_TYPE1_CDP: |
| if (vbvolt) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_CDP_MUIC; |
| pr_info("USB_CDP DETECTED\n"); |
| } |
| break; |
| case DEV_TYPE1_USB: |
| if (vbvolt) { |
| #ifdef CONFIG_MUIC_MANAGER |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_USB_MUIC; |
| pr_info("USB DETECTED\n"); |
| #else /* CONFIG_MUIC_MANAGER */ |
| if (muic_data->bcd_rescan_cnt++ < MAX_BCD_RESCAN_CNT) { |
| schedule_delayed_work(&muic_data->dcd_recheck, msecs_to_jiffies(10)); |
| return S2MU004_DETECT_SKIP; |
| } else { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_USB_MUIC; |
| pr_info("USB DETECTED\n"); |
| } |
| #endif |
| } |
| break; |
| case DEV_TYPE1_DEDICATED_CHG: |
| case 0x44: |
| case 0x60: |
| if (vbvolt) { |
| #ifdef CONFIG_MUIC_MANAGER |
| muic_if->is_dcp_charger = true; |
| if (muic_data->is_dcd_recheck) { |
| muic_data->is_dcd_recheck = false; |
| cancel_delayed_work(&muic_data->incomplete_check); |
| } |
| #endif /* CONFIG_MUIC_MANAGER */ |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_TA_MUIC; |
| muic_data->afc_check = true; |
| pr_info("DEDICATED CHARGER DETECTED\n"); |
| } |
| break; |
| #ifdef CONFIG_MUIC_S2MU004_NON_USB_C_TYPE |
| case DEV_TYPE1_USB_OTG: |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_OTG_MUIC; |
| pr_info("USB_OTG DETECTED\n"); |
| break; |
| #endif /* CONFIG_MUIC_S2MU004_NON_USB_C_TYPE */ |
| case DEV_TYPE1_T1_T2_CHG: |
| if (vbvolt) { |
| *intr = MUIC_INTR_ATTACH; |
| /* 200K, 442K should be checkef */ |
| if (adc == ADC_CEA936ATYPE2_CHG) { |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_VB_MUIC; |
| pr_info("CEA936ATYPE2_CHG DETECTED\n"); |
| muic_data->afc_check = false; |
| } else { |
| *new_dev = ATTACHED_DEV_USB_MUIC; |
| pr_info("T1_T2_CHG DETECTED\n"); |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| switch (read_val[READ_VAL_DEVICE_TYPE2]) { |
| case DEV_TYPE2_SDP_1P8S: |
| if (vbvolt) { |
| #if IS_ENABLED(CONFIG_SEC_FACTORY) |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_USB_MUIC; |
| pr_info("%s:%s: SDP_1P8S=>USB DETECTED\n", MUIC_DEV_NAME, __func__); |
| #else |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_TIMEOUT_OPEN_MUIC; |
| pr_info("%s:%s: SDP_1P8S DETECTED\n", MUIC_DEV_NAME, __func__); |
| muic_if->is_dcdtmr_intr = true; |
| schedule_delayed_work(&muic_data->dcd_recheck, 0); |
| #else |
| if (muic_data->bcd_rescan_cnt++ < MAX_BCD_RESCAN_CNT) { |
| schedule_delayed_work(&muic_data->dcd_recheck, msecs_to_jiffies(10)); |
| return S2MU004_DETECT_SKIP; |
| } else { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_TIMEOUT_OPEN_MUIC; |
| pr_info("%s:%s: SDP_1P8S DETECTED\n", MUIC_DEV_NAME, __func__); |
| } |
| #endif /* CONFIG_MUIC_MANAGER */ |
| #endif /* CONFIG_SEC_FACTORY */ |
| } |
| break; |
| default: |
| break; |
| } |
| return S2MU004_DETECT_DONE; |
| } |
| |
| #if defined(CONFIG_MUIC_MANAGER) |
| static int s2mu004_muic_detect_jig_dev_type(struct s2mu004_muic_data *muic_data, |
| int *read_val, int vbvolt, int *intr, muic_attached_dev_t *new_dev) |
| { |
| pr_info("%s:%s s2mu004 non usb-c type\n", MFD_DEV_NAME, __func__); |
| switch (read_val[READ_VAL_DEVICE_TYPE2]) { |
| case DEV_TYPE2_JIG_UART_OFF: |
| *intr = MUIC_INTR_ATTACH; |
| if (vbvolt) |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_VB_MUIC; |
| else |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC; |
| pr_info("JIG_UART_OFF DETECTED\n"); |
| break; |
| case DEV_TYPE2_JIG_USB_OFF: |
| if (!vbvolt) |
| break; |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_USB_OFF_MUIC; |
| pr_info("JIG_USB_OFF DETECTED\n"); |
| break; |
| case DEV_TYPE2_JIG_USB_ON: |
| if (!vbvolt) |
| break; |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_USB_ON_MUIC; |
| pr_info("JIG_USB_ON DETECTED\n"); |
| break; |
| case DEV_TYPE2_JIG_UART_ON: |
| if (*new_dev != ATTACHED_DEV_JIG_UART_ON_MUIC) { |
| *intr = MUIC_INTR_ATTACH; |
| #if defined(CONFIG_MUIC_SUPPORT_TYPEB) |
| *new_dev = ATTACHED_DEV_JIG_UART_ON_MUIC; |
| pr_info("ADC JIG_UART_ON DETECTED\n"); |
| #else |
| if (!vbvolt) { |
| *new_dev = ATTACHED_DEV_JIG_UART_ON_MUIC; |
| pr_info("ADC JIG_UART_ON DETECTED\n"); |
| } else { |
| *new_dev = ATTACHED_DEV_JIG_UART_ON_VB_MUIC; |
| pr_info("ADC JIG_UART_ON_VB DETECTED\n"); |
| } |
| #endif |
| } |
| break; |
| default: |
| break; |
| } |
| |
| switch (read_val[READ_VAL_DEVICE_TYPE3]) { |
| case DEV_TYPE3_VBUS_R255: |
| if (!vbvolt) |
| break; |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_USB_OFF_MUIC; |
| pr_info("JIG_USB_OFF DETECTED\n"); |
| break; |
| default: |
| break; |
| } |
| return S2MU004_DETECT_DONE; |
| } |
| #endif |
| |
| #ifdef CONFIG_MUIC_S2MU004_NON_USB_C_TYPE |
| static void s2mu004_muic_detect_jig_by_adc(struct s2mu004_muic_data *muic_data, |
| int *read_val, int adc, int vbvolt, int *vmid, int *intr, muic_attached_dev_t *new_dev) |
| { |
| if (*new_dev == ATTACHED_DEV_UNKNOWN_MUIC) { |
| switch (adc) { |
| case ADC_CEA936ATYPE1_CHG: /*200k ohm */ |
| if (vbvolt) { |
| *intr = MUIC_INTR_ATTACH; |
| /* This is workaournd for LG USB cable which has 219k ohm ID */ |
| *new_dev = ATTACHED_DEV_USB_MUIC; |
| pr_info("TYPE1 CHARGER DETECTED(USB)\n"); |
| } |
| break; |
| case ADC_CEA936ATYPE2_CHG: |
| if (vbvolt) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_UNDEFINED_RANGE_MUIC; |
| muic_data->afc_check = false; |
| pr_info("%s unsupported ADC(0x%02x)\n", |
| __func__, adc); |
| } |
| break; |
| case ADC_JIG_USB_OFF: /* 255k */ |
| if (!vbvolt) |
| break; |
| if (*new_dev != ATTACHED_DEV_JIG_USB_OFF_MUIC) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_USB_OFF_MUIC; |
| pr_info("ADC JIG_USB_OFF DETECTED\n"); |
| } |
| break; |
| case ADC_JIG_USB_ON: |
| if (!vbvolt) |
| break; |
| if (*new_dev != ATTACHED_DEV_JIG_USB_ON_MUIC) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_JIG_USB_ON_MUIC; |
| pr_info("ADC JIG_USB_ON DETECTED\n"); |
| } |
| break; |
| case ADC_JIG_UART_OFF: |
| *intr = MUIC_INTR_ATTACH; |
| if (muic_data->pdata->is_otg_test) { |
| mdelay(100); |
| read_val[READ_VAL_SC_STATUS2] = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_SC_STATUS2); |
| *vmid = read_val[READ_VAL_SC_STATUS2] & 0x7; |
| if (*vmid == 0x4) { |
| pr_info("OTG_TEST DETECTED, vmid = %d\n", *vmid); |
| vbvolt = 1; |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC; |
| } else |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC; |
| } else if (vbvolt) |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_VB_MUIC; |
| else |
| *new_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC; |
| |
| pr_info("ADC JIG_UART_OFF DETECTED\n"); |
| break; |
| case ADC_JIG_UART_ON: |
| if (*new_dev != ATTACHED_DEV_JIG_UART_ON_MUIC) { |
| *intr = MUIC_INTR_ATTACH; |
| #if defined(CONFIG_MUIC_SUPPORT_TYPEB) |
| *new_dev = ATTACHED_DEV_JIG_UART_ON_MUIC; |
| pr_info("ADC JIG_UART_ON DETECTED\n"); |
| #else |
| if (!vbvolt) { |
| *new_dev = ATTACHED_DEV_JIG_UART_ON_MUIC; |
| pr_info("ADC JIG_UART_ON DETECTED\n"); |
| } else { |
| *new_dev = ATTACHED_DEV_JIG_UART_ON_VB_MUIC; |
| pr_info("ADC JIG_UART_ON_VB DETECTED\n"); |
| } |
| #endif |
| } |
| break; |
| case ADC_SMARTDOCK: /* 0x10000 40.2K ohm */ |
| /* SMARTDOCK is not supported */ |
| /* force not to charge the device with SMARTDOCK */ |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_UNSUPPORTED_ID_VB_MUIC; |
| pr_info("%s unsupported ADC(0x%02x) but charging\n", |
| __func__, adc); |
| break; |
| case ADC_HMT: /* 0x10001 49.9K ohm */ |
| *new_dev = ATTACHED_DEV_UNKNOWN_MUIC; |
| *intr = MUIC_INTR_ATTACH; |
| pr_info("%s unsupported ADC(0x%02x) not charging\n", |
| __func__, adc); |
| break; |
| case ADC_AUDIODOCK: |
| *intr = MUIC_INTR_ATTACH; |
| #ifdef CONFIG_MUIC_S2MU004_SUPPORT_AUDIODOCK |
| *new_dev = ATTACHED_DEV_AUDIODOCK_MUIC; |
| #else |
| *new_dev = ATTACHED_DEV_UNDEFINED_RANGE_MUIC; |
| #endif |
| pr_info("ADC AUDIODOCK DETECTED\n"); |
| break; |
| case ADC_UNIVERSAL_MMDOCK: |
| *new_dev = ATTACHED_DEV_UNKNOWN_MUIC; |
| *intr = MUIC_INTR_ATTACH; |
| pr_info("%s unsupported ADC(0x%02x) not charging\n", |
| __func__, adc); |
| break; |
| case ADC_OPEN: |
| /* sometimes muic fails to catch JIG_UART_OFF detaching */ |
| /* double check with ADC */ |
| if (*new_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC) { |
| *new_dev = ATTACHED_DEV_UNKNOWN_MUIC; |
| *intr = MUIC_INTR_DETACH; |
| pr_info("ADC OPEN DETECTED\n"); |
| } |
| break; |
| default: |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_UNDEFINED_RANGE_MUIC; |
| pr_info("%s unsupported ADC(0x%02x)\n", |
| __func__, adc); |
| break; |
| } |
| } |
| } |
| #endif |
| |
| static void s2mu004_muic_detect_extra_cable(struct s2mu004_muic_data *muic_data, |
| int *read_val, int vbvolt, int *intr, muic_attached_dev_t *new_dev) |
| { |
| #if defined(CONFIG_MUIC_MANAGER) |
| struct muic_interface_t *muic_if = (struct muic_interface_t *)muic_data->if_data; |
| #endif |
| /* This is for Apple cables */ |
| if (vbvolt && ((read_val[READ_VAL_DEVICE_APPLE] & DEV_TYPE_APPLE_APPLE2P4A_CHG) |
| || (read_val[READ_VAL_DEVICE_APPLE] & DEV_TYPE_APPLE_APPLE2A_CHG) |
| || (read_val[READ_VAL_DEVICE_APPLE] & DEV_TYPE_APPLE_APPLE1A_CHG) |
| || (read_val[READ_VAL_DEVICE_APPLE] & DEV_TYPE_APPLE_APPLE0P5A_CHG))) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_TA_MUIC; |
| muic_data->afc_check = false; |
| pr_info("APPLE_CHG DETECTED\n"); |
| #ifdef CONFIG_MUIC_MANAGER |
| muic_if->is_dcdtmr_intr = true; |
| schedule_delayed_work(&muic_data->dcd_recheck, 0); |
| #endif |
| } |
| |
| if ((read_val[READ_VAL_CHG_TYPE] & DEV_TYPE_CHG_TYPE) && |
| (*new_dev == ATTACHED_DEV_UNKNOWN_MUIC)) { |
| *intr = MUIC_INTR_ATTACH; |
| *new_dev = ATTACHED_DEV_TA_MUIC; |
| muic_data->afc_check = false; |
| pr_info("CHG_TYPE DETECTED\n"); |
| #ifdef CONFIG_MUIC_MANAGER |
| muic_if->is_dcdtmr_intr = true; |
| schedule_delayed_work(&muic_data->dcd_recheck, 0); |
| #endif |
| } |
| } |
| |
| static void update_jig_state(struct muic_platform_data *pdata) |
| { |
| int jig_state; |
| |
| switch (pdata->attached_dev) { |
| case ATTACHED_DEV_JIG_UART_OFF_MUIC: |
| case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: /* VBUS enabled */ |
| case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: /* for otg test */ |
| case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC: /* for fg test */ |
| case ATTACHED_DEV_JIG_UART_ON_MUIC: |
| case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: /* VBUS enabled */ |
| case ATTACHED_DEV_JIG_USB_OFF_MUIC: |
| case ATTACHED_DEV_JIG_USB_ON_MUIC: |
| jig_state = true; |
| break; |
| default: |
| jig_state = false; |
| break; |
| } |
| pr_info("%s jig_state : %d\n", __func__, jig_state); |
| |
| pdata->jig_uart_cb(jig_state); |
| } |
| |
| static void s2mu004_muic_detect_dev(struct s2mu004_muic_data *muic_data) |
| { |
| muic_attached_dev_t new_dev = ATTACHED_DEV_UNKNOWN_MUIC; |
| int intr = MUIC_INTR_DETACH; |
| int vbvolt = 0, vmid = 0, adc = 0; |
| int read_val[READ_VAL_MAX_NUM] = {0, }; |
| int ret; |
| #if defined(CONFIG_MUIC_MANAGER) |
| struct muic_interface_t *muic_if = (struct muic_interface_t *)muic_data->if_data; |
| #endif |
| usleep_range(15000, 18000); |
| s2mu004_muic_get_detect_info(muic_data, read_val, &adc, &vbvolt, &vmid); |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| ret = s2mu004_muic_detect_with_ccic(muic_data, adc, vbvolt, &intr, &new_dev); |
| switch (ret) { |
| case S2MU004_DETECT_SKIP: |
| return; |
| break; |
| case S2MU004_DETECT_JIG: |
| goto jig; |
| break; |
| case S2MU004_DETECT_DONE: |
| default: |
| break; |
| } |
| #endif |
| ret = s2mu004_muic_detect_by_dpdm(muic_data, read_val, adc, vbvolt, &intr, &new_dev); |
| if (ret == S2MU004_DETECT_SKIP) |
| return; |
| |
| #if defined(CONFIG_MUIC_MANAGER) |
| if (!(muic_if->opmode & OPMODE_CCIC)) { |
| s2mu004_muic_detect_jig_dev_type(muic_data, read_val, vbvolt, &intr, &new_dev); |
| } |
| #endif |
| |
| #ifdef CONFIG_MUIC_S2MU004_NON_USB_C_TYPE |
| s2mu004_muic_detect_jig_by_adc(muic_data, read_val, adc, vbvolt, &vmid, &intr, &new_dev); |
| #endif |
| |
| s2mu004_muic_detect_extra_cable(muic_data, read_val, vbvolt, &intr, &new_dev); |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| /* FIXME: for VB on-off case */ |
| jig: |
| #endif/* CONFIG_CCIC_S2MU004 */ |
| #ifndef CONFIG_CCIC_S2MU004 |
| muic_data->bcd_rescan_cnt = 0; |
| #endif |
| |
| if (intr == MUIC_INTR_ATTACH) { |
| #ifdef CONFIG_MUIC_MANAGER |
| muic_manager_set_legacy_dev(muic_if, new_dev); |
| #endif |
| muic_core_handle_attach(muic_data->pdata, new_dev, adc, vbvolt); |
| } else { |
| if (muic_data->attach_mode == S2MU004_SECOND_ATTACH) |
| return; |
| #ifdef CONFIG_MUIC_MANAGER |
| if (muic_if->opmode & OPMODE_CCIC) { |
| if (muic_core_get_ccic_cable_state(muic_data->pdata) && (muic_if->is_ccic_attached == true)) { |
| pr_info("[muic] %s, skipped handle detach!\n", __func__); |
| return; |
| } |
| } |
| #endif |
| #if IS_ENABLED(CONFIG_HV_MUIC_S2MU004_AFC) |
| ret = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_AFC_CTRL1); |
| if (ret) { |
| pr_info("%s, need to AFC detach\n", __func__); |
| s2mu004_hv_muic_reset_hvcontrol_reg(muic_data); |
| } |
| #endif |
| muic_core_handle_detach(muic_data->pdata); |
| } |
| |
| if (muic_data->pdata->jig_uart_cb) |
| update_jig_state(muic_data->pdata); |
| |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| vbus_notifier_handle((!!vbvolt) ? STATUS_VBUS_HIGH : STATUS_VBUS_LOW); |
| #endif /* CONFIG_VBUS_NOTIFIER */ |
| } |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| static int s2mu004_muic_check_irq_exeptions(struct s2mu004_muic_data *muic_data, |
| struct muic_interface_t *muic_if, int irq_num, int adc, int vbvolt) |
| { |
| #if !IS_ENABLED(CONFIG_SEC_FACTORY) |
| if ((irq_num == S2MU004_MUIC_IRQ2_VBUS_OFF) && |
| (muic_if->opmode & OPMODE_CCIC) && |
| !muic_data->jig_state && |
| s2mu004_muic_get_otg_state()) |
| MUIC_SEND_NOTI_TO_CCIC_ATTACH(ATTACHED_DEV_CHECK_OCP); |
| #endif |
| |
| #if !IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| if (((adc > 0 && adc < ADC_OPEN) || (adc & 0x80)) |
| && !muic_data->re_detect && !vbvolt |
| && (muic_if->opmode & OPMODE_CCIC) |
| && ((irq_num == S2MU004_MUIC_IRQ2_ADC_CHANGE) |
| || (irq_num == S2MU004_MUIC_IRQ1_ATTATCH))) { |
| adc = s2mu004_muic_water_judge(muic_data); |
| } |
| #endif |
| |
| #if defined(CONFIG_HICCUP_CHARGER) |
| if (!lpcharge && vbvolt && |
| IS_WATER_STATUS(muic_data->water_status)) { |
| muic_data->is_hiccup_mode = true; |
| s2mu004_muic_com_to_gnd(muic_data); |
| } |
| #endif |
| |
| pr_info("%s adc 0x%x, vbvolt : %d, irq_num : %d\n", |
| __func__, adc, vbvolt, irq_num); |
| adc &= ADC_MASK; |
| if (((irq_num == S2MU004_MUIC_IRQ2_ADC_CHANGE) || (irq_num == S2MU004_MUIC_IRQ1_ATTATCH)) |
| && !vbvolt && adc != ADC_GND && (muic_if->opmode & OPMODE_CCIC)) { |
| pr_info("%s:%d adc : 0x%X, water_status : %d, vbvolt : %d\n", |
| __func__, __LINE__, adc, muic_data->water_status, vbvolt); |
| #if !IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| if (adc < 0x1F && muic_data->water_status == S2MU004_WATER_MUIC_IDLE) { |
| cancel_delayed_work(&muic_data->water_detect_handler); |
| schedule_delayed_work(&muic_data->water_detect_handler, |
| msecs_to_jiffies(0)); |
| } else if (adc == 0x1F && IS_WATER_STATUS(muic_data->water_status)) { |
| cancel_delayed_work(&muic_data->water_dry_handler); |
| schedule_delayed_work(&muic_data->water_dry_handler, |
| msecs_to_jiffies(WATER_DET_STABLE_DURATION_MS + 1000)); |
| msleep(100); |
| } else if ((muic_data->water_status == S2MU004_WATER_MUIC_CCIC_DET || |
| muic_data->water_status == S2MU004_WATER_MUIC_CCIC_STABLE || |
| muic_data->water_status == S2MU004_WATER_MUIC_DET) && |
| IS_WATER_ADC(adc)) { |
| s2mu004_muic_set_rid_adc_en(muic_data, false); |
| pr_info("%s WATER Toggling(audio),, adc : 0x%X\n", __func__, adc); |
| } |
| |
| return S2MU004_IRQ_SKIP; |
| } else if (muic_data->water_status == S2MU004_WATER_MUIC_CCIC_STABLE) { |
| if (irq_num == S2MU004_MUIC_IRQ2_VBUS_OFF || |
| irq_num == S2MU004_MUIC_IRQ2_VBUS_ON) { |
| MUIC_SEND_NOTI_DETACH(ATTACHED_DEV_UNDEFINED_RANGE_MUIC); |
| MUIC_SEND_NOTI_ATTACH(ATTACHED_DEV_UNDEFINED_RANGE_MUIC); |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| vbus_notifier_handle(vbvolt ? STATUS_VBUS_HIGH : STATUS_VBUS_LOW); |
| #endif /* CONFIG_VBUS_NOTIFIER */ |
| } |
| pr_info("%s WATER DETECT : Wet cable inserted\n", __func__); |
| pr_info("%s : skipped by water detected condition\n", __func__); |
| return S2MU004_IRQ_SKIP; |
| #endif |
| } else if (irq_num == S2MU004_MUIC_IRQ2_VBUS_OFF) { |
| if (muic_data->attach_mode == S2MU004_MUIC_OTG) { |
| s2mu004_muic_control_vbus_det(muic_data, false); |
| muic_data->otg_state = true; |
| |
| if (s2mu004_muic_refresh_adc(muic_data) == ADC_JIG_UART_OFF) { |
| pr_info("%s OTG - VBUS off case\n", __func__); |
| muic_data->attach_mode = S2MU004_NONE_CABLE; |
| } |
| } else |
| muic_data->attach_mode = S2MU004_MUIC_DETACH; |
| |
| /* FIXME: for VB on-off case */ |
| /* muic_data->jig_state = false; */ |
| |
| } else if (irq_num == S2MU004_MUIC_IRQ1_DETACH && |
| muic_data->attach_mode == S2MU004_FIRST_ATTACH) { |
| |
| muic_data->attach_mode = S2MU004_MUIC_DETACH; |
| } |
| return S2MU004_IRQ_CHECK_DONE; |
| } |
| #endif |
| |
| static irqreturn_t s2mu004_muic_irq_thread(int irq, void *data) |
| { |
| struct s2mu004_muic_data *muic_data = data; |
| struct irq_desc *desc = irq_to_desc(irq); |
| #if defined(CONFIG_CCIC_S2MU004) |
| struct muic_interface_t *muic_if = muic_data->if_data; |
| struct i2c_client *i2c = muic_data->i2c; |
| int ret; |
| int irq_num = irq - muic_data->s2mu004_dev->irq_base; |
| int vbvolt, adc; |
| #endif |
| |
| pr_info("%s %s start\n", |
| desc ? desc->action->name : "-1", |
| mode_to_str(muic_data->attach_mode)); |
| |
| mutex_lock(&muic_data->muic_mutex); |
| wake_lock(&muic_data->wake_lock); |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| /* NONE_CABLE : default muic mode that cable is empty |
| * DETACH : MUIC real DETACH occur |
| * SECOND ATTACH : there is vbus, so re-detect dp, dm for TA & USB |
| * FRIST ATTACH : there is cable but no vbus |
| * OTG : there is otg cable |
| */ |
| |
| /* divide timing that call the detect_dev() */ |
| /* when Vbus off, force rid to enable */ |
| vbvolt = s2mu004_muic_get_vbus_state(muic_data); |
| adc = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_ADC); |
| |
| #if defined(CONFIG_VBUS_NOTIFIER) |
| if (irq_num == S2MU004_MUIC_IRQ2_VBUS_OFF) |
| vbus_notifier_handle(STATUS_VBUS_LOW); |
| #endif /* CONFIG_VBUS_NOTIFIER */ |
| |
| ret = s2mu004_muic_check_irq_exeptions(muic_data, muic_if, irq_num, adc, vbvolt); |
| if (ret == S2MU004_IRQ_SKIP) |
| goto EOH; |
| |
| pr_info("%s attach_mode : %d, irq_num: %d\n", |
| __func__, muic_data->attach_mode, irq_num); |
| |
| #if IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| if (irq_num == S2MU004_MUIC_IRQ2_VBUS_ON) { |
| cancel_delayed_work(&muic_data->bad_cable_checker); |
| schedule_delayed_work(&muic_data->bad_cable_checker, |
| msecs_to_jiffies(1500)); |
| } |
| #endif |
| if ((muic_data->attach_mode == S2MU004_NONE_CABLE) || |
| ((muic_data->attach_mode == S2MU004_MUIC_DETACH) && |
| !((vbvolt) && (irq_num == S2MU004_MUIC_IRQ2_VBUS_OFF))) || |
| ((muic_data->attach_mode == S2MU004_SECOND_ATTACH) && |
| (irq_num == S2MU004_MUIC_IRQ1_ATTATCH) && |
| !muic_data->jig_state) || |
| ((muic_data->attach_mode == S2MU004_FIRST_ATTACH) && |
| (irq_num == S2MU004_MUIC_IRQ2_VBUS_ON)) || |
| ((muic_data->attach_mode == S2MU004_MUIC_OTG) && |
| (irq_num == S2MU004_MUIC_IRQ2_VBUS_ON))) |
| s2mu004_muic_detect_dev(muic_data); |
| EOH: |
| #else |
| s2mu004_muic_detect_dev(muic_data); |
| #endif |
| |
| wake_unlock(&muic_data->wake_lock); |
| mutex_unlock(&muic_data->muic_mutex); |
| |
| pr_info("%s %s done\n", |
| dev_to_str(muic_data->pdata->attached_dev), |
| mode_to_str(muic_data->attach_mode)); |
| |
| return IRQ_HANDLED; |
| } |
| |
| #if IS_ENABLED(CONFIG_MUIC_SUPPORT_CCIC) |
| static void s2mu004_muic_put_dry_chk_time(struct s2mu004_muic_data *muic_data) |
| { |
| struct timeval time; |
| |
| do_gettimeofday(&time); |
| pr_info("%s Dry check time : %ld\n", __func__, (long)time.tv_sec); |
| muic_data->dry_chk_time = (long)time.tv_sec; |
| } |
| |
| static void s2mu004_muic_set_water_adc_ldo_wa(struct s2mu004_muic_data *muic_data, bool en) |
| { |
| struct i2c_client *i2c = muic_data->i2c; |
| |
| pr_info("%s en : (%d)\n", __func__, (int)en); |
| if (en) { |
| /* W/A apply */ |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_LDOADC_VSETH, LDOADC_VSETH_MASK, 0, LDOADC_VSET_1_2V); |
| usleep_range(WATER_TOGGLE_WA_DURATION_US, WATER_TOGGLE_WA_DURATION_US + 1000); |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_LDOADC_VSETH, LDOADC_VSETH_MASK, 0, LDOADC_VSET_1_4V); |
| } else { |
| /* W/A unapply */ |
| s2mu004_i2c_update_bit(i2c, |
| S2MU004_REG_LDOADC_VSETH, LDOADC_VSETH_MASK, 0, LDOADC_VSET_2_7V); |
| } |
| } |
| |
| #if !IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| static int s2mu004_muic_water_judge(struct s2mu004_muic_data *muic_data) |
| { |
| int i, adc_recheck = 0; |
| |
| pr_info("%s : enter\n", __func__); |
| for (i = 0; i < WATER_DET_RETRY_CNT; i++) { |
| adc_recheck = s2mu004_muic_recheck_adc(muic_data); |
| if (adc_recheck == ADC_GND) { |
| pr_info("%s : NOT WATER\n", __func__); |
| return adc_recheck; |
| } |
| if (s2mu004_muic_get_vbus_state(muic_data)) { |
| pr_info("%s : vbus while detecting\n", __func__); |
| return ADC_GND; |
| } |
| |
| pr_info("%s : %d st try : adc(0x%x)\n", __func__, i, adc_recheck); |
| } |
| |
| if (adc_recheck != ADC_OPEN) |
| muic_data->re_detect = 1; |
| else |
| muic_data->re_detect = 0; |
| |
| return adc_recheck; |
| } |
| #endif |
| |
| static void s2mu004_muic_water_detect_handler(struct work_struct *work) |
| { |
| struct s2mu004_muic_data *muic_data = |
| container_of(work, struct s2mu004_muic_data, water_detect_handler.work); |
| int wait_ret = 0, adc = 0; |
| #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) |
| int vbvolt = 0; |
| #endif |
| |
| mutex_lock(&muic_data->water_det_mutex); |
| wake_lock(&muic_data->water_wake_lock); |
| if (muic_data->water_status != S2MU004_WATER_MUIC_IDLE) { |
| pr_info("%s %d exit detect, due to status mismatch\n", __func__, __LINE__); |
| goto EXIT_DETECT; |
| } |
| |
| pr_info("%s\n", __func__); |
| |
| s2mu004_muic_set_rid_adc_en(muic_data, false); |
| muic_data->water_status = S2MU004_WATER_MUIC_DET; |
| MUIC_SEND_NOTI_TO_CCIC_ATTACH(ATTACHED_DEV_CHK_WATER_REQ); |
| |
| wait_ret = wait_event_interruptible_timeout(muic_data->wait, |
| muic_data->water_status >= S2MU004_WATER_MUIC_CCIC_DET, |
| msecs_to_jiffies(WATER_CCIC_WAIT_DURATION_MS)); |
| |
| if ((wait_ret < 0) || (!wait_ret)) { |
| pr_err("%s wait_q abnormal, status : %d\n", __func__, muic_data->water_status); |
| muic_data->water_status = S2MU004_WATER_MUIC_IDLE; |
| muic_data->re_detect = 0; |
| s2mu004_muic_set_water_adc_ldo_wa(muic_data, false); |
| s2mu004_muic_set_rid_adc_en(muic_data, true); |
| } else { |
| if (muic_data->water_status == S2MU004_WATER_MUIC_CCIC_DET) { |
| pr_info("%s: WATER DETECT!!!\n", __func__); |
| muic_data->dry_cnt = 0; |
| muic_data->dry_duration_sec = WATER_DRY_RETRY_INTERVAL_SEC; |
| MUIC_SEND_NOTI_ATTACH(ATTACHED_DEV_UNDEFINED_RANGE_MUIC); |
| s2mu004_i2c_update_bit(muic_data->i2c, |
| 0xD5, |
| LDOADC_VSETH_WAKE_HYS_MASK, |
| LDOADC_VSETH_WAKE_HYS_SHIFT, 0x1); |
| s2mu004_muic_set_water_adc_ldo_wa(muic_data, true); |
| msleep(100); |
| adc = s2mu004_muic_recheck_adc(muic_data); |
| msleep(2000); |
| muic_data->water_status = S2MU004_WATER_MUIC_CCIC_STABLE; |
| muic_data->water_dry_status = S2MU004_WATER_DRY_MUIC_IDLE; |
| #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) |
| vbvolt = s2mu004_muic_get_vbus_state(muic_data); |
| vbus_notifier_handle(vbvolt ? STATUS_VBUS_HIGH : STATUS_VBUS_LOW); |
| #endif /* CONFIG_VBUS_NOTIFIER */ |
| s2mu004_muic_put_dry_chk_time(muic_data); |
| cancel_delayed_work(&muic_data->water_dry_handler); |
| schedule_delayed_work(&muic_data->water_dry_handler, |
| msecs_to_jiffies(1800000)); |
| pr_info("%s %d WATER DETECT stabled adc : 0x%X\n", __func__, __LINE__, adc); |
| } else if (muic_data->water_status == S2MU004_WATER_MUIC_CCIC_INVALID) { |
| pr_info("%s Not Water From CCIC.\n", __func__); |
| muic_data->water_status = S2MU004_WATER_MUIC_IDLE; |
| muic_data->re_detect = 0; |
| s2mu004_muic_set_water_adc_ldo_wa(muic_data, false); |
| s2mu004_muic_set_rid_adc_en(muic_data, true); |
| } |
| } |
| EXIT_DETECT: |
| wake_unlock(&muic_data->water_wake_lock); |
| mutex_unlock(&muic_data->water_det_mutex); |
| } |
| |
| static void s2mu004_muic_water_dry_handler(struct work_struct *work) |
| { |
| struct s2mu004_muic_data *muic_data = |
| container_of(work, struct s2mu004_muic_data, water_dry_handler.work); |
| int adc, i, wait_ret = 0; |
| |
| mutex_lock(&muic_data->water_dry_mutex); |
| wake_lock(&muic_data->water_dry_wake_lock); |
| |
| if (muic_data->water_status != S2MU004_WATER_MUIC_CCIC_STABLE) { |
| pr_info("%s Invalid status for Dry check\n", __func__); |
| goto EXIT_DRY_STATE; |
| } |
| |
| pr_info("%s Dry check start\n", __func__); |
| s2mu004_muic_put_dry_chk_time(muic_data); |
| s2mu004_muic_set_rid_int_mask_en(muic_data, true); |
| s2mu004_muic_set_water_adc_ldo_wa(muic_data, false); |
| |
| if (muic_data->dry_cnt++ > 5) { |
| muic_data->dry_duration_sec = 1800; |
| pr_info("%s Dry check cnt : %d\n", __func__, muic_data->dry_cnt); |
| } |
| |
| for (i = 0; i < WATER_DET_RETRY_CNT; i++) { |
| adc = s2mu004_muic_recheck_adc(muic_data); |
| pr_info("%s, %d th try, adc : 0x%X\n", __func__, i, (char)adc); |
| if (adc < 0x1F) { |
| pr_info("%s WATER IS NOT DRIED YET!!!\n", __func__); |
| s2mu004_muic_set_rid_adc_en(muic_data, false); |
| cancel_delayed_work(&muic_data->water_dry_handler); |
| schedule_delayed_work(&muic_data->water_dry_handler, |
| msecs_to_jiffies(1800000)); |
| msleep(1000); |
| goto EXIT_DRY; |
| } |
| } |
| muic_data->water_dry_status = S2MU004_WATER_DRY_MUIC_DET; |
| MUIC_SEND_NOTI_TO_CCIC_ATTACH(ATTACHED_DEV_CHK_WATER_DRY_REQ); |
| wait_ret = wait_event_interruptible_timeout(muic_data->wait, |
| muic_data->water_dry_status >= S2MU004_WATER_DRY_MUIC_CCIC_DET, |
| msecs_to_jiffies(WATER_CCIC_WAIT_DURATION_MS)); |
| |
| if ((wait_ret < 0) || (!wait_ret) |
| || muic_data->water_dry_status == S2MU004_WATER_DRY_MUIC_CCIC_INVALID) { |
| pr_err("%s wait_q abnormal, status : %d\n", |
| __func__, muic_data->water_dry_status); |
| s2mu004_muic_set_rid_adc_en(muic_data, false); |
| muic_data->water_dry_status = S2MU004_WATER_DRY_MUIC_IDLE; |
| s2mu004_muic_set_water_adc_ldo_wa(muic_data, true); |
| msleep(1000); |
| muic_data->water_status = S2MU004_WATER_MUIC_CCIC_STABLE; |
| cancel_delayed_work(&muic_data->water_dry_handler); |
| schedule_delayed_work(&muic_data->water_dry_handler, |
| msecs_to_jiffies(WATER_DRY_RETRY_INTERVAL_MS)); |
| } else if (muic_data->water_dry_status == S2MU004_WATER_DRY_MUIC_CCIC_DET) { |
| pr_info("%s WATER DRIED!!!\n", __func__); |
| s2mu004_muic_set_water_adc_ldo_wa(muic_data, false); |
| msleep(500); |
| MUIC_SEND_NOTI_DETACH(ATTACHED_DEV_UNDEFINED_RANGE_MUIC); |
| MUIC_SEND_NOTI_TO_CCIC_DETACH(ATTACHED_DEV_UNDEFINED_RANGE_MUIC); |
| muic_data->attach_mode = S2MU004_NONE_CABLE; |
| muic_data->water_status = S2MU004_WATER_MUIC_IDLE; |
| muic_data->water_dry_status = S2MU004_WATER_DRY_MUIC_IDLE; |
| muic_data->re_detect = 0; |
| #if defined(CONFIG_HICCUP_CHARGER) |
| muic_data->is_hiccup_mode = false; |
| #endif |
| muic_data->dry_duration_sec = WATER_DRY_RETRY_INTERVAL_SEC; |
| muic_data->dry_cnt = 0; |
| } |
| EXIT_DRY: |
| pr_info("%s %d Exit DRY handler!!!\n", __func__, __LINE__); |
| s2mu004_muic_set_rid_int_mask_en(muic_data, false); |
| EXIT_DRY_STATE: |
| wake_unlock(&muic_data->water_dry_wake_lock); |
| mutex_unlock(&muic_data->water_dry_mutex); |
| } |
| |
| static void s2mu004_muic_sleep_dry_checker(struct work_struct *work) |
| { |
| struct s2mu004_muic_data *muic_data = |
| container_of(work, struct s2mu004_muic_data, sleep_dry_checker.work); |
| struct timeval time; |
| long duration; |
| |
| if (muic_data->water_status == S2MU004_WATER_MUIC_CCIC_STABLE && muic_data->lcd_on) { |
| if (!s2mu004_muic_get_vbus_state(muic_data)) { |
| do_gettimeofday(&time); |
| duration = (long)time.tv_sec - muic_data->dry_chk_time; |
| pr_info("%s dry check duration : (%ld)\n", __func__, duration); |
| if (duration > muic_data->dry_duration_sec || duration < 0) { |
| cancel_delayed_work(&muic_data->water_dry_handler); |
| schedule_delayed_work(&muic_data->water_dry_handler, 0); |
| } |
| } |
| } |
| } |
| |
| #if IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| static void s2mu004_muic_bad_cable_checker(struct work_struct *work) |
| { |
| struct s2mu004_muic_data *muic_data = |
| container_of(work, struct s2mu004_muic_data, bad_cable_checker.work); |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| |
| pr_info("%s entered\n", __func__); |
| |
| if (!s2mu004_muic_get_vbus_state(muic_data)) { |
| pr_info("%s vbus detached while checking.\n", __func__); |
| return; |
| } |
| |
| if (!MUIC_IS_ATTACHED(muic_pdata->attached_dev)) { |
| pr_info("%s detected dev(%s)\n", __func__, dev_to_str(muic_pdata->attached_dev)); |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| muic_manager_set_legacy_dev(muic_pdata->muic_if, ATTACHED_DEV_TIMEOUT_OPEN_MUIC); |
| #endif |
| muic_core_handle_attach(muic_pdata, ATTACHED_DEV_TIMEOUT_OPEN_MUIC, |
| muic_pdata->adc, muic_pdata->vbvolt); |
| } |
| } |
| #endif |
| #endif |
| |
| #ifdef CONFIG_CCIC_S2MU004 |
| static void s2mu004_muic_incomplete_chk_handler(struct work_struct *work) |
| { |
| struct s2mu004_muic_data *muic_data = |
| container_of(work, struct s2mu004_muic_data, incomplete_check.work); |
| |
| pr_info("%s, is_dcd_recheck : %d\n", __func__, (int)muic_data->is_dcd_recheck); |
| |
| if (muic_data->is_dcd_recheck) |
| s2mu004_muic_dcd_rescan(muic_data); |
| } |
| #endif |
| |
| static void s2mu004_muic_dcd_recheck(struct work_struct *work) |
| { |
| struct s2mu004_muic_data *muic_data = |
| container_of(work, struct s2mu004_muic_data, dcd_recheck.work); |
| |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| struct muic_interface_t *muic_if = muic_data->if_data; |
| |
| pr_info("%s\n", __func__); |
| |
| muic_manager_dcd_rescan(muic_if); |
| #else |
| mutex_lock(&muic_data->muic_mutex); |
| wake_lock(&muic_data->wake_lock); |
| |
| pr_info("%s\n", __func__); |
| |
| s2mu004_muic_dcd_rescan(muic_data); |
| s2mu004_muic_detect_dev(muic_data); |
| |
| wake_unlock(&muic_data->wake_lock); |
| mutex_unlock(&muic_data->muic_mutex); |
| #endif /* CONFIG_MUIC_MANAGER */ |
| } |
| |
| void muic_disable_otg_detect(void) |
| { |
| |
| pr_info("%s\n", __func__); |
| |
| s2mu004_muic_control_vbus_det(static_data, S2MU004_DISABLE); |
| } |
| |
| void s2mu004_muic_dcd_rescan(struct s2mu004_muic_data *muic_data) |
| { |
| int ret = 0; |
| int reg_val = 0; |
| struct i2c_client *i2c = muic_data->i2c; |
| |
| mutex_lock(&muic_data->switch_mutex); |
| pr_info("%s call\n", __func__); |
| |
| /* muic mux switch open */ |
| reg_val = s2mu004_i2c_read_byte(i2c, S2MU004_REG_MUIC_SW_CTRL); |
| ret = s2mu004_muic_com_to_open(muic_data); |
| if (ret < 0) |
| pr_err("%s, fail to open mansw\n", __func__); |
| |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| s2mu004_muic_control_rid_adc(muic_data, S2MU004_ENABLE); |
| msleep(50); |
| s2mu004_muic_control_rid_adc(muic_data, S2MU004_DISABLE); |
| msleep(100); |
| s2mu004_muic_bcd_rescan(muic_data); |
| #if !IS_ENABLED(CONFIG_SEC_FACTORY) |
| msleep(650); |
| #else |
| msleep(50); |
| #endif |
| #else |
| s2mu004_muic_bcd_rescan(muic_data); |
| msleep(190); |
| #endif /* CONFIG_MUIC_MANAGER */ |
| |
| /* restore muic mux switch */ |
| ret = s2mu004_i2c_guaranteed_wbyte(i2c, S2MU004_REG_MUIC_SW_CTRL, reg_val); |
| if (ret < 0) |
| pr_err("%s err write MANSW(0x%x)\n", __func__, reg_val); |
| |
| mutex_unlock(&muic_data->switch_mutex); |
| } |
| |
| static int s2mu004_init_rev_info(struct s2mu004_muic_data *muic_data) |
| { |
| u8 dev_id; |
| int ret = 0; |
| |
| dev_id = s2mu004_i2c_read_byte(muic_data->i2c, S2MU004_REG_REV_ID); |
| if (dev_id < 0) { |
| pr_err("%s(%d)\n", __func__, dev_id); |
| ret = -ENODEV; |
| } else { |
| muic_data->muic_vendor = 0x05; |
| muic_data->muic_version = (dev_id & 0x0F); |
| muic_data->ic_rev_id = (dev_id & 0xF0) >> 4; |
| pr_info("%s : vendor=0x%x, ver=0x%x, dev_id=0x%x\n rev id =0x%x\n", |
| __func__, muic_data->muic_vendor, muic_data->muic_version, |
| dev_id, muic_data->ic_rev_id); |
| } |
| return ret; |
| } |
| |
| static int s2mu004_muic_irq_init(struct s2mu004_muic_data *muic_data) |
| { |
| int ret = 0; |
| |
| if (muic_data->mfd_pdata && (muic_data->mfd_pdata->irq_base > 0)) { |
| int irq_base = muic_data->mfd_pdata->irq_base; |
| |
| /* request MUIC IRQ */ |
| muic_data->irq_attach = irq_base + S2MU004_MUIC_IRQ1_ATTATCH; |
| REQUEST_IRQ(muic_data->irq_attach, muic_data, "muic-attach"); |
| |
| muic_data->irq_detach = irq_base + S2MU004_MUIC_IRQ1_DETACH; |
| REQUEST_IRQ(muic_data->irq_detach, muic_data, "muic-detach"); |
| |
| muic_data->irq_rid_chg = irq_base + S2MU004_MUIC_IRQ1_RID_CHG; |
| REQUEST_IRQ(muic_data->irq_rid_chg, muic_data, "muic-rid_chg"); |
| |
| muic_data->irq_vbus_on = irq_base + S2MU004_MUIC_IRQ2_VBUS_ON; |
| REQUEST_IRQ(muic_data->irq_vbus_on, muic_data, "muic-vbus_on"); |
| |
| muic_data->irq_rsvd_attach = irq_base + S2MU004_MUIC_IRQ2_RSVD_ATTACH; |
| REQUEST_IRQ(muic_data->irq_rsvd_attach, muic_data, "muic-rsvd_attach"); |
| |
| muic_data->irq_adc_change = irq_base + S2MU004_MUIC_IRQ2_ADC_CHANGE; |
| REQUEST_IRQ(muic_data->irq_adc_change, muic_data, "muic-adc_change"); |
| |
| muic_data->irq_av_charge = irq_base + S2MU004_MUIC_IRQ2_AV_CHARGE; |
| REQUEST_IRQ(muic_data->irq_av_charge, muic_data, "muic-av_charge"); |
| |
| muic_data->irq_vbus_off = irq_base + S2MU004_MUIC_IRQ2_VBUS_OFF; |
| REQUEST_IRQ(muic_data->irq_vbus_off, muic_data, "muic-vbus_off"); |
| |
| } |
| |
| pr_info("%s muic-attach(%d), muic-detach(%d), muic-rid_chg(%d), muic-vbus_on(%d)", |
| __func__, muic_data->irq_attach, muic_data->irq_detach, muic_data->irq_rid_chg, |
| muic_data->irq_vbus_on); |
| pr_info("muic-rsvd_attach(%d), muic-adc_change(%d), muic-av_charge(%d), muic-vbus_off(%d)\n", |
| muic_data->irq_rsvd_attach, muic_data->irq_adc_change, muic_data->irq_av_charge, muic_data->irq_vbus_off); |
| |
| return ret; |
| } |
| |
| static void s2mu004_muic_free_irqs(struct s2mu004_muic_data *muic_data) |
| { |
| pr_info("%s\n", __func__); |
| |
| /* free MUIC IRQ */ |
| FREE_IRQ(muic_data->irq_attach, muic_data, "muic-attach"); |
| FREE_IRQ(muic_data->irq_detach, muic_data, "muic-detach"); |
| FREE_IRQ(muic_data->irq_rid_chg, muic_data, "muic-rid_chg"); |
| FREE_IRQ(muic_data->irq_vbus_on, muic_data, "muic-vbus_on"); |
| FREE_IRQ(muic_data->irq_rsvd_attach, muic_data, "muic-rsvd_attach"); |
| FREE_IRQ(muic_data->irq_adc_change, muic_data, "muic-adc_change"); |
| FREE_IRQ(muic_data->irq_av_charge, muic_data, "muic-av_charge"); |
| FREE_IRQ(muic_data->irq_vbus_off, muic_data, "muic-vbus_off"); |
| } |
| |
| #if IS_ENABLED(CONFIG_OF) |
| static int of_s2mu004_muic_dt(struct device *dev, |
| struct s2mu004_muic_data *muic_data) |
| { |
| struct device_node *np, *np_muic; |
| int ret = 0; |
| |
| np = dev->parent->of_node; |
| if (!np) { |
| pr_err("%s : could not find np\n", __func__); |
| return -ENODEV; |
| } |
| |
| np_muic = of_find_node_by_name(np, "muic"); |
| if (!np_muic) { |
| pr_err("%s : could not find muic sub-node np_muic\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* FIXME */ |
| #if !IS_ENABLED(CONFIG_MUIC_UART_SWITCH) |
| if (of_gpio_count(np_muic) < 1) { |
| pr_err("%s : could not find muic gpio\n", __func__); |
| muic_data->pdata->gpio_uart_sel = 0; |
| } else |
| muic_data->pdata->gpio_uart_sel = of_get_gpio(np_muic, 0); |
| #else |
| muic_data->pdata->uart_addr = |
| (const char *)of_get_property(np_muic, "muic,uart_addr", NULL); |
| muic_data->pdata->uart_txd = |
| (const char *)of_get_property(np_muic, "muic,uart_txd", NULL); |
| muic_data->pdata->uart_rxd = |
| (const char *)of_get_property(np_muic, "muic,uart_rxd", NULL); |
| #endif |
| |
| return ret; |
| } |
| #endif /* CONFIG_OF */ |
| |
| static void s2mu004_muic_init_drvdata(struct s2mu004_muic_data *muic_data, |
| struct s2mu004_dev *s2mu004, struct platform_device *pdev, |
| struct s2mu004_platform_data *mfd_pdata) |
| { |
| /* save platfom data for gpio control functions */ |
| muic_data->s2mu004_dev = s2mu004; |
| muic_data->dev = &pdev->dev; |
| muic_data->i2c = s2mu004->i2c; |
| muic_data->mfd_pdata = mfd_pdata; |
| muic_data->temp = 0; |
| muic_data->attach_mode = S2MU004_NONE_CABLE; |
| muic_data->jig_state = false; |
| muic_data->re_detect = 0; |
| muic_data->afc_check = false; |
| muic_data->otg_state = false; |
| muic_data->is_dcd_recheck = false; |
| #if defined(CONFIG_CCIC_S2MU004) |
| muic_data->water_status = S2MU004_WATER_MUIC_IDLE; |
| muic_data->water_dry_status = S2MU004_WATER_DRY_MUIC_IDLE; |
| muic_data->dry_chk_time = 0; |
| muic_data->dry_cnt = 0; |
| muic_data->dry_duration_sec = WATER_DRY_RETRY_INTERVAL_SEC; |
| #endif |
| } |
| |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| static void s2mu004_muic_init_interface(struct s2mu004_muic_data *muic_data, |
| struct muic_interface_t *muic_if) |
| { |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| |
| pr_info("%s, muic_if : 0x%p, muic_data : 0x%p\n", |
| __func__, muic_if, muic_data); |
| |
| muic_if->muic_data = (void *)muic_data; |
| muic_if->set_com_to_open_with_vbus = s2mu004_if_com_to_open_with_vbus; |
| muic_if->set_com_to_open = s2mu004_if_com_to_open; |
| muic_if->set_switch_to_usb = s2mu004_if_switch_to_usb; |
| muic_if->set_switch_to_uart = s2mu004_if_switch_to_uart; |
| muic_if->get_vbus = s2mu004_if_get_vbus; |
| muic_if->set_jig_state = s2mu004_if_set_jig_state; |
| muic_if->set_cable_state = s2mu004_if_set_cable_state; |
| muic_if->set_otg_detect_en = s2mu004_if_set_otg_detect_en; |
| muic_if->set_dcd_rescan = s2mu004_if_dcd_rescan; |
| muic_if->set_jig_ctrl_on = s2mu004_if_jig_on; |
| muic_if->set_com_to_audio = s2mu004_if_com_to_audio; |
| muic_if->set_com_to_otg = s2mu004_if_com_to_otg; |
| muic_if->get_adc = s2mu004_if_get_adc; |
| muic_if->set_gpio_uart_sel = s2mu004_if_set_gpio_uart_sel; |
| muic_if->bcd_rescan = s2mu004_if_bcd_rescan; |
| muic_if->control_rid_adc = s2mu004_if_control_rid_adc; |
| #if defined(CONFIG_HV_MUIC_S2MU004_AFC) |
| muic_if->set_afc_reset = s2mu004_if_set_afc_reset; |
| muic_if->check_id_err = s2mu004_if_check_id_err; |
| muic_if->reset_hvcontrol_reg = s2mu004_if_reset_hvcontrol_reg; |
| muic_if->check_afc_ready = s2mu004_if_check_afc_ready; |
| muic_if->reset_afc_register = s2mu004_if_reset_afc_register; |
| muic_if->set_afc_ready = s2mu004_if_set_afc_ready; |
| #endif |
| muic_if->check_usb_killer = s2mu004_if_check_usb_killer; |
| #if defined(CONFIG_CCIC_S2MU004) |
| muic_if->set_water_detect = s2mu004_if_set_water_det; |
| #endif |
| #ifndef CONFIG_SEC_FACTORY |
| #if defined(CONFIG_CCIC_S2MU004) |
| muic_if->set_water_detect_from_boot = s2mu004_if_set_water_det_from_boot; |
| #endif |
| #endif |
| muic_data->if_data = muic_if; |
| muic_pdata->muic_if = muic_if; |
| } |
| #endif |
| |
| #if IS_ENABLED(CONFIG_MUIC_SUPPORT_CCIC) |
| static int muic_fb_notifier_event(struct notifier_block *this, |
| unsigned long val, void *v) |
| { |
| struct fb_event *evdata = v; |
| struct s2mu004_muic_data *muic_data = |
| container_of(this, struct s2mu004_muic_data, fb_notifier); |
| int fb_blank = 0; |
| |
| if (evdata->info->node) |
| return NOTIFY_DONE; |
| |
| switch (val) { |
| case FB_EVENT_BLANK: |
| break; |
| default: |
| return NOTIFY_DONE; |
| } |
| |
| fb_blank = *(int *)evdata->data; |
| |
| if (fb_blank == FB_BLANK_UNBLANK) { |
| pr_info("%s: lcd on\n", __func__); |
| muic_data->lcd_on = true; |
| } else |
| muic_data->lcd_on = false; |
| |
| return NOTIFY_DONE; |
| } |
| #endif |
| |
| static int s2mu004_muic_probe(struct platform_device *pdev) |
| { |
| struct s2mu004_dev *s2mu004 = dev_get_drvdata(pdev->dev.parent); |
| struct s2mu004_platform_data *mfd_pdata = dev_get_platdata(s2mu004->dev); |
| struct s2mu004_muic_data *muic_data; |
| struct muic_platform_data *muic_pdata; |
| int ret = 0; |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| struct muic_interface_t *muic_if; |
| #endif |
| |
| pr_info("%s start\n", __func__); |
| |
| muic_data = devm_kzalloc(&pdev->dev, sizeof(*muic_data), GFP_KERNEL); |
| if (unlikely(!muic_data)) { |
| pr_err("%s out of memory\n", __func__); |
| ret = -ENOMEM; |
| goto err_return; |
| } |
| static_data = muic_data; |
| |
| if (unlikely(!mfd_pdata)) { |
| pr_err("%s failed to get s2mu004 mfd platform data\n", |
| __func__); |
| ret = -ENOMEM; |
| goto err_kfree1; |
| } |
| |
| muic_pdata = muic_core_init(muic_data); |
| if (unlikely(!muic_pdata)) |
| goto err_kfree1; |
| |
| muic_data->pdata = muic_pdata; |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| muic_if = muic_manager_init(muic_pdata, muic_data); |
| if (!muic_if) { |
| pr_err("%s failed to init muic manager, ret : 0x%X\n", |
| __func__, ret); |
| goto err_init_manager; |
| } |
| |
| s2mu004_muic_init_interface(muic_data, muic_if); |
| #endif |
| |
| s2mu004_muic_init_drvdata(muic_data, s2mu004, pdev, mfd_pdata); |
| |
| #if IS_ENABLED(CONFIG_MUIC_SUPPORT_CCIC) |
| muic_data->fb_notifier.priority = -1; |
| muic_data->fb_notifier.notifier_call = muic_fb_notifier_event; |
| fb_register_client(&muic_data->fb_notifier); |
| #endif |
| |
| #if defined(CONFIG_OF) |
| ret = of_s2mu004_muic_dt(&pdev->dev, muic_data); |
| if (ret < 0) |
| pr_err("no muic dt! ret[%d]\n", ret); |
| #endif /* CONFIG_OF */ |
| |
| mutex_init(&muic_data->muic_mutex); |
| mutex_init(&muic_data->switch_mutex); |
| wake_lock_init(&muic_data->wake_lock, WAKE_LOCK_SUSPEND, "muic_wake"); |
| #ifdef CONFIG_CCIC_S2MU004 |
| mutex_init(&muic_data->water_det_mutex); |
| mutex_init(&muic_data->water_dry_mutex); |
| wake_lock_init(&muic_data->water_wake_lock, |
| WAKE_LOCK_SUSPEND, "muic_water_wake"); |
| wake_lock_init(&muic_data->water_dry_wake_lock, |
| WAKE_LOCK_SUSPEND, "muic_water_dry_wake"); |
| #endif |
| init_waitqueue_head(&muic_data->wait); |
| platform_set_drvdata(pdev, muic_data); |
| |
| if (muic_data->pdata->init_gpio_cb) |
| ret = muic_data->pdata->init_gpio_cb(muic_data->pdata, get_switch_sel()); |
| if (ret) { |
| pr_err("%s failed to init gpio(%d)\n", __func__, ret); |
| goto fail_init_gpio; |
| } |
| |
| ret = s2mu004_muic_init_sysfs(muic_data); |
| if (ret) { |
| pr_err("failed to create sysfs\n"); |
| goto fail_init_sysfs; |
| } |
| |
| ret = s2mu004_init_rev_info(muic_data); |
| if (ret) { |
| pr_err("failed to init muic(%d)\n", ret); |
| goto fail; |
| } |
| |
| ret = s2mu004_muic_reg_init(muic_data); |
| if (ret) { |
| pr_err("failed to init muic(%d)\n", ret); |
| goto fail; |
| } |
| |
| #if IS_ENABLED(CONFIG_HV_MUIC_S2MU004_AFC) |
| /* initial hv cable detection */ |
| if (muic_data->is_afc_muic_ready) |
| s2mu004_hv_muic_init_detect(muic_data); |
| |
| s2mu004_hv_muic_initialize(muic_data); |
| #endif /* CONFIG_HV_MUIC_S2MU004_AFC */ |
| |
| #if !defined(CONFIG_SEC_FACTORY) && defined(CONFIG_MUIC_SUPPORT_TYPEB) |
| muic_pdata->is_rustproof = muic_pdata->rustproof_on; |
| #endif |
| if (muic_pdata->is_rustproof) { |
| pr_err("%s rustproof is enabled\n", __func__); |
| s2mu004_muic_com_to_open_with_vbus(muic_data); |
| } |
| |
| #if IS_ENABLED(CONFIG_HV_MUIC_S2MU004_AFC) |
| if (get_afc_mode() == CH_MODE_AFC_DISABLE_VAL) { |
| pr_info("AFC mode disabled\n"); |
| muic_data->pdata->afc_disable = true; |
| } else { |
| pr_info("AFC mode enabled\n"); |
| muic_data->pdata->afc_disable = false; |
| } |
| #endif /* CONFIG_HV_MUIC_S2MU004_AFC */ |
| |
| ret = s2mu004_muic_irq_init(muic_data); |
| if (ret) { |
| pr_err("%s failed to init irq(%d)\n", __func__, ret); |
| goto fail_init_irq; |
| } |
| |
| #if IS_ENABLED(CONFIG_HV_MUIC_S2MU004_AFC) |
| ret = s2mu004_afc_muic_irq_init(muic_data); |
| if (ret < 0) { |
| pr_err("%s Failed to initialize HV MUIC irq:%d\n", |
| __func__, ret); |
| s2mu004_hv_muic_free_irqs(muic_data); |
| } |
| #endif /* CONFIG_HV_MUIC_S2MU004_AFC */ |
| |
| /* initial cable detection */ |
| s2mu004_muic_set_int_mask(muic_data, false); |
| #if IS_ENABLED(CONFIG_SEC_FACTORY) |
| s2mu004_muic_init_otg_reg(muic_data); |
| #endif |
| |
| #ifdef CONFIG_CCIC_S2MU004 |
| INIT_DELAYED_WORK(&muic_data->water_dry_handler, s2mu004_muic_water_dry_handler); |
| INIT_DELAYED_WORK(&muic_data->water_detect_handler, s2mu004_muic_water_detect_handler); |
| INIT_DELAYED_WORK(&muic_data->incomplete_check, s2mu004_muic_incomplete_chk_handler); |
| INIT_DELAYED_WORK(&muic_data->sleep_dry_checker, s2mu004_muic_sleep_dry_checker); |
| #endif |
| INIT_DELAYED_WORK(&muic_data->dcd_recheck, s2mu004_muic_dcd_recheck); |
| #if IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| INIT_DELAYED_WORK(&muic_data->bad_cable_checker, |
| s2mu004_muic_bad_cable_checker); |
| #endif |
| s2mu004_muic_irq_thread(-1, muic_data); |
| |
| #if IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL) |
| if (muic_if->opmode & OPMODE_CCIC) { |
| cancel_delayed_work(&muic_data->bad_cable_checker); |
| schedule_delayed_work(&muic_data->bad_cable_checker, |
| msecs_to_jiffies(3000)); |
| } |
| #endif |
| |
| if (!s2mu004_muic_get_vbus_state(muic_data)) { |
| pr_info("%s : init adc : 0x%X\n", __func__, |
| s2mu004_muic_recheck_adc(muic_data)); |
| } |
| |
| return 0; |
| |
| fail_init_irq: |
| fail: |
| #ifdef CONFIG_SEC_SYSFS |
| s2mu004_muic_deinit_sysfs(muic_data); |
| #endif |
| fail_init_sysfs: |
| fail_init_gpio: |
| mutex_destroy(&muic_data->muic_mutex); |
| #if IS_ENABLED(CONFIG_MUIC_MANAGER) |
| err_init_manager: |
| #endif |
| err_kfree1: |
| err_return: |
| return ret; |
| } |
| |
| /* if need to set s2mu004 pdata */ |
| static const struct of_device_id s2mu004_muic_match_table[] = { |
| { .compatible = "samsung,s2mu004-muic",}, |
| {}, |
| }; |
| |
| static int s2mu004_muic_remove(struct platform_device *pdev) |
| { |
| struct s2mu004_muic_data *muic_data = platform_get_drvdata(pdev); |
| |
| if (muic_data) { |
| pr_info("%s\n", __func__); |
| |
| #ifdef CONFIG_SEC_SYSFS |
| s2mu004_muic_deinit_sysfs(muic_data); |
| #endif |
| muic_manager_exit(muic_data->if_data); |
| muic_core_exit(muic_data->pdata); |
| |
| disable_irq_wake(muic_data->i2c->irq); |
| s2mu004_muic_free_irqs(muic_data); |
| mutex_destroy(&muic_data->muic_mutex); |
| mutex_destroy(&muic_data->switch_mutex); |
| mutex_destroy(&muic_data->water_det_mutex); |
| mutex_destroy(&muic_data->water_dry_mutex); |
| i2c_set_clientdata(muic_data->i2c, NULL); |
| } |
| |
| return 0; |
| } |
| |
| static void s2mu004_muic_shutdown(struct platform_device *pdev) |
| { |
| struct s2mu004_muic_data *muic_data = platform_get_drvdata(pdev); |
| |
| pr_info("%s\n", __func__); |
| |
| if (!muic_data->i2c) { |
| pr_err("%s no muic i2c client\n", __func__); |
| return; |
| } |
| |
| #if IS_ENABLED(CONFIG_HV_MUIC_S2MU004_AFC) |
| s2mu004_hv_muic_remove(muic_data); |
| #endif |
| } |
| |
| #if IS_ENABLED(CONFIG_PM) |
| static int s2mu004_muic_suspend(struct device *dev) |
| { |
| struct s2mu004_muic_data *muic_data = dev_get_drvdata(dev); |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| |
| muic_pdata->suspended = true; |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| if (muic_data->water_status == S2MU004_WATER_MUIC_CCIC_STABLE) { |
| cancel_delayed_work(&muic_data->sleep_dry_checker); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int s2mu004_muic_resume(struct device *dev) |
| { |
| struct s2mu004_muic_data *muic_data = dev_get_drvdata(dev); |
| struct muic_platform_data *muic_pdata = muic_data->pdata; |
| |
| muic_pdata->suspended = false; |
| |
| if (muic_pdata->need_to_noti) { |
| if (muic_pdata->attached_dev) |
| MUIC_SEND_NOTI_ATTACH(muic_pdata->attached_dev); |
| else |
| MUIC_SEND_NOTI_DETACH(muic_pdata->attached_dev); |
| muic_pdata->need_to_noti = false; |
| } |
| |
| #if defined(CONFIG_CCIC_S2MU004) |
| if (muic_data->water_status == S2MU004_WATER_MUIC_CCIC_STABLE) { |
| cancel_delayed_work(&muic_data->sleep_dry_checker); |
| schedule_delayed_work(&muic_data->sleep_dry_checker, |
| msecs_to_jiffies(WATER_WAKEUP_WAIT_DURATION_MS)); |
| } |
| #endif |
| |
| return 0; |
| } |
| #else |
| #define s2mu004_muic_suspend NULL |
| #define s2mu004_muic_resume NULL |
| #endif |
| |
| static SIMPLE_DEV_PM_OPS(s2mu004_muic_pm_ops, s2mu004_muic_suspend, |
| s2mu004_muic_resume); |
| |
| static struct platform_driver s2mu004_muic_driver = { |
| .probe = s2mu004_muic_probe, |
| .remove = s2mu004_muic_remove, |
| .shutdown = s2mu004_muic_shutdown, |
| .driver = { |
| .name = "s2mu004-muic", |
| .owner = THIS_MODULE, |
| .of_match_table = s2mu004_muic_match_table, |
| #if IS_ENABLED(CONFIG_PM) |
| .pm = &s2mu004_muic_pm_ops, |
| #endif |
| }, |
| }; |
| |
| static int __init s2mu004_muic_init(void) |
| { |
| return platform_driver_register(&s2mu004_muic_driver); |
| } |
| module_init(s2mu004_muic_init); |
| |
| static void __exit s2mu004_muic_exit(void) |
| { |
| platform_driver_unregister(&s2mu004_muic_driver); |
| } |
| module_exit(s2mu004_muic_exit); |
| |
| MODULE_DESCRIPTION("Samsung S2MU004 Micro USB IC driver"); |
| MODULE_LICENSE("GPL"); |