blob: c7d7d1ec7dc7d89bf31173445d8aa9d3b7a36416 [file] [log] [blame]
/*
* max77705-muic.c - MUIC driver for the Maxim 77705
*
* Copyright (C) 2015 Samsung Electronics
* Insun Choi <insun77.choi@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/workqueue.h>
#include <linux/switch.h>
/* MUIC header file */
#include <linux/mfd/max77705.h>
#include <linux/mfd/max77705-private.h>
#include <linux/muic/muic.h>
#include <linux/muic/max77705-muic.h>
#include <linux/ccic/max77705.h>
#include <linux/ccic/max77705_usbc.h>
#if defined(CONFIG_MUIC_NOTIFIER)
#include <linux/muic/muic_notifier.h>
#endif /* CONFIG_MUIC_NOTIFIER */
#if defined(CONFIG_VBUS_NOTIFIER)
#include <linux/vbus_notifier.h>
#endif /* CONFIG_VBUS_NOTIFIER */
#if defined(CONFIG_USB_EXTERNAL_NOTIFY)
#include <linux/usb_notify.h>
#endif
#include <linux/sec_debug.h>
#include <linux/sec_ext.h>
#include <linux/sec_batt.h>
#include "../battery_v2/include/sec_charging_common.h"
struct max77705_muic_data *g_muic_data;
/* for the bringup, should be fixed */
void __init_usbc_cmd_data(usbc_cmd_data *cmd_data)
{
pr_warn("%s is not defined!\n", __func__);
}
void __max77705_usbc_opcode_write(struct max77705_usbc_platform_data *usbc_data,
usbc_cmd_data *write_op)
{
pr_warn("%s is not defined!\n", __func__);
}
void init_usbc_cmd_data(usbc_cmd_data *cmd_data)
__attribute__((weak, alias("__init_usbc_cmd_data")));
void max77705_usbc_opcode_write(struct max77705_usbc_platform_data *usbc_data,
usbc_cmd_data *write_op)
__attribute__((weak, alias("__max77705_usbc_opcode_write")));
static bool debug_en_vps;
static void max77705_muic_detect_dev(struct max77705_muic_data *muic_data, int irq);
struct max77705_muic_vps_data {
int adc;
int vbvolt;
int chgtyp;
int muic_switch;
const char *vps_name;
const muic_attached_dev_t attached_dev;
};
static int max77705_muic_read_reg
(struct i2c_client *i2c, u8 reg, u8 *value)
{
int ret = max77705_read_reg(i2c, reg, value);
return ret;
}
#if 0
static int max77705_muic_write_reg
(struct i2c_client *i2c, u8 reg, u8 value, bool debug_en)
{
int ret = max77705_write_reg(i2c, reg, value);
if (debug_en)
pr_info("%s Reg[0x%02x]: 0x%02x\n",
__func__, reg, value);
return ret;
}
#endif
#if defined(CONFIG_VBUS_NOTIFIER)
static void max77705_muic_handle_vbus(struct max77705_muic_data *muic_data)
{
int vbvolt = muic_data->status3 & BC_STATUS_VBUSDET_MASK;
vbus_status_t status = (vbvolt > 0) ?
STATUS_VBUS_HIGH : STATUS_VBUS_LOW;
pr_info("%s <%d>\n", __func__, status);
vbus_notifier_handle(status);
}
#endif
static const struct max77705_muic_vps_data muic_vps_table[] = {
{
.adc = MAX77705_UIADC_523K,
.vbvolt = VB_LOW,
.chgtyp = CHGTYP_NO_VOLTAGE,
.muic_switch = COM_UART,
.vps_name = "JIG UART OFF",
.attached_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC,
},
{
.adc = MAX77705_UIADC_523K,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_DONTCARE,
.muic_switch = COM_UART,
.vps_name = "JIG UART OFF/VB",
.attached_dev = ATTACHED_DEV_JIG_UART_OFF_VB_MUIC,
},
{
.adc = MAX77705_UIADC_619K,
.vbvolt = VB_LOW,
.chgtyp = CHGTYP_NO_VOLTAGE,
.muic_switch = COM_UART,
.vps_name = "JIG UART ON",
.attached_dev = ATTACHED_DEV_JIG_UART_ON_MUIC,
},
{
.adc = MAX77705_UIADC_619K,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_DONTCARE,
.muic_switch = COM_UART,
.vps_name = "JIG UART ON/VB",
.attached_dev = ATTACHED_DEV_JIG_UART_ON_VB_MUIC,
},
{
.adc = MAX77705_UIADC_255K,
#if defined(CONFIG_SEC_FACTORY)
.vbvolt = VB_DONTCARE,
#else
.vbvolt = VB_HIGH,
#endif
.chgtyp = CHGTYP_DONTCARE,
.muic_switch = COM_USB,
.vps_name = "JIG USB OFF",
.attached_dev = ATTACHED_DEV_JIG_USB_OFF_MUIC,
},
{
.adc = MAX77705_UIADC_301K,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_DONTCARE,
.muic_switch = COM_USB,
.vps_name = "JIG USB ON",
.attached_dev = ATTACHED_DEV_JIG_USB_ON_MUIC,
},
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_DEDICATED_CHARGER,
.muic_switch = COM_OPEN,
.vps_name = "TA",
.attached_dev = ATTACHED_DEV_TA_MUIC,
},
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_USB,
.muic_switch = COM_USB,
.vps_name = "USB",
.attached_dev = ATTACHED_DEV_USB_MUIC,
},
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_TIMEOUT_OPEN,
.muic_switch = COM_USB,
.vps_name = "DCD Timeout",
.attached_dev = ATTACHED_DEV_TIMEOUT_OPEN_MUIC,
},
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_CDP,
.muic_switch = COM_USB,
.vps_name = "CDP",
.attached_dev = ATTACHED_DEV_CDP_MUIC,
},
{
.adc = MAX77705_UIADC_GND,
.vbvolt = VB_DONTCARE,
.chgtyp = CHGTYP_DONTCARE,
.muic_switch = COM_USB,
.vps_name = "OTG",
.attached_dev = ATTACHED_DEV_OTG_MUIC,
},
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_UNOFFICIAL_CHARGER,
.muic_switch = COM_OPEN,
.vps_name = "Unofficial TA",
.attached_dev = ATTACHED_DEV_UNOFFICIAL_TA_MUIC,
},
#if defined(CONFIG_HICCUP_CHARGER)
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_DONTCARE,
.chgtyp = CHGTYP_HICCUP_MODE,
.muic_switch = COM_USB_CP,
.vps_name = "Hiccup mode",
.attached_dev = ATTACHED_DEV_HICCUP_MUIC,
},
#endif
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_DEDICATED_CHARGER,
.muic_switch = COM_OPEN,
.vps_name = "AFC Charger",
.attached_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC,
},
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_DEDICATED_CHARGER,
.muic_switch = COM_OPEN,
.vps_name = "AFC Charger",
.attached_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC,
},
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_DEDICATED_CHARGER,
.muic_switch = COM_OPEN,
.vps_name = "QC Charger",
.attached_dev = ATTACHED_DEV_QC_CHARGER_9V_MUIC,
},
{
.adc = MAX77705_UIADC_OPEN,
.vbvolt = VB_HIGH,
.chgtyp = CHGTYP_DEDICATED_CHARGER,
.muic_switch = COM_OPEN,
.vps_name = "QC Charger",
.attached_dev = ATTACHED_DEV_QC_CHARGER_5V_MUIC,
},
#endif
};
static int muic_lookup_vps_table(muic_attached_dev_t new_dev,
struct max77705_muic_data *muic_data)
{
int i;
struct i2c_client *i2c = muic_data->i2c;
u8 reg_data;
const struct max77705_muic_vps_data *tmp_vps;
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_USBC_STATUS2, &reg_data);
reg_data = reg_data & USBC_STATUS2_SYSMSG_MASK;
pr_info("%s Last sysmsg = 0x%02x\n", __func__, reg_data);
for (i = 0; i < (int)ARRAY_SIZE(muic_vps_table); i++) {
tmp_vps = &(muic_vps_table[i]);
if (tmp_vps->attached_dev != new_dev)
continue;
pr_info("%s (%d) vps table match found at i(%d), %s\n",
__func__, new_dev, i,
tmp_vps->vps_name);
return i;
}
pr_info("%s can't find (%d) on vps table\n",
__func__, new_dev);
return -ENODEV;
}
static bool muic_check_support_dev(struct max77705_muic_data *muic_data,
muic_attached_dev_t attached_dev)
{
bool ret = muic_data->muic_support_list[attached_dev];
if (debug_en_vps)
pr_info("%s [%c]\n", __func__, ret ? 'T':'F');
return ret;
}
static void max77705_switch_path(struct max77705_muic_data *muic_data,
u8 reg_val)
{
struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata;
usbc_cmd_data write_data;
pr_info("%s value(0x%x)\n", __func__, reg_val);
#if defined(CONFIG_CCIC_NOTIFIER)
if (muic_data->usbc_pdata->fac_water_enable) {
pr_info("%s fac_water_enable(%d), skip\n", __func__,
muic_data->usbc_pdata->fac_water_enable);
return;
}
#endif
init_usbc_cmd_data(&write_data);
write_data.opcode = COMMAND_CONTROL1_WRITE;
write_data.write_length = 1;
write_data.write_data[0] = reg_val;
write_data.read_length = 0;
max77705_usbc_opcode_write(usbc_pdata, &write_data);
}
static void com_to_open(struct max77705_muic_data *muic_data)
{
u8 reg_val;
pr_info("%s\n", __func__);
reg_val = COM_OPEN;
/* write command - switch */
max77705_switch_path(muic_data, reg_val);
}
static int com_to_usb_ap(struct max77705_muic_data *muic_data)
{
u8 reg_val;
int ret = 0;
pr_info("%s\n", __func__);
reg_val = COM_USB;
/* write command - switch */
max77705_switch_path(muic_data, reg_val);
return ret;
}
static int com_to_usb_cp(struct max77705_muic_data *muic_data)
{
u8 reg_val;
int ret = 0;
pr_info("%s\n", __func__);
reg_val = COM_USB_CP;
/* write command - switch */
max77705_switch_path(muic_data, reg_val);
return ret;
}
static void com_to_uart_ap(struct max77705_muic_data *muic_data)
{
u8 reg_val;
#if defined(CONFIG_MUIC_MAX77705_CCIC)
if ((muic_data->pdata->opmode == OPMODE_MUIC) && muic_data->pdata->rustproof_on)
#else
if (muic_data->pdata->rustproof_on)
#endif
reg_val = COM_OPEN;
else
reg_val = COM_UART;
pr_info("%s(%d)\n", __func__, reg_val);
/* write command - switch */
max77705_switch_path(muic_data, reg_val);
}
static void com_to_uart_cp(struct max77705_muic_data *muic_data)
{
u8 reg_val;
#if defined(CONFIG_MUIC_MAX77705_CCIC)
if ((muic_data->pdata->opmode == OPMODE_MUIC) && muic_data->pdata->rustproof_on)
#else
if (muic_data->pdata->rustproof_on)
#endif
reg_val = COM_OPEN;
else
reg_val = COM_UART_CP;
pr_info("%s(%d)\n", __func__, reg_val);
/* write command - switch */
max77705_switch_path(muic_data, reg_val);
}
static int write_vps_regs(struct max77705_muic_data *muic_data,
muic_attached_dev_t new_dev)
{
const struct max77705_muic_vps_data *tmp_vps;
int vps_index;
u8 prev_switch;
vps_index = muic_lookup_vps_table(muic_data->attached_dev, muic_data);
if (vps_index < 0) {
pr_info("%s: prev cable is none.\n", __func__);
prev_switch = COM_OPEN;
} else {
/* Prev cable information. */
tmp_vps = &(muic_vps_table[vps_index]);
prev_switch = tmp_vps->muic_switch;
}
if (prev_switch == muic_data->switch_val)
pr_info("%s Duplicated(0x%02x), just ignore\n",
__func__, muic_data->switch_val);
#if 0
else {
/* write command - switch */
max77705_switch_path(muic_data, muic_data->switch_val);
}
#endif
return 0;
}
/* muic uart path control function */
static int switch_to_ap_uart(struct max77705_muic_data *muic_data,
muic_attached_dev_t new_dev)
{
int ret = 0;
switch (new_dev) {
case ATTACHED_DEV_JIG_UART_OFF_MUIC:
case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC:
case ATTACHED_DEV_JIG_UART_ON_MUIC:
case ATTACHED_DEV_JIG_UART_ON_VB_MUIC:
com_to_uart_ap(muic_data);
break;
default:
pr_warn("%s current attached is (%d) not Jig UART Off\n",
__func__, muic_data->attached_dev);
break;
}
return ret;
}
static int switch_to_cp_uart(struct max77705_muic_data *muic_data,
muic_attached_dev_t new_dev)
{
int ret = 0;
switch (new_dev) {
case ATTACHED_DEV_JIG_UART_OFF_MUIC:
case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC:
case ATTACHED_DEV_JIG_UART_ON_MUIC:
case ATTACHED_DEV_JIG_UART_ON_VB_MUIC:
com_to_uart_cp(muic_data);
break;
default:
pr_warn("%s current attached is (%d) not Jig UART Off\n",
__func__, muic_data->attached_dev);
break;
}
return ret;
}
static void max77705_muic_enable_detecting_short(struct max77705_muic_data *muic_data)
{
#if !defined(CONFIG_SEC_FACTORY)
struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata;
usbc_cmd_data write_data;
pr_info("%s\n", __func__);
init_usbc_cmd_data(&write_data);
write_data.opcode = 0x56;
write_data.write_length = 1;
/*
* bit 0: Enable detecting vbus-cc short
* bit 1: Enable detecting sbu-gnd short
* bit 2: Enable detecting vbus-sbu short
*/
write_data.write_data[0] = 0x7;
write_data.read_length = 1;
max77705_usbc_opcode_write(usbc_pdata, &write_data);
#endif
}
static void max77705_muic_dp_reset(struct max77705_muic_data *muic_data)
{
struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata;
usbc_cmd_data update_data;
pr_info("%s\n", __func__);
init_usbc_cmd_data(&update_data);
update_data.opcode = COMMAND_BC_CTRL2_READ;
update_data.mask = BC_CTRL2_DPDNMan_MASK | BC_CTRL2_DPDrv_MASK;
update_data.val = 0x10;
max77705_usbc_opcode_update(usbc_pdata, &update_data);
}
static void max77705_muic_enable_chgdet(struct max77705_muic_data *muic_data)
{
struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata;
usbc_cmd_data update_data;
pr_info("%s\n", __func__);
init_usbc_cmd_data(&update_data);
update_data.opcode = COMMAND_BC_CTRL1_READ;
update_data.mask = BC_CTRL1_CHGDetEn_MASK | BC_CTRL1_CHGDetMan_MASK;
update_data.val = 0xff;
max77705_usbc_opcode_update(usbc_pdata, &update_data);
}
#if 0
static void max77705_muic_disable_chgdet(struct max77705_muic_data *muic_data)
{
struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata;
usbc_cmd_data update_data;
pr_info("%s\n", __func__);
init_usbc_cmd_data(&update_data);
update_data.opcode = COMMAND_BC_CTRL1_READ;
update_data.mask = BC_CTRL1_CHGDetEn_MASK;
update_data.val = 0x0;
max77705_usbc_opcode_update(usbc_pdata, &update_data);
}
#endif
static u8 max77705_muic_get_adc_value(struct max77705_muic_data *muic_data)
{
u8 status;
u8 adc = MAX77705_UIADC_ERROR;
int ret;
ret = max77705_muic_read_reg(muic_data->i2c,
MAX77705_USBC_REG_USBC_STATUS1, &status);
if (ret)
pr_err("%s fail to read muic reg(%d)\n",
__func__, ret);
else
adc = status & USBC_STATUS1_UIADC_MASK;
return adc;
}
static u8 max77705_muic_get_vbadc_value(struct max77705_muic_data *muic_data)
{
u8 status;
u8 vbadc = 0;
int ret;
ret = max77705_muic_read_reg(muic_data->i2c,
MAX77705_USBC_REG_USBC_STATUS1, &status);
if (ret)
pr_err("%s fail to read muic reg(%d)\n",
__func__, ret);
else
vbadc = (status & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT;
return vbadc;
}
static ssize_t max77705_muic_show_uart_sel(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
struct muic_platform_data *pdata = muic_data->pdata;
const char *mode = "UNKNOWN\n";
switch (pdata->uart_path) {
case MUIC_PATH_UART_AP:
mode = "AP\n";
break;
case MUIC_PATH_UART_CP:
mode = "CP\n";
break;
#if IS_ENABLED(CONFIG_MUIC_UART_SWITCH)
case MUIC_PATH_UART_CP2:
mode = "CP2\n";
break;
#endif /* CONFIG_MUIC_UART_SWITCH */
default:
break;
}
pr_info("%s %s", __func__, mode);
return sprintf(buf, mode);
}
static ssize_t max77705_muic_set_uart_sel(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
struct muic_platform_data *pdata = muic_data->pdata;
mutex_lock(&muic_data->muic_mutex);
if (!strncasecmp(buf, "AP", 2)) {
pdata->uart_path = MUIC_PATH_UART_AP;
switch_to_ap_uart(muic_data, muic_data->attached_dev);
#if IS_ENABLED(CONFIG_MUIC_UART_SWITCH)
} else if (!strncasecmp(buf, "CP2", 3)) {
pdata->uart_path = MUIC_PATH_UART_CP2;
muic_data->pdata->set_gpio_uart_sel(pdata->uart_path);
switch_to_cp_uart(muic_data, muic_data->attached_dev);
#endif /* CONFIG_MUIC_UART_SWITCH */
} else if (!strncasecmp(buf, "CP", 2)) {
pdata->uart_path = MUIC_PATH_UART_CP;
switch_to_cp_uart(muic_data, muic_data->attached_dev);
} else {
pr_warn("%s invalid value\n", __func__);
}
pr_info("%s uart_path(%d)\n", __func__,
pdata->uart_path);
mutex_unlock(&muic_data->muic_mutex);
return count;
}
static ssize_t max77705_muic_show_usb_sel(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
struct muic_platform_data *pdata = muic_data->pdata;
const char *mode = "UNKNOWN\n";
switch (pdata->usb_path) {
case MUIC_PATH_USB_AP:
mode = "PDA\n";
break;
case MUIC_PATH_USB_CP:
mode = "MODEM\n";
break;
default:
break;
}
pr_debug("%s %s", __func__, mode);
return sprintf(buf, mode);
}
static ssize_t max77705_muic_set_usb_sel(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
struct muic_platform_data *pdata = muic_data->pdata;
if (!strncasecmp(buf, "PDA", 3))
pdata->usb_path = MUIC_PATH_USB_AP;
else if (!strncasecmp(buf, "MODEM", 5))
pdata->usb_path = MUIC_PATH_USB_CP;
else
pr_warn("%s invalid value\n", __func__);
pr_info("%s usb_path(%d)\n", __func__,
pdata->usb_path);
return count;
}
static ssize_t max77705_muic_show_uart_en(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
struct muic_platform_data *pdata = muic_data->pdata;
if (!pdata->rustproof_on) {
pr_info("%s UART ENABLE\n", __func__);
return sprintf(buf, "1\n");
}
pr_info("%s UART DISABLE", __func__);
return sprintf(buf, "0\n");
}
static ssize_t max77705_muic_set_uart_en(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
struct muic_platform_data *pdata = muic_data->pdata;
if (!strncasecmp(buf, "1", 1))
pdata->rustproof_on = false;
else if (!strncasecmp(buf, "0", 1))
pdata->rustproof_on = true;
else
pr_warn("%s invalid value\n", __func__);
pr_info("%s uart_en(%d)\n", __func__,
!pdata->rustproof_on);
return count;
}
static ssize_t max77705_muic_show_adc(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
u8 adc;
adc = max77705_muic_get_adc_value(muic_data);
pr_info("%s adc(0x%02x)\n", __func__, adc);
if (adc == MAX77705_UIADC_ERROR) {
pr_err("%s fail to read adc value\n",
__func__);
return sprintf(buf, "UNKNOWN\n");
}
switch (adc) {
case MAX77705_UIADC_GND:
adc = 0;
break;
case MAX77705_UIADC_255K:
adc = 0x18;
break;
case MAX77705_UIADC_301K:
adc = 0x19;
break;
case MAX77705_UIADC_523K:
adc = 0x1c;
break;
case MAX77705_UIADC_619K:
adc = 0x1d;
break;
case MAX77705_UIADC_OPEN:
adc = 0x1f;
break;
default:
adc = 0xff;
}
return sprintf(buf, "adc: 0x%x\n", adc);
}
static ssize_t max77705_muic_show_usb_state(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
pr_debug("%s attached_dev(%d)\n", __func__,
muic_data->attached_dev);
switch (muic_data->attached_dev) {
case ATTACHED_DEV_USB_MUIC:
return sprintf(buf, "USB_STATE_CONFIGURED\n");
default:
break;
}
return sprintf(buf, "USB_STATE_NOTCONFIGURED\n");
}
static ssize_t max77705_muic_show_attached_dev(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
const struct max77705_muic_vps_data *tmp_vps;
int vps_index;
vps_index = muic_lookup_vps_table(muic_data->attached_dev, muic_data);
if (vps_index < 0)
return sprintf(buf, "No VPS\n");
tmp_vps = &(muic_vps_table[vps_index]);
return sprintf(buf, "%s\n", tmp_vps->vps_name);
}
static ssize_t max77705_muic_show_otg_test(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
int ret = -ENODEV;
if (muic_check_support_dev(muic_data, ATTACHED_DEV_OTG_MUIC)) {
if (muic_data->is_otg_test == true)
ret = 0;
else
ret = 1;
pr_info("%s ret:%d buf:%s\n", __func__, ret, buf);
}
return ret;
}
static ssize_t max77705_muic_set_otg_test(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
int ret = -ENODEV;
mutex_lock(&muic_data->muic_mutex);
if (muic_check_support_dev(muic_data, ATTACHED_DEV_OTG_MUIC)) {
pr_info("%s buf:%s\n", __func__, buf);
if (!strncmp(buf, "0", 1)) {
muic_data->is_otg_test = true;
ret = 0;
} else if (!strncmp(buf, "1", 1)) {
muic_data->is_otg_test = false;
ret = 1;
} else {
pr_warn("%s Wrong command\n", __func__);
mutex_unlock(&muic_data->muic_mutex);
return count;
}
pr_info("%s ret: %d\n", __func__, ret);
mutex_unlock(&muic_data->muic_mutex);
return count;
}
mutex_unlock(&muic_data->muic_mutex);
return ret;
}
static ssize_t max77705_muic_show_apo_factory(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
const char *mode;
/* true: Factory mode, false: not Factory mode */
if (muic_data->is_factory_start)
mode = "FACTORY_MODE";
else
mode = "NOT_FACTORY_MODE";
pr_info("%s apo factory=%s\n", __func__, mode);
return sprintf(buf, "%s\n", mode);
}
static ssize_t max77705_muic_set_apo_factory(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
#if defined(CONFIG_SEC_FACTORY)
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
#endif /* CONFIG_SEC_FACTORY */
const char *mode;
pr_info("%s buf:%s\n", __func__, buf);
/* "FACTORY_START": factory mode */
if (!strncmp(buf, "FACTORY_START", 13)) {
#if defined(CONFIG_SEC_FACTORY)
muic_data->is_factory_start = true;
#endif /* CONFIG_SEC_FACTORY */
mode = "FACTORY_MODE";
} else {
pr_warn("%s Wrong command\n", __func__);
return count;
}
pr_info("%s apo factory=%s\n", __func__, mode);
return count;
}
#if defined(CONFIG_HV_MUIC_MAX77705_AFC) || defined(CONFIG_SUPPORT_QC30)
static ssize_t max77705_muic_show_afc_disable(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
struct muic_platform_data *pdata = muic_data->pdata;
if (pdata->afc_disable) {
pr_info("%s AFC DISABLE\n", __func__);
return sprintf(buf, "1\n");
}
pr_info("%s AFC ENABLE", __func__);
return sprintf(buf, "0\n");
}
static ssize_t max77705_muic_set_afc_disable(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
struct muic_platform_data *pdata = muic_data->pdata;
int param_val, ret = 0;
bool curr_val = pdata->afc_disable;
union power_supply_propval psy_val;
if (!strncasecmp(buf, "1", 1)) {
/* Disable AFC */
ret = sec_set_param(CM_OFFSET + 1, '1');
pdata->afc_disable = true;
} else if (!strncasecmp(buf, "0", 1)) {
/* Enable AFC */
ret = sec_set_param(CM_OFFSET + 1, '0');
pdata->afc_disable = false;
} else {
pr_warn("%s invalid value\n", __func__);
}
param_val = pdata->afc_disable ? '1' : '0';
pr_info("%s: param_val:%d\n", __func__, param_val);
if (ret < 0) {
pr_info("%s:set_param failed - %02x:%02x(%d)\n", __func__,
param_val, curr_val, ret);
pdata->afc_disable = curr_val;
return -EIO;
} else {
pr_info("%s: afc_disable:%d (AFC %s)\n", __func__,
pdata->afc_disable, pdata->afc_disable ? "Disabled" : "Enabled");
if (pdata->afc_disabled_updated & 0x2)
pdata->afc_disabled_updated |= 0x1;
else
max77705_muic_check_afc_disabled(muic_data);
}
#if defined(CONFIG_SEC_FACTORY)
/* for factory self charging test (AFC-> NORMAL TA) */
if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC)
max77705_muic_afc_hv_set(muic_data, 5);
#endif
pr_info("%s afc_disable(%d)\n", __func__, pdata->afc_disable);
psy_val.intval = param_val;
psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_HV_DISABLE, psy_val);
return count;
}
#endif /* CONFIG_HV_MUIC_MAX77705_AFC */
static ssize_t max77705_muic_show_vbus_value(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
u8 vbadc;
vbadc = max77705_muic_get_vbadc_value(muic_data);
pr_info("%s vbadc(0x%02x)\n", __func__, vbadc);
switch (vbadc) {
case MAX77705_VBADC_3_8V_UNDER:
vbadc = 0;
break;
case MAX77705_VBADC_3_8V_TO_4_5V ... MAX77705_VBADC_6_5V_TO_7_5V:
vbadc = 5;
break;
case MAX77705_VBADC_7_5V_TO_8_5V ... MAX77705_VBADC_8_5V_TO_9_5V:
vbadc = 9;
break;
default:
vbadc += 3;
}
return sprintf(buf, "%d\n", vbadc);
}
#if defined(CONFIG_HICCUP_CHARGER)
static ssize_t hiccup_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "ENABLE\n");
}
static ssize_t hiccup_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct max77705_muic_data *muic_data = dev_get_drvdata(dev);
if (!strncasecmp(buf, "DISABLE", 7)) {
pr_info("%s\n", __func__);
pdic_manual_ccopen_request(0);
com_to_open(muic_data);
muic_data->is_hiccup_mode = MUIC_HICCUP_MODE_OFF;
} else
pr_warn("%s invalid com : %s\n", __func__, buf);
return count;
}
#endif /* CONFIG_HICCUP_CHARGER */
static DEVICE_ATTR(uart_sel, 0664, max77705_muic_show_uart_sel,
max77705_muic_set_uart_sel);
static DEVICE_ATTR(usb_sel, 0664, max77705_muic_show_usb_sel,
max77705_muic_set_usb_sel);
static DEVICE_ATTR(uart_en, 0660, max77705_muic_show_uart_en,
max77705_muic_set_uart_en);
static DEVICE_ATTR(adc, 0444, max77705_muic_show_adc, NULL);
static DEVICE_ATTR(usb_state, 0444, max77705_muic_show_usb_state, NULL);
static DEVICE_ATTR(attached_dev, 0444, max77705_muic_show_attached_dev, NULL);
static DEVICE_ATTR(otg_test, 0664,
max77705_muic_show_otg_test, max77705_muic_set_otg_test);
static DEVICE_ATTR(apo_factory, 0664,
max77705_muic_show_apo_factory, max77705_muic_set_apo_factory);
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
static DEVICE_ATTR(afc_disable, 0664,
max77705_muic_show_afc_disable, max77705_muic_set_afc_disable);
#endif /* CONFIG_HV_MUIC_MAX77705_AFC */
static DEVICE_ATTR(vbus_value, 0444, max77705_muic_show_vbus_value, NULL);
static DEVICE_ATTR(vbus_value_pd, 0444, max77705_muic_show_vbus_value, NULL);
#if defined(CONFIG_HICCUP_CHARGER)
static DEVICE_ATTR_RW(hiccup);
#endif /* CONFIG_HICCUP_CHARGER */
static struct attribute *max77705_muic_attributes[] = {
&dev_attr_uart_sel.attr,
&dev_attr_usb_sel.attr,
&dev_attr_uart_en.attr,
&dev_attr_adc.attr,
&dev_attr_usb_state.attr,
&dev_attr_attached_dev.attr,
&dev_attr_otg_test.attr,
&dev_attr_apo_factory.attr,
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
&dev_attr_afc_disable.attr,
#endif /* CONFIG_HV_MUIC_MAX77705_AFC */
&dev_attr_vbus_value.attr,
&dev_attr_vbus_value_pd.attr,
#if defined(CONFIG_HICCUP_CHARGER)
&dev_attr_hiccup.attr,
#endif /* CONFIG_HICCUP_CHARGER */
NULL
};
static const struct attribute_group max77705_muic_group = {
.attrs = max77705_muic_attributes,
};
void max77705_muic_read_register(struct i2c_client *i2c)
{
const enum max77705_usbc_reg regfile[] = {
MAX77705_USBC_REG_UIC_HW_REV,
MAX77705_USBC_REG_USBC_STATUS1,
MAX77705_USBC_REG_USBC_STATUS2,
MAX77705_USBC_REG_BC_STATUS,
MAX77705_USBC_REG_UIC_INT_M,
};
u8 val;
int i, ret;
pr_info("%s read register--------------\n", __func__);
for (i = 0; i < (int)ARRAY_SIZE(regfile); i++) {
ret = max77705_muic_read_reg(i2c, regfile[i], &val);
if (ret) {
pr_err("%s fail to read muic reg(0x%02x), ret=%d\n",
__func__, regfile[i], ret);
continue;
}
pr_info("%s reg(0x%02x)=[0x%02x]\n",
__func__, regfile[i], val);
}
pr_info("%s end register---------------\n", __func__);
}
static int max77705_muic_attach_uart_path(struct max77705_muic_data *muic_data,
muic_attached_dev_t new_dev)
{
struct muic_platform_data *pdata = muic_data->pdata;
int ret = 0;
pr_info("%s\n", __func__);
if (pdata->uart_path == MUIC_PATH_UART_AP)
ret = switch_to_ap_uart(muic_data, new_dev);
else if (pdata->uart_path == MUIC_PATH_UART_CP)
ret = switch_to_cp_uart(muic_data, new_dev);
#if IS_ENABLED(CONFIG_MUIC_UART_SWITCH)
else if (pdata->uart_path == MUIC_PATH_UART_CP2)
ret = switch_to_cp_uart(muic_data, new_dev);
#endif /* CONFIG_MUIC_UART_SWITCH */
else
pr_warn("%s invalid uart_path\n", __func__);
return ret;
}
static int max77705_muic_attach_usb_path(struct max77705_muic_data *muic_data,
muic_attached_dev_t new_dev)
{
struct muic_platform_data *pdata = muic_data->pdata;
int ret = 0;
pr_info("%s usb_path=%d\n", __func__, pdata->usb_path);
if (pdata->usb_path == MUIC_PATH_USB_AP)
ret = com_to_usb_ap(muic_data);
else if (pdata->usb_path == MUIC_PATH_USB_CP)
ret = com_to_usb_cp(muic_data);
else
pr_warn("%s invalid usb_path\n", __func__);
return ret;
}
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
void max77705_muic_disable_afc_protocol(struct max77705_muic_data *muic_data)
{
struct i2c_client *pmic_i2c = muic_data->usbc_pdata->max77705->i2c;
struct i2c_client *debug_i2c = muic_data->usbc_pdata->max77705->debug;
pr_info("%s\n", __func__);
/*
* MAXIM's request.
* This is workaround of D- high during AFC charging issue,
* set hidden register.
*/
max77705_write_reg(pmic_i2c, 0xFE, 0xC5); /* Unlock TKEY */
max77705_write_reg(debug_i2c, 0x0F, 0x04); /* Force FC clock always ON */
max77705_write_reg(debug_i2c, 0x0F, 0x00); /* Force FC clock always OFF */
max77705_write_reg(pmic_i2c, 0xFE, 0x00); /* Lock TKEY */
}
#endif
static int max77705_muic_handle_detach(struct max77705_muic_data *muic_data, int irq)
{
int ret = 0;
#if defined(CONFIG_MUIC_NOTIFIER)
bool noti = true;
muic_attached_dev_t attached_dev = muic_data->attached_dev;
#endif /* CONFIG_MUIC_NOTIFIER */
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
/* Do workaround if Vbusdet goes to low status */
if (muic_data->is_check_hv && irq == muic_data->irq_vbusdet &&
(muic_data->status3 & BC_STATUS_VBUSDET_MASK) == 0) {
muic_data->is_check_hv = false;
max77705_muic_disable_afc_protocol(muic_data);
}
#if defined(CONFIG_HICCUP_CHARGER)
muic_data->is_hiccup_mode = MUIC_HICCUP_MODE_OFF;
#endif
muic_data->hv_voltage = 0;
muic_data->afc_retry = 0;
muic_data->is_afc_reset = false;
muic_data->is_skip_bigdata = false;
#endif
if (muic_data->attached_dev == ATTACHED_DEV_NONE_MUIC) {
pr_info("%s Duplicated(%d), just ignore\n",
__func__, muic_data->attached_dev);
goto out_without_noti;
}
#if 0
/* Enable Charger Detection */
max77705_muic_enable_chgdet(muic_data);
#endif
muic_lookup_vps_table(muic_data->attached_dev, muic_data);
switch (muic_data->attached_dev) {
case ATTACHED_DEV_TA_MUIC:
case ATTACHED_DEV_UNOFFICIAL_TA_MUIC:
case ATTACHED_DEV_AFC_CHARGER_5V_MUIC:
case ATTACHED_DEV_AFC_CHARGER_9V_MUIC:
case ATTACHED_DEV_QC_CHARGER_5V_MUIC:
case ATTACHED_DEV_QC_CHARGER_9V_MUIC:
if ((muic_data->status3 & BC_STATUS_VBUSDET_MASK) > 0) {
/* W/A for chgtype 0 irq when CC pin is only detached */
pr_info("%s Vbus is high, keep the current state(%d)\n", __func__,
muic_data->attached_dev);
return 0;
}
break;
case ATTACHED_DEV_JIG_UART_OFF_MUIC:
case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC:
case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC:
case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC:
case ATTACHED_DEV_JIG_UART_ON_MUIC:
case ATTACHED_DEV_JIG_UART_ON_VB_MUIC:
case ATTACHED_DEV_JIG_USB_OFF_MUIC:
case ATTACHED_DEV_JIG_USB_ON_MUIC:
case ATTACHED_DEV_NONE_MUIC:
com_to_open(muic_data);
break;
case ATTACHED_DEV_USB_MUIC:
case ATTACHED_DEV_CDP_MUIC:
case ATTACHED_DEV_OTG_MUIC:
case ATTACHED_DEV_TIMEOUT_OPEN_MUIC:
if (muic_data->ccic_info_data.ccic_evt_attached == MUIC_CCIC_NOTI_DETACH)
com_to_open(muic_data);
break;
case ATTACHED_DEV_UNOFFICIAL_ID_MUIC:
goto out_without_noti;
default:
break;
}
#if defined(CONFIG_MUIC_NOTIFIER)
if (noti) {
muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC;
muic_notifier_detach_attached_dev(attached_dev);
}
#endif /* CONFIG_MUIC_NOTIFIER */
out_without_noti:
muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC;
return ret;
}
static int max77705_muic_logically_detach(struct max77705_muic_data *muic_data,
muic_attached_dev_t new_dev)
{
#if defined(CONFIG_MUIC_NOTIFIER)
bool noti = true;
#endif /* CONFIG_MUIC_NOTIFIER */
bool force_path_open = true;
int ret = 0;
switch (muic_data->attached_dev) {
case ATTACHED_DEV_OTG_MUIC:
break;
case ATTACHED_DEV_USB_MUIC:
case ATTACHED_DEV_CDP_MUIC:
case ATTACHED_DEV_TIMEOUT_OPEN_MUIC:
if (new_dev == ATTACHED_DEV_OTG_MUIC) {
pr_info("%s: data role changed, not detach\n", __func__);
force_path_open = false;
goto out;
}
break;
case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC:
case ATTACHED_DEV_JIG_USB_OFF_MUIC:
case ATTACHED_DEV_JIG_USB_ON_MUIC:
break;
case ATTACHED_DEV_JIG_UART_OFF_MUIC:
case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC:
case ATTACHED_DEV_JIG_UART_ON_MUIC:
case ATTACHED_DEV_JIG_UART_ON_VB_MUIC:
case ATTACHED_DEV_UNKNOWN_MUIC:
if (new_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC ||
new_dev == ATTACHED_DEV_JIG_UART_OFF_VB_MUIC ||
new_dev == ATTACHED_DEV_JIG_UART_ON_MUIC ||
new_dev == ATTACHED_DEV_JIG_UART_ON_VB_MUIC)
force_path_open = false;
break;
case ATTACHED_DEV_TA_MUIC:
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
case ATTACHED_DEV_AFC_CHARGER_9V_MUIC:
case ATTACHED_DEV_AFC_CHARGER_5V_MUIC:
case ATTACHED_DEV_QC_CHARGER_9V_MUIC:
case ATTACHED_DEV_QC_CHARGER_5V_MUIC:
#endif
#if defined(CONFIG_HICCUP_CHARGER)
if (new_dev == ATTACHED_DEV_HICCUP_MUIC) {
pr_info("%s hiccup charger, do not logically detach\n", __func__);
force_path_open = false;
goto out;
}
#endif
break;
case ATTACHED_DEV_UNOFFICIAL_TA_MUIC:
break;
#if defined(CONFIG_HICCUP_CHARGER)
case ATTACHED_DEV_HICCUP_MUIC:
break;
#endif /* CONFIG_HICCUP_CHARGER */
case ATTACHED_DEV_NONE_MUIC:
force_path_open = false;
goto out;
default:
pr_warn("%s try to attach without logically detach\n",
__func__);
goto out;
}
pr_info("%s attached(%d)!=new(%d), assume detach\n",
__func__, muic_data->attached_dev, new_dev);
#if defined(CONFIG_MUIC_NOTIFIER)
if (noti) {
muic_notifier_detach_attached_dev(muic_data->attached_dev);
muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC;
}
#endif /* CONFIG_MUIC_NOTIFIER */
out:
if (force_path_open)
com_to_open(muic_data);
return ret;
}
static int max77705_muic_handle_attach(struct max77705_muic_data *muic_data,
muic_attached_dev_t new_dev, int irq)
{
#if defined(CONFIG_MUIC_NOTIFIER)
bool notify_skip = false;
#endif /* CONFIG_MUIC_NOTIFIER */
#if defined(CONFIG_CCIC_MAX77705)
int fw_update_state = muic_data->usbc_pdata->max77705->fw_update_state;
#endif /* CONFIG_CCIC_MAX77705 */
int ret = 0;
pr_info("%s\n", __func__);
if (new_dev == muic_data->attached_dev) {
if (new_dev == ATTACHED_DEV_OTG_MUIC) {
/* W/A for setting usb path */
pr_info("%s:%s Duplicated(%d), Not ignore\n",
MUIC_DEV_NAME, __func__, muic_data->attached_dev);
goto handle_attach;
}
if (new_dev == ATTACHED_DEV_HICCUP_MUIC)
goto handle_attach;
pr_info("%s Duplicated(%d), just ignore\n",
__func__, muic_data->attached_dev);
return ret;
}
ret = max77705_muic_logically_detach(muic_data, new_dev);
if (ret)
pr_warn("%s fail to logically detach(%d)\n",
__func__, ret);
handle_attach:
switch (new_dev) {
case ATTACHED_DEV_OTG_MUIC:
ret = com_to_usb_ap(muic_data);
break;
case ATTACHED_DEV_JIG_UART_OFF_MUIC:
case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC:
ret = max77705_muic_attach_uart_path(muic_data, new_dev);
break;
case ATTACHED_DEV_JIG_UART_ON_MUIC:
case ATTACHED_DEV_JIG_UART_ON_VB_MUIC:
ret = max77705_muic_attach_uart_path(muic_data, new_dev);
break;
case ATTACHED_DEV_TA_MUIC:
case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC:
case ATTACHED_DEV_UNOFFICIAL_TA_MUIC:
ret = write_vps_regs(muic_data, new_dev);
break;
case ATTACHED_DEV_JIG_USB_ON_MUIC:
case ATTACHED_DEV_JIG_USB_OFF_MUIC:
case ATTACHED_DEV_USB_MUIC:
case ATTACHED_DEV_CDP_MUIC:
ret = max77705_muic_attach_usb_path(muic_data, new_dev);
break;
case ATTACHED_DEV_TIMEOUT_OPEN_MUIC:
pr_info("%s DCD_TIMEOUT system_state = 0x%x\n", __func__, system_state);
#if defined(CONFIG_CCIC_MAX77705)
if (fw_update_state == FW_UPDATE_END && system_state < SYSTEM_RUNNING) {
/* TA Reset, D+ gnd */
max77705_muic_dp_reset(muic_data);
max77705_muic_enable_chgdet(muic_data);
goto out;
}
#endif /* CONFIG_CCIC_MAX77705 */
ret = max77705_muic_attach_usb_path(muic_data, new_dev);
break;
#if defined(CONFIG_HICCUP_CHARGER)
case ATTACHED_DEV_HICCUP_MUIC:
ret = com_to_usb_cp(muic_data);
if (!(muic_data->status3 & BC_STATUS_VBUSDET_MASK))
notify_skip = true;
break;
#endif /* CONFIG_HICCUP_CHARGER */
default:
pr_warn("%s unsupported dev(%d)\n", __func__,
new_dev);
ret = -ENODEV;
goto out;
}
#if defined(CONFIG_MUIC_NOTIFIER)
if (notify_skip) {
pr_info("%s: noti\n", __func__);
} else {
muic_notifier_attach_attached_dev(new_dev);
muic_data->attached_dev = new_dev;
}
#endif /* CONFIG_MUIC_NOTIFIER */
muic_data->attached_dev = new_dev;
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
if (max77705_muic_check_is_enable_afc(muic_data, new_dev)) {
/* Maxim's request, wait 500ms for checking HVDCP */
pr_info("%s afc work after 500ms\n", __func__);
cancel_delayed_work_sync(&(muic_data->afc_work));
schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(500));
}
#endif /* CONFIG_HV_MUIC_MAX77705_AFC */
out:
return ret;
}
static bool muic_check_vps_adc
(const struct max77705_muic_vps_data *tmp_vps, u8 adc)
{
bool ret = false;
if (tmp_vps->adc == adc) {
ret = true;
goto out;
}
if (tmp_vps->adc == MAX77705_UIADC_DONTCARE)
ret = true;
out:
if (debug_en_vps) {
pr_info("%s vps(%s) adc(0x%02x) ret(%c)\n",
__func__, tmp_vps->vps_name,
adc, ret ? 'T' : 'F');
}
return ret;
}
static bool muic_check_vps_vbvolt(const struct max77705_muic_vps_data *tmp_vps,
u8 vbvolt)
{
bool ret = false;
if (tmp_vps->vbvolt == vbvolt) {
ret = true;
goto out;
}
if (tmp_vps->vbvolt == VB_DONTCARE)
ret = true;
out:
if (debug_en_vps) {
pr_debug("%s vps(%s) vbvolt(0x%02x) ret(%c)\n",
__func__, tmp_vps->vps_name,
vbvolt, ret ? 'T' : 'F');
}
return ret;
}
static bool muic_check_vps_chgtyp(const struct max77705_muic_vps_data *tmp_vps,
u8 chgtyp)
{
bool ret = false;
if (tmp_vps->chgtyp == chgtyp) {
ret = true;
goto out;
}
if (tmp_vps->chgtyp == CHGTYP_ANY) {
if (chgtyp > CHGTYP_NO_VOLTAGE) {
ret = true;
goto out;
}
}
if (tmp_vps->chgtyp == CHGTYP_DONTCARE)
ret = true;
out:
if (debug_en_vps) {
pr_info("%s vps(%s) chgtyp(0x%02x) ret(%c)\n",
__func__, tmp_vps->vps_name,
chgtyp, ret ? 'T' : 'F');
}
return ret;
}
#if defined(CONFIG_MUIC_MAX77705_CCIC)
static u8 max77705_muic_update_adc_with_rid(struct max77705_muic_data *muic_data,
u8 adc)
{
u8 new_adc = adc;
if (muic_data->pdata->opmode & OPMODE_CCIC) {
switch (muic_data->ccic_info_data.ccic_evt_rid) {
case RID_000K:
new_adc = MAX77705_UIADC_GND;
break;
case RID_255K:
new_adc = MAX77705_UIADC_255K;
break;
case RID_301K:
new_adc = MAX77705_UIADC_301K;
break;
case RID_523K:
new_adc = MAX77705_UIADC_523K;
break;
case RID_619K:
new_adc = MAX77705_UIADC_619K;
break;
default:
new_adc = MAX77705_UIADC_OPEN;
break;
}
if (muic_data->ccic_info_data.ccic_evt_rprd)
new_adc = MAX77705_UIADC_GND;
pr_info("%s: adc(0x%x->0x%x) rid(%d) rprd(%d)\n",
__func__, adc, new_adc,
muic_data->ccic_info_data.ccic_evt_rid,
muic_data->ccic_info_data.ccic_evt_rprd);
}
return new_adc;
}
#endif /* CONFIG_MUIC_MAX77705_CCIC */
static u8 max77705_resolve_chgtyp(struct max77705_muic_data *muic_data, u8 chgtyp,
u8 spchgtyp, u8 dcdtmo, int irq)
{
u8 ret = chgtyp;
#if defined(CONFIG_HICCUP_CHARGER)
/* Check hiccup mode */
if (muic_data->is_hiccup_mode > MUIC_HICCUP_MODE_OFF) {
pr_info("%s is_hiccup_mode(%d)\n", __func__, muic_data->is_hiccup_mode);
return CHGTYP_HICCUP_MODE;
}
#endif /* CONFIG_HICCUP_CHARGER */
#if defined(CONFIG_MUIC_MAX77705_CCIC)
/* Check chgtype and ccic attach don't exist */
if (irq == MUIC_IRQ_VBUS_WA && chgtyp == CHGTYP_NO_VOLTAGE && spchgtyp == CHGTYP_NO_VOLTAGE
&& muic_data->ccic_info_data.ccic_evt_attached != MUIC_CCIC_NOTI_ATTACH) {
ret = CHGTYP_TIMEOUT_OPEN;
goto out;
}
#endif /* CONFIG_MUIC_MAX77705_CCIC */
/* Check DCD timeout */
if (dcdtmo && chgtyp == CHGTYP_USB &&
(irq == muic_data->irq_chgtyp || irq == MUIC_IRQ_INIT_DETECT)) {
ret = CHGTYP_TIMEOUT_OPEN;
goto out;
}
/* Check Special chgtyp */
switch (spchgtyp) {
case PRCHGTYP_SAMSUNG_2A:
case PRCHGTYP_APPLE_500MA:
case PRCHGTYP_APPLE_1A:
case PRCHGTYP_APPLE_2A:
case PRCHGTYP_APPLE_12W:
if (chgtyp == CHGTYP_USB || chgtyp == CHGTYP_CDP) {
ret = CHGTYP_UNOFFICIAL_CHARGER;
goto out;
}
break;
default:
break;
}
out:
if (ret != chgtyp)
pr_info("%s chgtyp(0x%x) spchgtyp(0x%x) dcdtmo(0x%x) -> chgtyp(0x%x)",
__func__, chgtyp, spchgtyp, dcdtmo, ret);
return ret;
}
muic_attached_dev_t max77705_muic_check_new_dev(struct max77705_muic_data *muic_data,
int *intr, int irq)
{
const struct max77705_muic_vps_data *tmp_vps;
muic_attached_dev_t new_dev = ATTACHED_DEV_NONE_MUIC;
u8 adc = muic_data->status1 & USBC_STATUS1_UIADC_MASK;
u8 vbvolt = muic_data->status3 & BC_STATUS_VBUSDET_MASK;
u8 chgtyp = muic_data->status3 & BC_STATUS_CHGTYP_MASK;
u8 spchgtyp = (muic_data->status3 & BC_STATUS_PRCHGTYP_MASK) >> BC_STATUS_PRCHGTYP_SHIFT;
u8 dcdtmo = (muic_data->status3 & BC_STATUS_DCDTMO_MASK) >> BC_STATUS_DCDTMO_SHIFT;
unsigned long i;
chgtyp = max77705_resolve_chgtyp(muic_data, chgtyp, spchgtyp, dcdtmo, irq);
#if defined(CONFIG_MUIC_MAX77705_CCIC)
adc = max77705_muic_update_adc_with_rid(muic_data, adc);
/* Do not check vbus if CCIC RID is 523K */
if ((muic_data->pdata->opmode & OPMODE_CCIC) && (adc == MAX77705_UIADC_523K))
vbvolt = 0;
#endif /* CONFIG_MUIC_MAX77705_CCIC */
for (i = 0; i < (int)ARRAY_SIZE(muic_vps_table); i++) {
tmp_vps = &(muic_vps_table[i]);
if (!(muic_check_vps_adc(tmp_vps, adc)))
continue;
if (!(muic_check_vps_vbvolt(tmp_vps, vbvolt)))
continue;
if (!(muic_check_vps_chgtyp(tmp_vps, chgtyp)))
continue;
#if defined(CONFIG_SEC_FACTORY)
if (muic_data->pdata->opmode & OPMODE_CCIC) {
if (tmp_vps->attached_dev == ATTACHED_DEV_JIG_USB_OFF_MUIC &&
!vbvolt) {
pr_info("%s 255k w/o vbus\n", __func__);
continue;
}
}
#endif
pr_info("%s vps table match found at i(%lu), %s\n",
__func__, i, tmp_vps->vps_name);
new_dev = tmp_vps->attached_dev;
muic_data->switch_val = tmp_vps->muic_switch;
*intr = MUIC_INTR_ATTACH;
break;
}
pr_info("%s %d->%d switch_val[0x%02x]\n", __func__,
muic_data->attached_dev, new_dev, muic_data->switch_val);
return new_dev;
}
static void max77705_muic_detect_dev(struct max77705_muic_data *muic_data,
int irq)
{
struct i2c_client *i2c = muic_data->i2c;
#if defined(CONFIG_USB_HW_PARAM)
struct otg_notify *o_notify = get_otg_notify();
#endif
muic_attached_dev_t new_dev = ATTACHED_DEV_NONE_MUIC;
int intr = MUIC_INTR_DETACH;
u8 status[5];
u8 adc, vbvolt, chgtyp, spchgtyp, sysmsg, vbadc, dcdtmo, ccstat, fakvb;
int ret;
#ifdef CONFIG_USB_NOTIFY_PROC_LOG
int event;
#endif
ret = max77705_bulk_read(i2c,
MAX77705_USBC_REG_USBC_STATUS1, 5, status);
if (ret) {
pr_err("%s fail to read muic reg(%d)\n",
__func__, ret);
return;
}
pr_info("%s USBC1:0x%02x, USBC2:0x%02x, BC:0x%02x\n",
__func__, status[0], status[1], status[2]);
/* attached status */
muic_data->status1 = status[0];
muic_data->status2 = status[1];
muic_data->status3 = status[2];
adc = status[0] & USBC_STATUS1_UIADC_MASK;
sysmsg = status[1] & USBC_STATUS2_SYSMSG_MASK;
vbvolt = (status[2] & BC_STATUS_VBUSDET_MASK) >> BC_STATUS_VBUSDET_SHIFT;
chgtyp = status[2] & BC_STATUS_CHGTYP_MASK;
spchgtyp = (status[2] & BC_STATUS_PRCHGTYP_MASK) >> BC_STATUS_PRCHGTYP_SHIFT;
vbadc = (status[0] & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT;
dcdtmo = (status[2] & BC_STATUS_DCDTMO_MASK) >> BC_STATUS_DCDTMO_SHIFT;
ccstat = (status[4] & BIT_CCStat) >> FFS(BIT_CCStat);
fakvb = (status[0] & USBC_STATUS1_FAKVB_MASK) >> USBC_STATUS1_FAKVB_SHIFT;
pr_info("%s adc:0x%x vbvolt:0x%x chgtyp:0x%x spchgtyp:0x%x sysmsg:0x%x vbadc:0x%x dcdtmo:0x%x fakvb:0x%x\n",
__func__, adc, vbvolt, chgtyp, spchgtyp, sysmsg, vbadc, dcdtmo, fakvb);
if (irq == muic_data->irq_vbadc) {
if (vbadc == MAX77705_VBADC_3_8V_TO_4_5V &&
ccstat == cc_No_Connection) {
/* W/A of CC is detached but Vbus is valid(3.8~4.5V) */
vbvolt = 0;
muic_data->status3 = muic_data->status3 & ~(BC_STATUS_VBUSDET_MASK);
pr_info("%s vbadc(0x%x), ccstat(0x%x), set vbvolt to 0 => BC(0x%x)\n",
__func__, vbadc, ccstat, muic_data->status3);
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
} else if (vbadc > MAX77705_VBADC_3_8V_TO_4_5V &&
vbadc <= MAX77705_VBADC_6_5V_TO_7_5V &&
muic_data->is_afc_reset) {
muic_data->is_afc_reset = false;
pr_info("%s afc reset is done\n", __func__);
switch (muic_data->attached_dev) {
case ATTACHED_DEV_AFC_CHARGER_5V_MUIC:
case ATTACHED_DEV_AFC_CHARGER_9V_MUIC:
muic_data->attached_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC;
#if defined(CONFIG_MUIC_NOTIFIER)
muic_notifier_attach_attached_dev(muic_data->attached_dev);
#endif /* CONFIG_MUIC_NOTIFIER */
break;
case ATTACHED_DEV_QC_CHARGER_5V_MUIC:
case ATTACHED_DEV_QC_CHARGER_9V_MUIC:
muic_data->attached_dev = ATTACHED_DEV_QC_CHARGER_5V_MUIC;
#if defined(CONFIG_MUIC_NOTIFIER)
muic_notifier_attach_attached_dev(muic_data->attached_dev);
#endif /* CONFIG_MUIC_NOTIFIER */
break;
default:
break;
}
return;
#endif /* CONFIG_HV_MUIC_MAX77705_AFC */
} else {
pr_info("%s vbadc irq(%d), return\n",
__func__, muic_data->irq_vbadc);
return;
}
}
if (irq == muic_data->irq_fakvb) {
pr_info("%s fake vbus status : %s\n", __func__, fakvb? "enable":"disable");
if (fakvb) {
#ifdef CONFIG_USB_NOTIFY_PROC_LOG
event = NOTIFY_EXTRA_USBKILLER;
store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL);
#endif
#if defined(CONFIG_USB_HW_PARAM)
if (o_notify)
inc_hw_param(o_notify, USB_CCIC_USB_KILLER_COUNT);
#endif
}
}
#if !defined(CONFIG_SEC_FACTORY)
/* W/A of defect cable(Vbus is valid and CC is invalid), set or cancel vbus_wa_work */
if (irq == muic_data->irq_vbusdet || irq == MUIC_IRQ_INIT_DETECT) {
wake_unlock(&muic_data->muic_wake_lock);
cancel_delayed_work(&(muic_data->vbus_wa_work));
if (vbvolt > 0) {
wake_lock_timeout(&muic_data->muic_wake_lock, 2100);
schedule_delayed_work(&(muic_data->vbus_wa_work), msecs_to_jiffies(2000));
}
} else if (irq == muic_data->irq_chgtyp && chgtyp > 0) {
wake_unlock(&muic_data->muic_wake_lock);
cancel_delayed_work(&(muic_data->vbus_wa_work));
}
#endif
#if defined(CONFIG_MUIC_MAX77705_CCIC)
if (!lpcharge && !muic_data->is_factory_start) {
if ((irq == MUIC_IRQ_CCIC_HANDLER) &&
(muic_data->ccic_evt_id == CCIC_NOTIFY_ID_WATER)) {
/* Force path open once at water state */
if (muic_data->afc_water_disable)
com_to_open(muic_data);
}
#if defined(CONFIG_HICCUP_CHARGER)
if (muic_data->afc_water_disable && !muic_data->is_hiccup_mode) {
if (vbvolt > 0) {
pr_info("%s water hiccup mode, Aux USB path\n", __func__);
com_to_usb_cp(muic_data);
} else {
/* Clear muic deive type and hiccup at water state (booting with water) */
if (muic_data->attached_dev != ATTACHED_DEV_NONE_MUIC) {
pr_info("%s initialize hiccup state and device type(%d) at hiccup booting\n",
__func__, muic_data->attached_dev);
muic_notifier_detach_attached_dev(muic_data->attached_dev);
muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC;
com_to_open(muic_data);
}
}
max77705_muic_handle_vbus(muic_data);
return;
}
#endif /* CONFIG_HICCUP_CHARGER */
}
#endif /* CONFIG_MUIC_MAX77705_CCIC */
new_dev = max77705_muic_check_new_dev(muic_data, &intr, irq);
if (intr == MUIC_INTR_ATTACH) {
pr_info("%s ATTACHED\n", __func__);
ret = max77705_muic_handle_attach(muic_data, new_dev, irq);
if (ret)
pr_err("%s cannot handle attach(%d)\n", __func__, ret);
} else {
pr_info("%s DETACHED\n", __func__);
if (vbvolt == 0 && chgtyp == CHGTYP_DEDICATED_CHARGER)
pr_info("%s catch the Fake Vbus type\n", __func__);
ret = max77705_muic_handle_detach(muic_data, irq);
if (ret)
pr_err("%s cannot handle detach(%d)\n", __func__, ret);
}
max77705_muic_handle_vbus(muic_data);
}
static irqreturn_t max77705_muic_irq(int irq, void *data)
{
struct max77705_muic_data *muic_data = data;
struct irq_desc *desc = irq_to_desc(irq);
pr_info("%s irq:%d (%s)\n", __func__, irq, desc->action->name);
if (!muic_data) {
pr_err("%s irq data is null\n", desc->action->name);
goto out;
}
mutex_lock(&muic_data->muic_mutex);
if (muic_data->is_muic_ready == true)
max77705_muic_detect_dev(muic_data, irq);
else
pr_info("%s MUIC is not ready, just return\n", __func__);
mutex_unlock(&muic_data->muic_mutex);
out:
return IRQ_HANDLED;
}
static void max77705_muic_vbus_wa_work(struct work_struct *work)
{
struct max77705_muic_data *muic_data =
container_of(work, struct max77705_muic_data, vbus_wa_work.work);
u8 vbvolt = (muic_data->status3 & BC_STATUS_VBUSDET_MASK) >> BC_STATUS_VBUSDET_SHIFT;
int ccic_attach = muic_data->ccic_info_data.ccic_evt_attached;
pr_info("%s vbvolt(%d) ccic_attach(%d)\n", __func__, vbvolt, ccic_attach);
mutex_lock(&muic_data->muic_mutex);
if (muic_data->is_muic_ready == true) {
if (vbvolt > 0 && ccic_attach != MUIC_CCIC_NOTI_ATTACH)
max77705_muic_detect_dev(muic_data, MUIC_IRQ_VBUS_WA);
} else {
pr_info("%s MUIC is not ready, just return\n", __func__);
}
mutex_unlock(&muic_data->muic_mutex);
}
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
static void max77705_muic_afc_work(struct work_struct *work)
{
struct max77705_muic_data *muic_data =
container_of(work, struct max77705_muic_data, afc_work.work);
pr_info("%s\n", __func__);
if (max77705_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) {
muic_data->pdata->afc_disabled_updated |= 0x2;
if (!muic_data->pdata->afc_disable) {
muic_data->is_check_hv = true;
muic_data->hv_voltage = 9;
max77705_muic_afc_hv_set(muic_data, 9);
} else {
muic_data->is_check_hv = true;
muic_data->hv_voltage = 5;
max77705_muic_afc_hv_set(muic_data, 5);
}
}
}
static int max77705_muic_hv_charger_disable(bool en)
{
struct max77705_muic_data *muic_data = g_muic_data;
muic_data->is_charger_mode = en;
schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(0));
return 0;
}
static int max77705_muic_afc_set_voltage(int voltage)
{
struct max77705_muic_data *muic_data = g_muic_data;
int now_voltage = 0;
switch (voltage) {
case 5:
case 9:
break;
default:
pr_err("%s: invalid value %d, return\n", __func__, voltage);
return -EINVAL;
}
switch (muic_data->attached_dev) {
case ATTACHED_DEV_AFC_CHARGER_5V_MUIC:
case ATTACHED_DEV_QC_CHARGER_5V_MUIC:
now_voltage = 5;
break;
case ATTACHED_DEV_AFC_CHARGER_9V_MUIC:
case ATTACHED_DEV_QC_CHARGER_9V_MUIC:
now_voltage = 9;
break;
default:
break;
}
if (voltage == now_voltage) {
pr_err("%s: same with current voltage, return\n", __func__);
return -EINVAL;
}
muic_data->hv_voltage = voltage;
switch (muic_data->attached_dev) {
case ATTACHED_DEV_AFC_CHARGER_5V_MUIC:
case ATTACHED_DEV_AFC_CHARGER_9V_MUIC:
max77705_muic_afc_hv_set(muic_data, voltage);
break;
case ATTACHED_DEV_QC_CHARGER_5V_MUIC:
case ATTACHED_DEV_QC_CHARGER_9V_MUIC:
max77705_muic_qc_hv_set(muic_data, voltage);
break;
default:
pr_err("%s: not a HV Charger %d, return\n", __func__, muic_data->attached_dev);
return -EINVAL;
}
return 0;
}
static int max77705_muic_hv_charger_init(void)
{
struct max77705_muic_data *muic_data = g_muic_data;
if (muic_data->is_charger_ready) {
pr_info("%s: charger is already ready(%d), return\n",
__func__, muic_data->is_charger_ready);
return -EINVAL;
}
muic_data->is_charger_ready = true;
if (max77705_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) {
pr_info("%s afc work start\n", __func__);
cancel_delayed_work_sync(&(muic_data->afc_work));
schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(0));
}
return 0;
}
static void max77705_muic_detect_dev_hv_work(struct work_struct *work)
{
struct max77705_muic_data *muic_data = container_of(work,
struct max77705_muic_data, afc_handle_work);
unsigned char opcode = muic_data->afc_op_dataout[0];
mutex_lock(&muic_data->muic_mutex);
if (!max77705_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) {
switch (muic_data->attached_dev) {
case ATTACHED_DEV_AFC_CHARGER_5V_MUIC:
case ATTACHED_DEV_AFC_CHARGER_9V_MUIC:
case ATTACHED_DEV_QC_CHARGER_5V_MUIC:
case ATTACHED_DEV_QC_CHARGER_9V_MUIC:
pr_info("%s high voltage value is changed\n", __func__);
break;
default:
pr_info("%s status is changed, return\n", __func__);
goto out;
}
}
if (opcode == COMMAND_AFC_RESULT_READ)
max77705_muic_handle_detect_dev_afc(muic_data, muic_data->afc_op_dataout);
else if (opcode == COMMAND_QC_2_0_SET)
max77705_muic_handle_detect_dev_qc(muic_data, muic_data->afc_op_dataout);
else
pr_info("%s undefined opcode(%d)\n", __func__, opcode);
out:
mutex_unlock(&muic_data->muic_mutex);
}
void max77705_muic_handle_detect_dev_hv(struct max77705_muic_data *muic_data, unsigned char *data)
{
int i;
for (i = 0; i < AFC_OP_OUT_LEN; i++)
muic_data->afc_op_dataout[i] = data[i];
schedule_work(&(muic_data->afc_handle_work));
}
#endif /* CONFIG_HV_MUIC_MAX77705_AFC */
#if defined(CONFIG_HICCUP_CHARGER)
static int max77705_muic_set_hiccup_mode(int on_off)
{
struct max77705_muic_data *muic_data = g_muic_data;
pr_info("%s (%d)\n", __func__, on_off);
switch (on_off) {
case MUIC_HICCUP_MODE_OFF:
case MUIC_HICCUP_MODE_ON:
case MUIC_HICCUP_MODE_NOTY:
if (muic_data->is_hiccup_mode != on_off) {
muic_data->is_hiccup_mode = on_off;
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
if (muic_data->is_check_hv)
max77705_muic_clear_hv_control(muic_data);
#endif /* CONFIG_HV_MUIC_MAX77705_AFC */
#if defined(CONFIG_MUIC_MAX77705_CCIC)
schedule_work(&(muic_data->ccic_info_data_work));
#endif /* CONFIG_MUIC_MAX77705_CCIC */
}
break;
default:
pr_err("%s undefined value(%d), return\n", __func__, on_off);
return -EINVAL;
}
return 0;
}
#endif /* CONFIG_HICCUP_CHARGER */
static void max77705_muic_print_reg_log(struct work_struct *work)
{
struct max77705_muic_data *muic_data =
container_of(work, struct max77705_muic_data, debug_work.work);
struct i2c_client *i2c = muic_data->i2c;
struct i2c_client *pmic_i2c = muic_data->usbc_pdata->i2c;
u8 status[12] = {0, };
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_USBC_STATUS1, &status[0]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_USBC_STATUS2, &status[1]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_BC_STATUS, &status[2]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_CC_STATUS0, &status[3]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_CC_STATUS1, &status[4]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_PD_STATUS0, &status[5]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_PD_STATUS1, &status[6]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_UIC_INT_M, &status[7]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_CC_INT_M, &status[8]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_PD_INT_M, &status[9]);
max77705_muic_read_reg(i2c, MAX77705_USBC_REG_VDM_INT_M, &status[10]);
max77705_muic_read_reg(pmic_i2c, MAX77705_PMIC_REG_INTSRC_MASK, &status[11]);
pr_info("%s USBC1:0x%02x, USBC2:0x%02x, BC:0x%02x, CC0:0x%x, CC1:0x%x, PD0:0x%x, PD1:0x%x attached_dev:%d\n",
__func__, status[0], status[1], status[2], status[3], status[4], status[5], status[6],
muic_data->attached_dev);
pr_info("%s UIC_INT_M:0x%x, CC_INT_M:0x%x, PD_INT_M:0x%x, VDM_INT_M:0x%x, PMIC_MASK:0x%x, WDT:%d, POR:%d\n",
__func__, status[7], status[8], status[9], status[10], status[11],
muic_data->usbc_pdata->watchdog_count, muic_data->usbc_pdata->por_count);
schedule_delayed_work(&(muic_data->debug_work),
msecs_to_jiffies(60000));
}
#if defined(CONFIG_MUIC_MAX77705_CCIC)
static void max77705_muic_handle_ccic_event(struct work_struct *work)
{
struct max77705_muic_data *muic_data = container_of(work,
struct max77705_muic_data, ccic_info_data_work);
pr_info("%s\n", __func__);
mutex_lock(&muic_data->muic_mutex);
if (muic_data->is_muic_ready == true)
max77705_muic_detect_dev(muic_data, MUIC_IRQ_CCIC_HANDLER);
else
pr_info("%s MUIC is not ready, just return\n", __func__);
mutex_unlock(&muic_data->muic_mutex);
}
#endif /* CONFIG_MUIC_MAX77705_CCIC */
#define REQUEST_IRQ(_irq, _dev_id, _name) \
do { \
ret = request_threaded_irq(_irq, NULL, max77705_muic_irq, \
IRQF_NO_SUSPEND, _name, _dev_id); \
if (ret < 0) { \
pr_err("%s Failed to request IRQ #%d: %d\n", \
__func__, _irq, ret); \
_irq = 0; \
} \
} while (0)
static int max77705_muic_irq_init(struct max77705_muic_data *muic_data)
{
int ret = 0;
pr_info("%s\n", __func__);
if (muic_data->mfd_pdata && (muic_data->mfd_pdata->irq_base > 0)) {
int irq_base = muic_data->mfd_pdata->irq_base;
/* request MUIC IRQ */
muic_data->irq_uiadc = irq_base + MAX77705_USBC_IRQ_UIDADC_INT;
REQUEST_IRQ(muic_data->irq_uiadc, muic_data, "muic-uiadc");
muic_data->irq_chgtyp = irq_base + MAX77705_USBC_IRQ_CHGT_INT;
REQUEST_IRQ(muic_data->irq_chgtyp, muic_data, "muic-chgtyp");
muic_data->irq_fakvb = irq_base + MAX77705_USBC_IRQ_FAKVB_INT;
REQUEST_IRQ(muic_data->irq_fakvb, muic_data, "muic-fakvb");
muic_data->irq_dcdtmo = irq_base + MAX77705_USBC_IRQ_DCD_INT;
REQUEST_IRQ(muic_data->irq_dcdtmo, muic_data, "muic-dcdtmo");
muic_data->irq_vbadc = irq_base + MAX77705_USBC_IRQ_VBADC_INT;
REQUEST_IRQ(muic_data->irq_vbadc, muic_data, "muic-vbadc");
muic_data->irq_vbusdet = irq_base + MAX77705_USBC_IRQ_VBUS_INT;
REQUEST_IRQ(muic_data->irq_vbusdet, muic_data, "muic-vbusdet");
}
pr_info("%s uiadc(%d), chgtyp(%d), fakvb(%d), dcdtmo(%d), vbadc(%d), vbusdet(%d)\n",
__func__,
muic_data->irq_uiadc, muic_data->irq_chgtyp,
muic_data->irq_fakvb, muic_data->irq_dcdtmo,
muic_data->irq_vbadc, muic_data->irq_vbusdet);
return ret;
}
#define FREE_IRQ(_irq, _dev_id, _name) \
do { \
if (_irq) { \
free_irq(_irq, _dev_id); \
pr_info("%s IRQ(%d):%s free done\n", \
__func__, _irq, _name); \
} \
} while (0)
static void max77705_muic_free_irqs(struct max77705_muic_data *muic_data)
{
pr_info("%s\n", __func__);
disable_irq(muic_data->irq_uiadc);
disable_irq(muic_data->irq_chgtyp);
disable_irq(muic_data->irq_fakvb);
disable_irq(muic_data->irq_dcdtmo);
disable_irq(muic_data->irq_vbadc);
disable_irq(muic_data->irq_vbusdet);
/* free MUIC IRQ */
FREE_IRQ(muic_data->irq_uiadc, muic_data, "muic-uiadc");
FREE_IRQ(muic_data->irq_chgtyp, muic_data, "muic-chgtyp");
FREE_IRQ(muic_data->irq_fakvb, muic_data, "muic-fakvb");
FREE_IRQ(muic_data->irq_dcdtmo, muic_data, "muic-dcdtmo");
FREE_IRQ(muic_data->irq_vbadc, muic_data, "muic-vbadc");
FREE_IRQ(muic_data->irq_vbusdet, muic_data, "muic-vbusdet");
}
static void max77705_muic_init_detect(struct max77705_muic_data *muic_data)
{
pr_info("%s\n", __func__);
mutex_lock(&muic_data->muic_mutex);
muic_data->is_muic_ready = true;
max77705_muic_detect_dev(muic_data, MUIC_IRQ_INIT_DETECT);
max77705_muic_enable_detecting_short(muic_data);
mutex_unlock(&muic_data->muic_mutex);
}
#if IS_ENABLED(CONFIG_MUIC_UART_SWITCH)
int max77705_muic_set_gpio_uart_sel(int uart_sel)
{
int gpio = g_muic_data->pdata->gpio_uart_sel;
int ret;
pr_info("%s %d\n", __func__, uart_sel);
ret = gpio_request(gpio, "GPIO_UART_CTRL");
if (ret) {
pr_err("failed to gpio_request GPIO_UART_CTRL\n");
return ret;
}
if (!gpio_is_valid(gpio))
return -ENXIO;
if (uart_sel == MUIC_PATH_UART_CP2)
gpio_direction_output(gpio, 1);
else
gpio_direction_output(gpio, 0);
gpio_free(gpio);
return 0;
}
#endif /* CONFIG_MUIC_UART_SWITCH */
#if defined(CONFIG_OF)
static int of_max77705_muic_dt(struct max77705_muic_data *muic_data)
{
struct device_node *np_muic;
const char *prop_support_list;
int i, j, prop_num;
int ret = 0;
np_muic = of_find_node_by_path("/muic");
if (np_muic == NULL)
return -EINVAL;
prop_num = of_property_count_strings(np_muic, "muic,support-list");
if (prop_num < 0) {
pr_warn("%s Cannot parse 'muic support list dt node'[%d]\n",
__func__, prop_num);
ret = prop_num;
goto err;
}
/* for debug */
for (i = 0; i < prop_num; i++) {
ret = of_property_read_string_index(np_muic,
"muic,support-list", i, &prop_support_list);
if (ret) {
pr_err("%s Cannot find string at [%d], ret[%d]\n",
__func__, i, ret);
goto err;
}
pr_debug("%s prop_support_list[%d] is %s\n", __func__,
i, prop_support_list);
for (j = 0; j < (int)ARRAY_SIZE(muic_vps_table); j++) {
if (!strcmp(muic_vps_table[j].vps_name, prop_support_list)) {
muic_data->muic_support_list[(muic_vps_table[j].attached_dev)] = true;
break;
}
}
}
#if IS_ENABLED(CONFIG_MUIC_UART_SWITCH)
muic_data->pdata->gpio_uart_sel =
of_get_named_gpio(np_muic, "uart_ctrl", 0);
pr_info("%s uart_ctrl[%d]\n", __func__,
muic_data->pdata->gpio_uart_sel);
#endif
/* for debug */
for (i = 0; i < ATTACHED_DEV_NUM; i++)
pr_debug("%s pmuic_support_list[%d] = %c\n", __func__,
i, (muic_data->muic_support_list[i] ? 'T' : 'F'));
err:
of_node_put(np_muic);
return ret;
}
#endif /* CONFIG_OF */
static void max77705_muic_clear_interrupt(struct max77705_muic_data *muic_data)
{
struct i2c_client *i2c = muic_data->i2c;
u8 interrupt;
int ret;
pr_info("%s\n", __func__);
ret = max77705_muic_read_reg(i2c,
MAX77705_USBC_REG_UIC_INT, &interrupt);
if (ret)
pr_err("%s fail to read muic INT1 reg(%d)\n",
__func__, ret);
pr_info("%s CLEAR!! UIC_INT:0x%02x\n",
__func__, interrupt);
}
static int max77705_muic_init_regs(struct max77705_muic_data *muic_data)
{
int ret;
pr_info("%s\n", __func__);
max77705_muic_clear_interrupt(muic_data);
ret = max77705_muic_irq_init(muic_data);
if (ret < 0) {
pr_err("%s Failed to initialize MUIC irq:%d\n",
__func__, ret);
max77705_muic_free_irqs(muic_data);
}
return ret;
}
#if defined(CONFIG_USB_EXTERNAL_NOTIFY)
static int muic_handle_usb_notification(struct notifier_block *nb,
unsigned long action, void *data)
{
struct max77705_muic_data *pmuic =
container_of(nb, struct max77705_muic_data, usb_nb);
switch (action) {
/* Abnormal device */
case EXTERNAL_NOTIFY_3S_NODEVICE:
pr_info("%s: 3S_NODEVICE(USB HOST Connection timeout)\n",
__func__);
if (pmuic->attached_dev == ATTACHED_DEV_HMT_MUIC)
muic_send_dock_intent(MUIC_DOCK_ABNORMAL);
break;
/* Gamepad device connected */
case EXTERNAL_NOTIFY_DEVICE_CONNECT:
pr_info("%s: DEVICE_CONNECT(Gamepad)\n", __func__);
break;
default:
break;
}
return NOTIFY_DONE;
}
static void muic_register_usb_notifier(struct max77705_muic_data *pmuic)
{
int ret = 0;
pr_info("%s\n", __func__);
ret = usb_external_notify_register(&pmuic->usb_nb,
muic_handle_usb_notification, EXTERNAL_NOTIFY_DEV_MUIC);
if (ret < 0) {
pr_info("%s: USB Noti. is not ready.\n", __func__);
return;
}
pr_info("%s: done.\n", __func__);
}
static void muic_unregister_usb_notifier(struct max77705_muic_data *pmuic)
{
int ret = 0;
pr_info("%s\n", __func__);
ret = usb_external_notify_unregister(&pmuic->usb_nb);
if (ret < 0) {
pr_info("%s: USB Noti. unregister error.\n", __func__);
return;
}
pr_info("%s: done.\n", __func__);
}
#else
static void muic_register_usb_notifier(struct max77705_muic_data *pmuic) {}
static void muic_unregister_usb_notifier(struct max77705_muic_data *pmuic) {}
#endif
int max77705_muic_probe(struct max77705_usbc_platform_data *usbc_data)
{
struct max77705_platform_data *mfd_pdata = usbc_data->max77705_data;
struct max77705_muic_data *muic_data;
int ret = 0;
pr_info("%s\n", __func__);
muic_data = devm_kzalloc(usbc_data->dev, sizeof(struct max77705_muic_data), GFP_KERNEL);
if (!muic_data) {
ret = -ENOMEM;
goto err_return;
}
if (!mfd_pdata) {
pr_err("%s: failed to get mfd platform data\n", __func__);
ret = -ENOMEM;
goto err_return;
}
muic_data->pdata = &muic_pdata;
#if defined(CONFIG_OF)
ret = of_max77705_muic_dt(muic_data);
if (ret < 0)
pr_err("%s not found muic dt! ret[%d]\n", __func__, ret);
#endif /* CONFIG_OF */
mutex_init(&muic_data->muic_mutex);
wake_lock_init(&muic_data->muic_wake_lock, WAKE_LOCK_SUSPEND, "muic-irq");
muic_data->i2c = usbc_data->muic;
muic_data->mfd_pdata = mfd_pdata;
muic_data->usbc_pdata = usbc_data;
muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC;
muic_data->is_muic_ready = false;
muic_data->is_otg_test = false;
muic_data->is_factory_start = false;
muic_data->switch_val = COM_OPEN;
muic_data->is_charger_mode = false;
usbc_data->muic_data = muic_data;
g_muic_data = muic_data;
#if IS_ENABLED(CONFIG_MUIC_UART_SWITCH)
muic_data->pdata->set_gpio_uart_sel = max77705_muic_set_gpio_uart_sel;
#endif
if (muic_data->pdata->init_gpio_cb) {
ret = muic_data->pdata->init_gpio_cb(get_switch_sel());
if (ret)
pr_err("%s: failed to init gpio(%d)\n", __func__, ret);
}
mutex_lock(&muic_data->muic_mutex);
/* create sysfs group */
ret = sysfs_create_group(&switch_device->kobj, &max77705_muic_group);
if (ret) {
pr_err("%s: failed to create attribute group\n",
__func__);
goto fail_sysfs_create;
}
dev_set_drvdata(switch_device, muic_data);
if (muic_data->pdata->init_switch_dev_cb)
muic_data->pdata->init_switch_dev_cb();
ret = max77705_muic_init_regs(muic_data);
if (ret < 0) {
pr_err("%s Failed to initialize MUIC irq:%d\n",
__func__, ret);
goto fail_init_irq;
}
mutex_unlock(&muic_data->muic_mutex);
INIT_DELAYED_WORK(&(muic_data->vbus_wa_work), max77705_muic_vbus_wa_work);
#if defined(CONFIG_MUIC_MAX77705_CCIC)
muic_data->pdata->opmode = get_ccic_info() & 0x0f;
if (muic_data->pdata->opmode & OPMODE_CCIC) {
max77705_muic_register_ccic_notifier(muic_data);
INIT_WORK(&(muic_data->ccic_info_data_work),
max77705_muic_handle_ccic_event);
}
muic_data->afc_water_disable = false;
#endif /* CONFIG_MUIC_MAX77705_CCIC */
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
if (get_afc_mode() == CH_MODE_AFC_DISABLE_VAL) {
pr_info("%s: afc_disable: AFC disabled\n", __func__);
muic_data->pdata->afc_disable = true;
} else {
pr_info("%s: afc_disable: AFC enabled\n", __func__);
muic_data->pdata->afc_disable = false;
}
muic_data->pdata->afc_disabled_updated = 0;
INIT_DELAYED_WORK(&(muic_data->afc_work),
max77705_muic_afc_work);
INIT_WORK(&(muic_data->afc_handle_work),
max77705_muic_detect_dev_hv_work);
/* set MUIC afc voltage switching function */
muic_data->pdata->muic_afc_set_voltage_cb = max77705_muic_afc_set_voltage;
muic_data->pdata->muic_hv_charger_disable_cb = max77705_muic_hv_charger_disable;
/* set MUIC check charger init function */
muic_data->pdata->muic_hv_charger_init_cb = max77705_muic_hv_charger_init;
muic_data->is_charger_ready = false;
muic_data->is_check_hv = false;
muic_data->hv_voltage = 0;
muic_data->afc_retry = 0;
muic_data->is_afc_reset = false;
muic_data->is_skip_bigdata = false;
#endif /* CONFIG_HV_MUIC_MAX77705_AFC */
#if defined(CONFIG_HICCUP_CHARGER)
muic_data->is_hiccup_mode = 0;
muic_data->pdata->muic_set_hiccup_mode_cb = max77705_muic_set_hiccup_mode;
#endif /* CONFIG_HICCUP_CHARGER */
/* initial cable detection */
max77705_muic_init_detect(muic_data);
/* register usb external notifier */
muic_register_usb_notifier(muic_data);
INIT_DELAYED_WORK(&(muic_data->debug_work),
max77705_muic_print_reg_log);
schedule_delayed_work(&(muic_data->debug_work),
msecs_to_jiffies(10000));
return 0;
fail_init_irq:
if (muic_data->pdata->cleanup_switch_dev_cb)
muic_data->pdata->cleanup_switch_dev_cb();
sysfs_remove_group(&switch_device->kobj, &max77705_muic_group);
fail_sysfs_create:
mutex_unlock(&muic_data->muic_mutex);
mutex_destroy(&muic_data->muic_mutex);
err_return:
return ret;
}
int max77705_muic_remove(struct max77705_usbc_platform_data *usbc_data)
{
struct max77705_muic_data *muic_data = usbc_data->muic_data;
sysfs_remove_group(&switch_device->kobj, &max77705_muic_group);
if (muic_data) {
pr_info("%s\n", __func__);
max77705_muic_free_irqs(muic_data);
cancel_delayed_work(&(muic_data->vbus_wa_work));
#if defined(CONFIG_MUIC_MAX77705_CCIC)
if (muic_data->pdata->opmode & OPMODE_CCIC) {
max77705_muic_unregister_ccic_notifier(muic_data);
cancel_work_sync(&(muic_data->ccic_info_data_work));
}
#endif
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
cancel_delayed_work_sync(&(muic_data->afc_work));
cancel_work_sync(&(muic_data->afc_handle_work));
#endif
muic_unregister_usb_notifier(muic_data);
cancel_delayed_work_sync(&(muic_data->debug_work));
if (muic_data->pdata->cleanup_switch_dev_cb)
muic_data->pdata->cleanup_switch_dev_cb();
mutex_destroy(&muic_data->muic_mutex);
wake_lock_destroy(&muic_data->muic_wake_lock);
}
return 0;
}
void max77705_muic_shutdown(struct max77705_usbc_platform_data *usbc_data)
{
struct max77705_muic_data *muic_data = usbc_data->muic_data;
pr_info("%s +\n", __func__);
sysfs_remove_group(&switch_device->kobj, &max77705_muic_group);
if (!muic_data) {
pr_err("%s no drvdata\n", __func__);
goto out;
}
com_to_open(muic_data);
max77705_muic_free_irqs(muic_data);
cancel_delayed_work(&(muic_data->vbus_wa_work));
#if defined(CONFIG_MUIC_MAX77705_CCIC)
if ((muic_data->pdata) && (muic_data->pdata->opmode & OPMODE_CCIC)) {
max77705_muic_unregister_ccic_notifier(muic_data);
cancel_work_sync(&(muic_data->ccic_info_data_work));
}
#endif
#if defined(CONFIG_HV_MUIC_MAX77705_AFC)
/* clear MUIC afc voltage switching function */
muic_data->pdata->muic_afc_set_voltage_cb = NULL;
/* clear MUIC check charger init function */
muic_data->pdata->muic_hv_charger_init_cb = NULL;
cancel_delayed_work_sync(&(muic_data->afc_work));
cancel_work_sync(&(muic_data->afc_handle_work));
#endif
#if defined(CONFIG_HICCUP_CHARGER)
muic_data->pdata->muic_set_hiccup_mode_cb = NULL;
#endif /* CONFIG_HICCUP_CHARGER */
muic_unregister_usb_notifier(muic_data);
cancel_delayed_work_sync(&(muic_data->debug_work));
out:
pr_info("%s -\n", __func__);
}
int max77705_muic_suspend(struct max77705_usbc_platform_data *usbc_data)
{
struct max77705_muic_data *muic_data = usbc_data->muic_data;
pr_info("%s\n", __func__);
cancel_delayed_work(&(muic_data->debug_work));
return 0;
}
int max77705_muic_resume(struct max77705_usbc_platform_data *usbc_data)
{
struct max77705_muic_data *muic_data = usbc_data->muic_data;
pr_info("%s\n", __func__);
schedule_delayed_work(&(muic_data->debug_work), msecs_to_jiffies(1000));
return 0;
}