blob: 134caa44b1243c9e0038295624dcb0825af129b4 [file] [log] [blame]
/*
* s2mu004-muic.c - MUIC driver for the Samsung s2mu004
*
* 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_HV] " 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/workqueue.h>
#include <linux/wakelock.h>
#include <linux/mfd/samsung/s2mu004.h>
#include <linux/mfd/samsung/s2mu004-private.h>
/* MUIC header file */
#include <linux/muic/muic.h>
#include <linux/muic/s2mu004-muic.h>
#include <linux/muic/s2mu004-muic-hv.h>
#ifdef CONFIG_MUIC_MANAGER
#include <linux/muic/muic_interface.h>
#endif
#if defined(CONFIG_MUIC_NOTIFIER)
#include <linux/muic/muic_notifier.h>
#endif /* CONFIG_MUIC_NOTIFIER */
#include <linux/delay.h>
static bool debug_en_checklist = true;
enum act_function_num {
FUNC_TA_TO_PREPARE,
FUNC_PREPARE_TO_PREPARE_DUPLI,
FUNC_PREPARE_TO_AFC_5V,
FUNC_PREPARE_TO_QC_PREPARE,
FUNC_PREPARE_DUPLI_TO_PREPARE_DUPLI,
FUNC_PREPARE_DUPLI_TO_AFC_5V,
FUNC_PREPARE_DUPLI_TO_AFC_ERR_V,
FUNC_PREPARE_DUPLI_TO_AFC_9V,
FUNC_AFC_5V_TO_AFC_5V_DUPLI,
FUNC_AFC_5V_TO_AFC_ERR_V,
FUNC_AFC_5V_TO_AFC_9V,
FUNC_AFC_5V_DUPLI_TO_AFC_5V_DUPLI,
FUNC_AFC_5V_DUPLI_TO_AFC_ERR_V,
FUNC_AFC_5V_DUPLI_TO_AFC_9V,
FUNC_AFC_ERR_V_TO_AFC_ERR_V_DUPLI,
FUNC_AFC_ERR_V_TO_AFC_9V,
FUNC_AFC_ERR_V_DUPLI_TO_AFC_ERR_V_DUPLI,
FUNC_AFC_ERR_V_DUPLI_TO_AFC_9V,
FUNC_AFC_9V_TO_AFC_5V,
FUNC_AFC_9V_TO_AFC_9V,
FUNC_QC_PREPARE_TO_QC_9V,
FUNC_QC_9V_TO_QC_5V,
};
muic_afc_data_t prepare_dupli_to_afc_9v;
/* afc_condition_checklist[ATTACHED_DEV_TA_MUIC] */
muic_afc_data_t ta_to_prepare = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC,
.afc_name = "AFC charger Prepare",
.afc_irq = MUIC_AFC_IRQ_VDNMON,
.status_vbadc = VBADC_DONTCARE,
.status_vdnmon = VDNMON_LOW,
.function_num = FUNC_TA_TO_PREPARE,
.next = &ta_to_prepare,
};
/* afc_condition_checklist[ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC] */
muic_afc_data_t prepare_to_qc_prepare = {
.new_dev = ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC,
.afc_name = "QC charger Prepare",
.afc_irq = MUIC_AFC_IRQ_MPNACK,
.status_vbadc = VBADC_DONTCARE,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_TO_QC_PREPARE,
.next = &prepare_dupli_to_afc_9v,
};
muic_afc_data_t prepare_to_afc_5v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC,
.afc_name = "AFC charger 5V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_5V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_TO_AFC_5V,
.next = &prepare_to_afc_5v,
};
muic_afc_data_t prepare_to_prepare_dupli = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC,
.afc_name = "AFC charger prepare (mrxrdy)",
.afc_irq = MUIC_AFC_IRQ_MRXRDY,
.status_vbadc = VBADC_DONTCARE,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_TO_PREPARE_DUPLI,
.next = &prepare_to_qc_prepare,
};
muic_afc_data_t prepare_dupli_to_prepare_dupli = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC,
.afc_name = "AFC charger prepare (mrxrdy)",
.afc_irq = MUIC_AFC_IRQ_MRXRDY,
.status_vbadc = VBADC_DONTCARE,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_DUPLI_TO_PREPARE_DUPLI,
.next = &prepare_to_qc_prepare,
};
muic_afc_data_t prepare_dupli_to_mrxtrf = {
.new_dev = ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC,
.afc_name = "QC charger Prepare",
.afc_irq = MUIC_AFC_IRQ_MRXTRF,
.status_vbadc = VBADC_DONTCARE,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_TO_QC_PREPARE,
.next = &prepare_dupli_to_prepare_dupli,
};
muic_afc_data_t prepare_dupli_to_afc_early_9v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC,
.afc_name = "AFC charger 9V (early in mrxrdy)",
.afc_irq = MUIC_AFC_IRQ_MRXRDY,
.status_vbadc = VBADC_AFC_9V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_DUPLI_TO_AFC_9V,
.next = &prepare_dupli_to_mrxtrf,
};
muic_afc_data_t prepare_dupli_to_afc_9v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC,
.afc_name = "AFC charger 9V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_9V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_DUPLI_TO_AFC_9V,
.next = &prepare_dupli_to_afc_early_9v,
};
muic_afc_data_t prepare_dupli_to_afc_err_v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC,
.afc_name = "AFC charger ERR V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_ERR_V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_DUPLI_TO_AFC_ERR_V,
.next = &prepare_dupli_to_afc_9v,
};
muic_afc_data_t prepare_dupli_to_afc_5v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC,
.afc_name = "AFC charger 5V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_5V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_PREPARE_DUPLI_TO_AFC_5V,
.next = &prepare_dupli_to_afc_err_v,
};
muic_afc_data_t afc_5v_to_afc_9v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC,
.afc_name = "AFC charger 9V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_5V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_5V_TO_AFC_9V,
.next = &afc_5v_to_afc_9v,
};
muic_afc_data_t afc_5v_to_afc_err_v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC,
.afc_name = "AFC charger ERR V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_ERR_V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_5V_TO_AFC_ERR_V,
.next = &afc_5v_to_afc_9v,
};
muic_afc_data_t afc_5v_to_afc_5v_dupli = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC,
.afc_name = "AFC charger 5V (mrxrdy)",
.afc_irq = MUIC_AFC_IRQ_MRXRDY,
.status_vbadc = VBADC_AFC_5V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_5V_TO_AFC_5V_DUPLI,
.next = &prepare_dupli_to_prepare_dupli,
};
muic_afc_data_t afc_5v_dupli_to_afc_5v_dupli = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC,
.afc_name = "AFC charger 5V (mrxrdy)",
.afc_irq = MUIC_AFC_IRQ_MRXRDY,
.status_vbadc = VBADC_AFC_5V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_5V_DUPLI_TO_AFC_5V_DUPLI,
.next = &afc_5v_dupli_to_afc_5v_dupli,
};
muic_afc_data_t afc_5v_dupli_to_afc_9v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC,
.afc_name = "AFC charger 9V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_9V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_5V_DUPLI_TO_AFC_9V,
.next = &afc_5v_dupli_to_afc_5v_dupli,
};
muic_afc_data_t afc_5v_dupli_to_afc_err_v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC,
.afc_name = "AFC charger ERR V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_ERR_V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_5V_DUPLI_TO_AFC_ERR_V,
.next = &afc_5v_dupli_to_afc_9v,
};
muic_afc_data_t afc_err_v_to_afc_9v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC,
.afc_name = "AFC charger 9V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_9V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_ERR_V_TO_AFC_9V,
.next = &afc_err_v_to_afc_9v,
};
muic_afc_data_t afc_err_v_to_afc_err_v_dupli = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC,
.afc_name = "AFC charger ERR V (mrxrdy)",
.afc_irq = MUIC_AFC_IRQ_MRXRDY,
.status_vbadc = VBADC_AFC_ERR_V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_ERR_V_TO_AFC_ERR_V_DUPLI,
.next = &afc_err_v_to_afc_9v,
};
muic_afc_data_t afc_err_v_dupli_to_afc_9v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC,
.afc_name = "AFC charger 9V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_9V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_ERR_V_DUPLI_TO_AFC_9V,
.next = &afc_err_v_dupli_to_afc_9v,
};
muic_afc_data_t afc_err_v_dupli_to_afc_err_v_dupli = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC,
.afc_name = "AFC charger ERR V (mrxrdy)",
.afc_irq = MUIC_AFC_IRQ_MRXRDY,
.status_vbadc = VBADC_AFC_ERR_V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_ERR_V_DUPLI_TO_AFC_ERR_V_DUPLI,
.next = &afc_err_v_dupli_to_afc_9v,
};
muic_afc_data_t qc_prepare_to_qc_9v = {
.new_dev = ATTACHED_DEV_QC_CHARGER_9V_MUIC,
.afc_name = "QC charger 9V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_QC_9V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_QC_PREPARE_TO_QC_9V,
.next = &qc_prepare_to_qc_9v,
};
muic_afc_data_t qc_9v_to_qc_9v = {
.new_dev = ATTACHED_DEV_QC_CHARGER_9V_MUIC,
.afc_name = "QC charger 9V",
.afc_irq = MUIC_AFC_IRQ_DONTCARE,
.status_vbadc = VBADC_QC_9V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_QC_PREPARE_TO_QC_9V,
.next = &qc_9v_to_qc_9v,
};
/* afc_condition_checklist[ATTACHED_DEV_AFC_CHARGER_9V_MUIC] */
muic_afc_data_t afc_9v_to_afc_5v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC,
.afc_name = "AFC charger 5V",
.afc_irq = MUIC_AFC_IRQ_VBADC,
.status_vbadc = VBADC_AFC_5V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_9V_TO_AFC_5V,
.next = &afc_5v_to_afc_5v_dupli,
};
/* afc_condition_checklist[ATTACHED_DEV_AFC_CHARGER_9V_MUIC] */
muic_afc_data_t afc_9v_to_afc_9v = {
.new_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC,
.afc_name = "AFC charger 9V",
.afc_irq = MUIC_AFC_IRQ_DONTCARE,
.status_vbadc = VBADC_AFC_9V,
.status_vdnmon = VDNMON_DONTCARE,
.function_num = FUNC_AFC_9V_TO_AFC_9V,
.next = &afc_9v_to_afc_5v,
};
muic_afc_data_t *afc_condition_checklist[ATTACHED_DEV_NUM] = {
[ATTACHED_DEV_TA_MUIC] = &ta_to_prepare,
[ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC] = &prepare_to_prepare_dupli,
[ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC] = &prepare_dupli_to_afc_5v,
[ATTACHED_DEV_AFC_CHARGER_5V_MUIC] = &afc_5v_to_afc_5v_dupli,
[ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC] = &afc_5v_dupli_to_afc_err_v,
[ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC] = &afc_err_v_to_afc_err_v_dupli,
[ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC] = &afc_err_v_dupli_to_afc_err_v_dupli,
[ATTACHED_DEV_AFC_CHARGER_9V_MUIC] = &afc_9v_to_afc_9v,
[ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC] = &qc_prepare_to_qc_9v,
[ATTACHED_DEV_QC_CHARGER_9V_MUIC] = &qc_9v_to_qc_9v,
};
struct afc_init_data_s {
struct work_struct muic_afc_init_work;
struct s2mu004_muic_data *muic_data;
};
struct afc_init_data_s afc_init_data;
static void s2mu004_mrxtrf_irq_mask(struct s2mu004_muic_data *muic_data, int enable);
bool muic_check_is_hv_dev(struct s2mu004_muic_data *muic_data)
{
bool ret = false;
struct muic_platform_data *muic_pdata = muic_data->pdata;
switch (muic_pdata->attached_dev) {
case ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC:
case ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC:
case ATTACHED_DEV_AFC_CHARGER_5V_MUIC:
case ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC:
case ATTACHED_DEV_AFC_CHARGER_9V_MUIC:
case ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC:
case ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC:
case ATTACHED_DEV_HV_ID_ERR_UNDEFINED_MUIC:
case ATTACHED_DEV_HV_ID_ERR_UNSUPPORTED_MUIC:
case ATTACHED_DEV_HV_ID_ERR_SUPPORTED_MUIC:
ret = true;
break;
default:
ret = false;
break;
}
if (debug_en_checklist)
pr_info("%s attached_dev(%d)[%c]\n",
__func__, muic_pdata->attached_dev, (ret ? 'T' : 'F'));
return ret;
}
muic_attached_dev_t hv_muic_check_id_err(struct s2mu004_muic_data *muic_data,
muic_attached_dev_t new_dev)
{
muic_attached_dev_t after_new_dev = new_dev;
struct muic_platform_data *muic_pdata = muic_data->pdata;
if (!muic_check_is_hv_dev(muic_data))
goto out;
switch (new_dev) {
case ATTACHED_DEV_TA_MUIC:
pr_info("%s cannot change HV(%d)->TA(%d)!\n",
__func__, muic_pdata->attached_dev, new_dev);
after_new_dev = muic_pdata->attached_dev;
break;
case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC:
pr_info("%s Undefined\n", __func__);
after_new_dev = ATTACHED_DEV_HV_ID_ERR_UNDEFINED_MUIC;
break;
case ATTACHED_DEV_UNSUPPORTED_ID_VB_MUIC:
pr_info("%s Unsupported\n", __func__);
after_new_dev = ATTACHED_DEV_HV_ID_ERR_UNSUPPORTED_MUIC;
break;
default:
pr_info("%s Supported\n", __func__);
after_new_dev = ATTACHED_DEV_HV_ID_ERR_SUPPORTED_MUIC;
break;
}
out:
return after_new_dev;
}
static int s2mu004_hv_muic_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
{
u8 before_val, after_val;
int ret;
s2mu004_read_reg(i2c, reg, &before_val);
ret = s2mu004_write_reg(i2c, reg, value);
s2mu004_read_reg(i2c, reg, &after_val);
pr_info("%s reg[0x%02x] = [0x%02x] + [0x%02x] -> [0x%02x]\n",
__func__, reg, before_val, value, after_val);
return ret;
}
int s2mu004_muic_hv_update_reg(struct i2c_client *i2c,
const u8 reg, const u8 val, const u8 mask, const bool debug_en)
{
u8 before_val, new_val, after_val;
int ret = 0;
ret = s2mu004_read_reg(i2c, reg, &before_val);
if (ret)
pr_err("%s failed to read REG(0x%02x) [%d]\n",
__func__, reg, ret);
new_val = (val & mask) | (before_val & (~mask));
if (before_val ^ new_val) {
ret = s2mu004_hv_muic_write_reg(i2c, reg, new_val);
if (ret)
pr_err("%s failed to write REG(0x%02x) [%d]\n",
__func__, reg, ret);
} else if (debug_en) {
pr_info("%s REG(0x%02x): already [0x%02x], don't write reg\n",
__func__, reg, before_val);
goto out;
}
if (debug_en) {
ret = s2mu004_read_reg(i2c, reg, &after_val);
if (ret < 0)
pr_err("%s failed to read REG(0x%02x) [%d]\n",
__func__, reg, ret);
pr_info("%s REG(0x%02x): [0x%02x]+[0x%02x:0x%02x]=[0x%02x]\n",
__func__, reg, before_val,
val, mask, after_val);
}
out:
return ret;
}
void s2mu004_hv_muic_reset_hvcontrol_reg(struct s2mu004_muic_data *muic_data)
{
struct i2c_client *i2c = muic_data->i2c;
cancel_delayed_work(&muic_data->afc_qc_retry);
cancel_delayed_work(&muic_data->prepare_afc_charger);
cancel_delayed_work(&muic_data->afc_send_mpnack);
cancel_delayed_work(&muic_data->afc_control_ping_retry);
s2mu004_hv_muic_write_reg(i2c, 0x4b, 0x00);
s2mu004_hv_muic_write_reg(i2c, 0x49, 0x00);
s2mu004_hv_muic_write_reg(i2c, 0x4a, 0x00);
s2mu004_hv_muic_write_reg(i2c, 0x5f, 0x01);
s2mu004_mrxtrf_irq_mask(muic_data, 0);
msleep(50);
muic_data->is_afc_muic_prepare = false;
s2mu004_muic_set_afc_ready(muic_data, false);
muic_data->is_afcblock_ready = false;
muic_data->is_handling_afc = false;
muic_data->is_mrxtrf_in = false;
muic_data->retry_qc_cnt = 0;
muic_data->qc_prepare = 0;
#if IS_ENABLED(CONFIG_NONE_WATERPROOF_MODEL)
muic_data->afc_check = false;
#endif
}
void s2mu004_muic_set_afc_ready(struct s2mu004_muic_data *muic_data, bool value)
{
bool before, after;
before = muic_data->is_afc_muic_ready;
muic_data->is_afc_muic_ready = value;
after = muic_data->is_afc_muic_ready;
pr_info("%s afc_muic_ready[%d->%d]\n", __func__, before, after);
}
static int s2mu004_hv_muic_state_maintain(struct s2mu004_muic_data *muic_data)
{
int ret = 0;
struct muic_platform_data *muic_pdata = muic_data->pdata;
pr_info("%s\n", __func__);
if (muic_pdata->attached_dev == ATTACHED_DEV_NONE_MUIC) {
pr_info("%s Detached(%d), need to check after\n",
__func__, muic_pdata->attached_dev);
return ret;
}
return ret;
}
static void s2mu004_mrxtrf_irq_mask(struct s2mu004_muic_data *muic_data, int enable)
{
pr_info("%s: irq enable: %d\n", __func__, enable);
if (enable)
s2mu004_muic_hv_update_reg(muic_data->i2c, 0x05, 0x00, 0x20, 0);
else
s2mu004_muic_hv_update_reg(muic_data->i2c, 0x05, 0x20, 0x20, 0);
}
static void s2mu004_mpnack_irq_mask(struct s2mu004_muic_data *muic_data, int enable)
{
pr_info("%s: irq enable: %d\n", __func__, enable);
if (enable)
s2mu004_muic_hv_update_reg(muic_data->i2c, 0x05, 0x00, 0x08, 0);
else
s2mu004_muic_hv_update_reg(muic_data->i2c, 0x05, 0x08, 0x08, 0);
}
static void s2mu004_hv_muic_set_afc_after_prepare
(struct s2mu004_muic_data *muic_data)
{
u8 reg_val = 0;
pr_info("%s HV charger is detected\n", __func__);
muic_data->retry_cnt = 0;
muic_data->is_afcblock_ready = true;
s2mu004_mpnack_irq_mask(muic_data, 0);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x5f, 0x05);
s2mu004_read_reg(muic_data->i2c, S2MU004_REG_AFC_INT, &reg_val);
pr_info("%s afc_int(%#x)\n", __func__, reg_val);
msleep(20);
s2mu004_mrxtrf_irq_mask(muic_data, 1);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4A, 0x06);
usleep_range(10000, 11000);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4A, 0x0e);
cancel_delayed_work(&muic_data->afc_control_ping_retry);
cancel_delayed_work(&muic_data->afc_send_mpnack);
schedule_delayed_work(&muic_data->afc_send_mpnack, msecs_to_jiffies(2000));
schedule_delayed_work(&muic_data->afc_control_ping_retry, msecs_to_jiffies(50));
}
void s2mu004_muic_afc_after_prepare(struct work_struct *work)
{
struct s2mu004_muic_data *muic_data =
container_of(work, struct s2mu004_muic_data, afc_after_prepare.work);
pr_info("%s\n ", __func__);
mutex_lock(&muic_data->afc_mutex);
s2mu004_hv_muic_set_afc_after_prepare(muic_data);
mutex_unlock(&muic_data->afc_mutex);
}
static void s2mu004_muic_afc_xiaomi_retry(struct s2mu004_muic_data *muic_data)
{
u8 vdnmon = 0;
u8 reg_val = 0, maskRead_val = 0, maskWrite_val = 0;
int i;
pr_info("%s\n", __func__);
/* Mask vdnmon_INT */
s2mu004_read_reg(muic_data->i2c, S2MU004_REG_AFC_INT_MASK, &maskRead_val);
maskWrite_val = maskRead_val | 0x2;
s2mu004_hv_muic_write_reg(muic_data->i2c, S2MU004_REG_AFC_INT_MASK, maskWrite_val);
msleep(20);
/* Reset xiaomi battery pack*/
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x5f, 0x05);
s2mu004_hv_muic_write_reg(muic_data->i2c, S2MU004_REG_AFC_CTRL1, 0x00);
msleep(20);
/* Prepare afc block */
reg_val = (HVCONTROL1_AFCEN_MASK | HVCONTROL1_VBUSADCEN_MASK |
HVCONTROL1_DPDNVDEN_MASK);
s2mu004_hv_muic_write_reg(muic_data->i2c, S2MU004_REG_AFC_CTRL1, reg_val);
msleep(20);
s2mu004_hv_muic_write_reg(muic_data->i2c, S2MU004_REG_AFC_CTRL2, HVCONTROL2_DP06EN_MASK);
for (i = 0; i < 5; i++) {
msleep(350);
s2mu004_read_reg(muic_data->i2c, S2MU004_REG_AFC_STATUS, &vdnmon);
if ((vdnmon & STATUS_VDNMON_MASK) == VDNMON_LOW)
break;
}
if (i == 5) {
pr_info("%s retry fail vdnmon(%#x)\n", __func__, vdnmon);
goto RETRY_FAIL;
}
/* Retry qc 9V */
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x5f, 0x01);
s2mu004_hv_muic_write_reg(muic_data->i2c, S2MU004_REG_AFC_CTRL1, 0xbd);
msleep(20);
RETRY_FAIL:
/* Restore mask vdnmon_INT */
s2mu004_hv_muic_write_reg(muic_data->i2c, S2MU004_REG_AFC_INT_MASK, maskRead_val);
}
#define RETRY_QC_CNT 3
void s2mu004_muic_afc_qc_retry(struct work_struct *work)
{
struct s2mu004_muic_data *muic_data =
container_of(work, struct s2mu004_muic_data, afc_qc_retry.work);
u8 vbadc, vbvolt = s2mu004_muic_get_vbus_state(muic_data);
s2mu004_read_reg(muic_data->i2c, S2MU004_REG_AFC_STATUS, &vbadc);
vbadc &= STATUS_VBADC_MASK;
cancel_delayed_work(&muic_data->afc_qc_retry);
if (vbadc == VBADC_8_7V_9_3V || !vbvolt) {
pr_info("%s skip vbadc(%#x), vbvolt(%d)\n", __func__, vbadc, vbvolt);
muic_data->retry_qc_cnt = 0;
muic_data->qc_prepare = 0;
return;
}
pr_info("%s retry_qc_cnt : (%d)\n", __func__, muic_data->retry_qc_cnt);
if (muic_data->retry_qc_cnt < RETRY_QC_CNT) {
if (muic_data->is_mrxtrf_in) {
s2mu004_muic_afc_xiaomi_retry(muic_data);
} else {
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x49, 0x00);
msleep(20);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x49, 0xbd);
}
muic_data->retry_qc_cnt++;
schedule_delayed_work(&muic_data->afc_qc_retry, msecs_to_jiffies(100));
} else {
muic_data->retry_qc_cnt = 0;
muic_data->qc_prepare = 0;
}
}
#define RETRY_PING_CNT 20
static void s2mu004_muic_afc_control_ping_retry(struct work_struct *work)
{
struct s2mu004_muic_data *muic_data =
container_of(work, struct s2mu004_muic_data, afc_control_ping_retry.work);
pr_info("%s retry_cnt : %d\n", __func__, muic_data->retry_cnt);
if (!s2mu004_muic_get_vbus_state(muic_data)) {
cancel_delayed_work(&muic_data->afc_control_ping_retry);
return;
}
if (muic_data->retry_cnt < RETRY_PING_CNT) {
muic_data->retry_cnt++;
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4A, 0x06);
usleep_range(10000, 11000);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4A, 0x0e);
schedule_delayed_work(&muic_data->afc_control_ping_retry, msecs_to_jiffies(50));
} else {
muic_data->retry_cnt = 0;
s2mu004_mpnack_irq_mask(muic_data, 1); /* enable mpnack irq */
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4A, 0x0e);
}
}
static void s2mu004_hv_muic_afc_control_ping
(struct s2mu004_muic_data *muic_data, bool ping_continue)
{
pr_info("%s [%d, %c]\n", __func__,
muic_data->afc_count, ping_continue ? 'T' : 'F');
if (ping_continue) {
msleep(30);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4A, 0x06);
usleep_range(10000, 11000);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4A, 0x0e);
schedule_delayed_work(&muic_data->afc_send_mpnack, msecs_to_jiffies(2000));
schedule_delayed_work(&muic_data->afc_control_ping_retry, msecs_to_jiffies(50));
}
}
static int s2mu004_hv_muic_handle_attach
(struct s2mu004_muic_data *muic_data, const muic_afc_data_t *new_afc_data)
{
int ret = 0;
bool noti = true;
muic_attached_dev_t new_dev = new_afc_data->new_dev;
struct muic_platform_data *muic_pdata = muic_data->pdata;
pr_info("%s %d\n", __func__, new_afc_data->function_num);
if (muic_data->is_charger_ready == false) {
if (new_afc_data->new_dev == ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC) {
muic_data->is_afc_muic_prepare = true;
pr_info("%s is_charger_ready[%c], is_afc_muic_prepare[%c]\n",
__func__,
(muic_data->is_charger_ready ? 'T' : 'F'),
(muic_data->is_afc_muic_prepare ? 'T' : 'F'));
return ret;
}
pr_info("%s is_charger_ready[%c], just return\n",
__func__, (muic_data->is_charger_ready ? 'T' : 'F'));
return ret;
} else if (!s2mu004_muic_get_vbus_state(muic_data)) {
return ret;
}
switch (new_afc_data->function_num) {
case FUNC_TA_TO_PREPARE:
schedule_delayed_work(&muic_data->afc_after_prepare, msecs_to_jiffies(60));
muic_data->afc_count = 0;
break;
case FUNC_PREPARE_TO_PREPARE_DUPLI:
muic_data->afc_count++;
if (muic_data->afc_count < 3) {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_PREPARE_TO_AFC_5V:
if (muic_data->afc_count > AFC_CHARGER_WA_PING) {
s2mu004_hv_muic_afc_control_ping(muic_data, false);
} else {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_PREPARE_TO_QC_PREPARE:
if (muic_data->is_afcblock_ready == false)
return ret;
muic_data->afc_count = 0;
muic_data->retry_qc_cnt = 0;
muic_data->qc_prepare = 1;
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4A, 0x06);
msleep(60);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x5f, 0x01);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x49, 0xbd);
schedule_delayed_work(&muic_data->afc_qc_retry, msecs_to_jiffies(100));
break;
case FUNC_PREPARE_DUPLI_TO_PREPARE_DUPLI:
pr_err("[DEBUG] %s(%d)dupli, dupli\n", __func__, __LINE__);
muic_data->afc_count++;
if (muic_data->afc_count < 3) {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_PREPARE_DUPLI_TO_AFC_5V:
break;
case FUNC_PREPARE_DUPLI_TO_AFC_ERR_V:
if (muic_data->afc_count > AFC_CHARGER_WA_PING) {
s2mu004_hv_muic_afc_control_ping(muic_data, false);
} else {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_PREPARE_DUPLI_TO_AFC_9V:
s2mu004_hv_muic_afc_control_ping(muic_data, false);
cancel_delayed_work(&muic_data->afc_control_ping_retry);
break;
case FUNC_AFC_5V_TO_AFC_5V_DUPLI:
/*
* attached_dev is changed. MPING Missing did not happened
* Cancel delayed work
*/
pr_info("%s cancel_delayed_work(dev %d), Mping missing wa\n",
__func__, new_dev);
muic_data->afc_count++;
if (muic_data->afc_count < 3) {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_AFC_5V_TO_AFC_ERR_V:
/*
* attached_dev is changed. MPING Missing did not happened
* Cancel delayed work
*/
pr_info("%s cancel_delayed_work(dev %d), Mping missing wa\n",
__func__, new_dev);
if (muic_data->afc_count > AFC_CHARGER_WA_PING) {
s2mu004_hv_muic_afc_control_ping(muic_data, false);
} else {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_AFC_5V_TO_AFC_9V:
/*
* attached_dev is changed. MPING Missing did not happened
* Cancel delayed work
*/
pr_info("%s cancel_delayed_work(dev %d), Mping missing wa\n",
__func__, new_dev);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x05, 0x00);
break;
case FUNC_AFC_5V_DUPLI_TO_AFC_5V_DUPLI:
muic_data->afc_count++;
if (muic_data->afc_count > AFC_CHARGER_WA_PING) {
s2mu004_hv_muic_afc_control_ping(muic_data, false);
} else {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_AFC_5V_DUPLI_TO_AFC_ERR_V:
if (muic_data->afc_count > AFC_CHARGER_WA_PING) {
s2mu004_hv_muic_afc_control_ping(muic_data, false);
} else {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_AFC_5V_DUPLI_TO_AFC_9V:
s2mu004_hv_muic_afc_control_ping(muic_data, false);
#ifdef CONFIG_MUIC_HV_FORCE_LIMIT
if (muic_data->pdata->silent_chg_change_state == SILENT_CHG_CHANGING)
noti = false;
#endif
break;
case FUNC_AFC_ERR_V_TO_AFC_ERR_V_DUPLI:
muic_data->afc_count++;
if (muic_data->afc_count > AFC_CHARGER_WA_PING) {
s2mu004_hv_muic_afc_control_ping(muic_data, false);
} else {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_AFC_ERR_V_TO_AFC_9V:
s2mu004_hv_muic_afc_control_ping(muic_data, false);
break;
case FUNC_AFC_ERR_V_DUPLI_TO_AFC_ERR_V_DUPLI:
muic_data->afc_count++;
if (muic_data->afc_count > AFC_CHARGER_WA_PING) {
s2mu004_hv_muic_afc_control_ping(muic_data, false);
} else {
s2mu004_hv_muic_afc_control_ping(muic_data, true);
noti = false;
}
break;
case FUNC_AFC_ERR_V_DUPLI_TO_AFC_9V:
s2mu004_hv_muic_afc_control_ping(muic_data, false);
break;
case FUNC_AFC_9V_TO_AFC_5V:
break;
case FUNC_AFC_9V_TO_AFC_9V:
#ifdef CONFIG_MUIC_HV_FORCE_LIMIT
muic_data->afc_count++;
if (muic_data->afc_count > AFC_CHARGER_WA_PING)
s2mu004_hv_muic_afc_control_ping(muic_data, false);
else
pr_info("dummy int called [%d]\n", muic_data->afc_count);
#else
cancel_delayed_work(&muic_data->afc_control_ping_retry);
#endif
break;
case FUNC_QC_PREPARE_TO_QC_9V:
pr_info("%s FUNC_QC_PREPARE_TO_QC_9V\n", __func__);
cancel_delayed_work(&muic_data->afc_qc_retry);
break;
default:
pr_warn("%s undefinded hv function num(%d)\n",
__func__, new_afc_data->function_num);
ret = -ESRCH;
goto out;
}
if (muic_pdata->attached_dev == new_dev)
noti = false;
else if (new_dev == ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC)
noti = false;
if (noti) {
if (new_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC) {
pr_err("%s: AFC CHARGER 5V MUIC delay cable noti\n", __func__);
schedule_delayed_work(&muic_data->afc_cable_type_work, msecs_to_jiffies(80));
} else {
muic_notifier_attach_attached_dev(new_dev);
}
}
muic_pdata->attached_dev = new_dev;
out:
return ret;
}
static bool muic_check_hv_irq
(struct s2mu004_muic_data *muic_data,
const muic_afc_data_t *afc_data, int irq)
{
int afc_irq = 0;
bool ret = false;
pr_info("%s irq %d , irq_dnres %d, irq_mrxrdy %d, irq_vbadc %d irq_mpnack %d irq_vdnmon %d\n",
__func__, irq, muic_data->irq_dnres, muic_data->irq_mrxrdy, muic_data->irq_vbadc,
muic_data->irq_mpnack, muic_data->irq_vdnmon);
/* change irq num to muic_afc_irq_t */
if (irq == muic_data->irq_vbadc)
afc_irq = MUIC_AFC_IRQ_VBADC;
else if (irq == muic_data->irq_mrxrdy)
afc_irq = MUIC_AFC_IRQ_MRXRDY;
else if (irq == muic_data->irq_vdnmon)
afc_irq = MUIC_AFC_IRQ_VDNMON;
else if (irq == muic_data->irq_mpnack)
afc_irq = MUIC_AFC_IRQ_MPNACK;
else if (irq == muic_data->irq_mrxtrf)
afc_irq = MUIC_AFC_IRQ_MRXTRF;
else {
pr_err("%s cannot find irq #(%d)\n", __func__, irq);
ret = false;
goto out;
}
pr_info("%s afc_irq %d , %d\n",
__func__, afc_data->afc_irq, afc_irq);
if (afc_data->afc_irq == afc_irq) {
ret = true;
goto out;
}
if (afc_data->afc_irq == MUIC_AFC_IRQ_DONTCARE) {
ret = true;
goto out;
}
out:
if (debug_en_checklist)
pr_info("%s check_data dev(%d) irq(%d:%d) ret(%c)\n",
__func__, afc_data->new_dev,
afc_data->afc_irq, afc_irq, ret ? 'T' : 'F');
return ret;
}
static bool muic_check_status_vbadc
(const muic_afc_data_t *afc_data, u8 vbadc)
{
bool ret = false;
if (afc_data->status_vbadc == vbadc) {
ret = true;
goto out;
}
if (afc_data->status_vbadc == VBADC_AFC_5V) {
switch (vbadc) {
case VBADC_5_3V:
case VBADC_5_7V_6_3V:
ret = true;
goto out;
default:
break;
}
}
if (afc_data->status_vbadc == VBADC_AFC_9V) {
switch (vbadc) {
case VBADC_8_7V_9_3V:
case VBADC_9_7V_10_3V:
ret = true;
goto out;
default:
break;
}
}
if (afc_data->status_vbadc == VBADC_AFC_ERR_V_NOT_0) {
switch (vbadc) {
case VBADC_7_7V_8_3V:
case VBADC_10_7V_11_3V:
case VBADC_11_7V_12_3V:
case VBADC_12_7V_13_3V:
case VBADC_13_7V_14_3V:
case VBADC_14_7V_15_3V:
case VBADC_15_7V_16_3V:
case VBADC_16_7V_17_3V:
case VBADC_17_7V_18_3V:
case VBADC_18_7V_19_3V:
ret = true;
goto out;
default:
break;
}
}
if (afc_data->status_vbadc == VBADC_AFC_ERR_V) {
switch (vbadc) {
case VBADC_7_7V_8_3V:
case VBADC_10_7V_11_3V:
case VBADC_11_7V_12_3V:
case VBADC_12_7V_13_3V:
case VBADC_13_7V_14_3V:
case VBADC_14_7V_15_3V:
case VBADC_15_7V_16_3V:
case VBADC_16_7V_17_3V:
case VBADC_17_7V_18_3V:
case VBADC_18_7V_19_3V:
case VBADC_19_7V:
ret = true;
goto out;
default:
break;
}
}
#if 0
if (afc_data->status_vbadc == VBADC_QC_5V) {
switch (vbadc) {
case VBADC_4V_5V:
case VBADC_5V_6V:
ret = true;
goto out;
default:
break;
}
}
if (afc_data->status_vbadc == VBADC_QC_9V) {
switch (vbadc) {
case VBADC_6V_7V:
case VBADC_7V_8V:
case VBADC_8V_9V:
case VBADC_9V_10V:
ret = true;
goto out;
default:
break;
}
}
#endif
if (afc_data->status_vbadc == VBADC_ANY) {
switch (vbadc) {
case VBADC_5_3V:
case VBADC_5_7V_6_3V:
case VBADC_6_7V_7_3V:
case VBADC_7_7V_8_3V:
case VBADC_8_7V_9_3V:
case VBADC_9_7V_10_3V:
case VBADC_10_7V_11_3V:
case VBADC_11_7V_12_3V:
case VBADC_12_7V_13_3V:
case VBADC_13_7V_14_3V:
case VBADC_14_7V_15_3V:
case VBADC_15_7V_16_3V:
case VBADC_16_7V_17_3V:
case VBADC_17_7V_18_3V:
case VBADC_18_7V_19_3V:
case VBADC_19_7V:
ret = true;
goto out;
default:
break;
}
}
if (afc_data->status_vbadc == VBADC_DONTCARE) {
ret = true;
goto out;
}
out:
if (debug_en_checklist)
pr_info("%s check_data dev(%d) vbadc(0x%x:0x%x) ret(%c)\n",
__func__, afc_data->new_dev,
afc_data->status_vbadc, vbadc, ret ? 'T' : 'F');
return ret;
}
static bool muic_check_status_vdnmon
(const muic_afc_data_t *afc_data, u8 vdnmon)
{
bool ret = false;
if (afc_data->status_vdnmon == vdnmon) {
ret = true;
goto out;
}
if (afc_data->status_vdnmon == VDNMON_DONTCARE) {
ret = true;
goto out;
}
out:
if (debug_en_checklist)
pr_info("%s check_data dev(%d) vdnmon(0x%x:0x%x) ret(%c)\n",
__func__, afc_data->new_dev,
afc_data->status_vdnmon, vdnmon, ret ? 'T' : 'F');
return ret;
}
bool muic_check_dev_ta(struct s2mu004_muic_data *muic_data)
{
u8 status1 = muic_data->status1;
u8 status2 = muic_data->status2;
u8 status4 = muic_data->status4;
u8 adc, vbvolt, chgtyp;
struct muic_platform_data *muic_pdata = muic_data->pdata;
adc = status1 & ADC_MASK;
vbvolt = status2 & DEV_TYPE_APPLE_VBUS_WAKEUP;
chgtyp = status4 & DEV_TYPE_DCPCHG;
pr_info("%s adc %d\n", __func__, adc);
if (adc != ADC_OPEN) {
s2mu004_muic_set_afc_ready(muic_data, false);
return false;
}
if (vbvolt == 0 || chgtyp == 0) {
s2mu004_muic_set_afc_ready(muic_data, false);
#if defined(CONFIG_MUIC_NOTIFIER)
muic_notifier_detach_attached_dev(muic_pdata->attached_dev);
#endif
muic_pdata->attached_dev = ATTACHED_DEV_NONE_MUIC;
s2mu004_hv_muic_reset_hvcontrol_reg(muic_data);
return false;
}
return true;
}
static void s2mu004_hv_muic_detect_dev(struct s2mu004_muic_data *muic_data, int irq)
{
struct i2c_client *i2c = muic_data->i2c;
struct muic_platform_data *muic_pdata = muic_data->pdata;
const muic_afc_data_t *afc_data = afc_condition_checklist[muic_pdata->attached_dev];
#ifdef CONFIG_MUIC_MANAGER
struct muic_interface_t *muic_if = (struct muic_interface_t *)muic_data->if_data;
#endif
int intr = MUIC_INTR_DETACH;
int ret;
int i;
u8 adc, dev_app, status, chgt;
u8 hvcontrol[2];
u8 vdnmon, vbadc;
bool flag_next = true;
bool muic_dev_ta = false;
if (afc_data == NULL) {
pr_err("%s non AFC Charger, just return!\n", __func__);
return;
}
ret = s2mu004_read_reg(i2c, S2MU004_REG_MUIC_ADC, &adc);
if (ret) {
pr_err("%s fail to read muic reg(%d)\n", __func__, ret);
return;
}
ret = s2mu004_read_reg(i2c, S2MU004_REG_MUIC_DEVICE_APPLE, &dev_app);
if (ret) {
pr_err("%s fail to read muic reg(%d)\n", __func__, ret);
return;
}
ret = s2mu004_read_reg(i2c, S2MU004_REG_AFC_STATUS, &status);
if (ret) {
pr_err("%s fail to read muic reg(%d)\n", __func__, ret);
return;
}
ret = s2mu004_read_reg(i2c, S2MU004_REG_MUIC_CHG_TYPE, &chgt);
if (ret) {
pr_err("%s fail to read muic reg(%d)\n", __func__, ret);
return;
}
pr_info("STATUS1:0x%02x, 2:0x%02x, 3:0x%02x\n",
adc, dev_app, status);
muic_data->status1 = adc;
muic_data->status2 = dev_app;
muic_data->status3 = status;
muic_data->status4 = chgt;
/* check TA type */
muic_dev_ta = muic_check_dev_ta(muic_data);
if (!muic_dev_ta) {
pr_err("%s device type is not TA!\n", __func__);
return;
}
vdnmon = status & STATUS_VDNMON_MASK;
vbadc = status & STATUS_VBADC_MASK;
ret = s2mu004_bulk_read(i2c, S2MU004_REG_AFC_CTRL1, 2, hvcontrol);
if (ret) {
pr_err("%s fail to read muic reg(%d)\n",
__func__, ret);
return;
}
pr_info("%s HVCONTROL1:0x%02x, 2:0x%02x\n", __func__,
hvcontrol[0], hvcontrol[1]);
/* attached - control */
muic_data->hvcontrol1 = hvcontrol[0];
muic_data->hvcontrol2 = hvcontrol[1];
pr_info("%s vbadc:0x%x\n", __func__, vbadc);
for (i = 0; i < 10; i++, afc_data = afc_data->next) {
if (!flag_next) {
pr_info("%s not found new_dev in afc_condition_checklist\n",
__func__);
break;
}
pr_err("%s afc_data->name %s\n",
__func__, afc_data->afc_name);
if (afc_data->next == afc_data)
flag_next = false;
if (!(muic_check_hv_irq(muic_data, afc_data, irq)))
continue;
if (!(muic_check_status_vbadc(afc_data, vbadc)))
continue;
if (!(muic_check_status_vdnmon(afc_data, vdnmon)))
continue;
pr_err("%s checklist match found at i(%d), %s(%d)\n",
__func__, i, afc_data->afc_name,
afc_data->new_dev);
intr = MUIC_INTR_ATTACH;
break;
}
if (intr == MUIC_INTR_ATTACH) {
pr_err("%s AFC ATTACHED %d->%d\n", __func__,
muic_pdata->attached_dev, afc_data->new_dev);
#ifdef CONFIG_MUIC_MANAGER
muic_manager_set_legacy_dev(muic_if, afc_data->new_dev);
#endif
ret = s2mu004_hv_muic_handle_attach(muic_data, afc_data);
if (ret)
pr_err("%s cannot handle attach(%d)\n",
__func__, ret);
} else {
pr_info("%s AFC MAINTAIN (%d)\n", __func__,
muic_pdata->attached_dev);
ret = s2mu004_hv_muic_state_maintain(muic_data);
if (ret)
pr_err("%s cannot maintain state(%d)\n",
__func__, ret);
goto out;
}
out:
return;
}
#define MASK(width, shift) (((0x1 << (width)) - 1) << shift)
#define CHGIN_STATUS_SHIFT 5
#define CHGIN_STATUS_WIDTH 3
#define CHGIN_STATUS_MASK MASK(CHGIN_STATUS_WIDTH, CHGIN_STATUS_SHIFT)
void s2mu004_muic_afc_check_vbadc(struct work_struct *work)
{
struct s2mu004_muic_data *muic_data =
container_of(work, struct s2mu004_muic_data, afc_check_vbadc.work);
int ret;
u8 val_vbadc, chg_sts0;
struct muic_platform_data *muic_pdata = muic_data->pdata;
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);
ret = s2mu004_read_reg(muic_data->i2c, 0X0A, &chg_sts0);
if (ret)
pr_err("%s fail to read muic chg reg(%d)\n", __func__, ret);
chg_sts0 = chg_sts0 & CHGIN_STATUS_MASK;
val_vbadc = val_vbadc & STATUS_VBADC_MASK;
pr_err("%s(%d) vbadc : %d, attached_dev : %d, chg in: %02x\n", __func__, __LINE__,
val_vbadc, muic_pdata->attached_dev, chg_sts0);
if ((muic_pdata->attached_dev != ATTACHED_DEV_AFC_CHARGER_9V_MUIC)
&& (!(chg_sts0 == 0x0))
&& (val_vbadc == VBADC_8_7V_9_3V)) {
mutex_lock(&muic_data->muic_mutex);
s2mu004_hv_muic_detect_dev(muic_data, muic_data->irq_vbadc);
mutex_unlock(&muic_data->muic_mutex);
}
}
void s2mu004_muic_afc_cable_type_work(struct work_struct *work)
{
struct s2mu004_muic_data *muic_data =
container_of(work, struct s2mu004_muic_data, afc_cable_type_work.work);
int ret;
u8 val_vbadc, chg_sts0;
struct muic_platform_data *muic_pdata = muic_data->pdata;
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);
ret = s2mu004_read_reg(muic_data->i2c, 0X0A, &chg_sts0);
if (ret)
pr_err("%s fail to read muic chg reg(%d)\n", __func__, ret);
chg_sts0 = chg_sts0 & CHGIN_STATUS_MASK;
val_vbadc = val_vbadc & STATUS_VBADC_MASK;
pr_err("%s(%d) vbadc : %d, attached_dev : %d, chg in: %02x\n", __func__, __LINE__,
val_vbadc, muic_pdata->attached_dev, chg_sts0);
if (muic_pdata->attached_dev != ATTACHED_DEV_NONE_MUIC
&& (val_vbadc != VBADC_8_7V_9_3V)
&& (!(chg_sts0 == 0x0))) {
mutex_lock(&muic_data->muic_mutex);
MUIC_SEND_NOTI_ATTACH(ATTACHED_DEV_AFC_CHARGER_5V_MUIC);
mutex_unlock(&muic_data->muic_mutex);
}
}
void s2mu004_muic_afc_send_mpnack(struct work_struct *work)
{
struct s2mu004_muic_data *muic_data =
container_of(work, struct s2mu004_muic_data, afc_send_mpnack.work);
pr_info("%s\n ", __func__);
mutex_lock(&muic_data->afc_mutex);
s2mu004_hv_muic_detect_dev(muic_data, muic_data->irq_mpnack);
mutex_unlock(&muic_data->afc_mutex);
}
/* TA setting in s2mu004-muic.c */
void s2mu004_muic_prepare_afc_charger(struct work_struct *work)
{
struct s2mu004_muic_data *muic_data =
container_of(work, struct s2mu004_muic_data, prepare_afc_charger.work);
struct i2c_client *i2c = muic_data->i2c;
u8 reg_val = 0, vdnmon = 0;
int i = 0;
if (muic_data->is_handling_afc)
return;
pr_info("%s\n", __func__);
muic_data->is_handling_afc = true;
reg_val = (HVCONTROL1_AFCEN_MASK | HVCONTROL1_VBUSADCEN_MASK);
s2mu004_hv_muic_write_reg(i2c, S2MU004_REG_AFC_CTRL1, reg_val);
/* Set TX DATA */
muic_data->tx_data = (HVTXBYTE_9V << 4) | HVTXBYTE_1_65A;
s2mu004_hv_muic_write_reg(i2c, S2MU004_REG_TX_BYTE1, muic_data->tx_data);
reg_val = (HVCONTROL1_AFCEN_MASK | HVCONTROL1_VBUSADCEN_MASK |
HVCONTROL1_DPDNVDEN_MASK);
s2mu004_hv_muic_write_reg(i2c, S2MU004_REG_AFC_CTRL1, reg_val);
s2mu004_hv_muic_write_reg(i2c,S2MU004_REG_AFC_CTRL2, HVCONTROL2_DNRESEN_MASK);
for (i = 0; i < 10; i++) {
msleep(130);
if(!s2mu004_muic_get_vbus_state(muic_data))
return;
s2mu004_read_reg(muic_data->i2c, S2MU004_REG_AFC_STATUS, &vdnmon);
pr_info("%s vdnmon(%#x)\n", __func__, vdnmon);
if ((vdnmon & STATUS_VDNMON_MASK) == VDNMON_LOW) {
s2mu004_muic_set_afc_ready(muic_data, true);
s2mu004_hv_muic_write_reg(i2c,S2MU004_REG_AFC_CTRL2, HVCONTROL2_DP06EN_MASK);
return;
}
}
/* To check pre-condition for xiaomi battery pack */
s2mu004_muic_bcd_rescan(muic_data);
for (i = 0; i < 5; i++) {
msleep(100);
if(!s2mu004_muic_get_vbus_state(muic_data))
return;
s2mu004_read_reg(muic_data->i2c, S2MU004_REG_AFC_STATUS, &vdnmon);
pr_info("%s vdnmon(%#x)\n", __func__, vdnmon);
if ((vdnmon & STATUS_VDNMON_MASK) == VDNMON_LOW) {
s2mu004_muic_set_afc_ready(muic_data, true);
s2mu004_hv_muic_write_reg(i2c,S2MU004_REG_AFC_CTRL2, HVCONTROL2_DP06EN_MASK);
return;
}
}
}
/* TA setting in s2mu004-muic.c */
bool s2mu004_muic_check_change_dev_afc_charger(struct s2mu004_muic_data *muic_data,
muic_attached_dev_t new_dev)
{
bool ret = true;
if (new_dev == ATTACHED_DEV_TA_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC ||
new_dev == ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC){
if (muic_check_dev_ta(muic_data))
ret = false;
}
return ret;
}
static void s2mu004_hv_muic_detect_after_charger_init(struct work_struct *work)
{
struct afc_init_data_s *init_data =
container_of(work, struct afc_init_data_s, muic_afc_init_work);
struct s2mu004_muic_data *muic_data = init_data->muic_data;
int ret;
u8 status3;
pr_info("%s\n", __func__);
mutex_lock(&muic_data->muic_mutex);
/* check vdnmon status value */
ret = s2mu004_read_reg(muic_data->i2c, S2MU004_REG_AFC_STATUS, &status3);
if (ret) {
pr_err("%s fail to read muic reg(%d)\n",
__func__, ret);
return;
}
pr_info("%s STATUS3:0x%02x\n", __func__, status3);
if (muic_data->is_afc_muic_ready) {
if (muic_data->is_afc_muic_prepare)
s2mu004_hv_muic_detect_dev(muic_data, -1);
}
mutex_unlock(&muic_data->muic_mutex);
}
#ifdef CONFIG_HV_MUIC_VOLTAGE_CTRL
void hv_muic_change_afc_voltage(int tx_data)
{
struct i2c_client *i2c = afc_init_data.muic_data->i2c;
struct s2mu004_muic_data *muic_data = afc_init_data.muic_data;
u8 value;
struct muic_platform_data *muic_pdata = muic_data->pdata;
pr_info("%s %x\n", __func__, tx_data);
/* QC */
if (muic_pdata->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) {
switch (tx_data) {
case MUIC_HV_5V:
s2mu004_hv_muic_write_reg(i2c, 0x49, 0xa1);
break;
case MUIC_HV_9V:
s2mu004_hv_muic_write_reg(i2c, 0x49, 0xbd);
break;
default:
break;
}
} else { /* AFC */
muic_data->afc_count = 0;
s2mu004_read_reg(i2c, S2MU004_REG_TX_BYTE1, &value);
if (value == tx_data) {
pr_info("%s: same to current voltage %x\n", __func__, value);
return;
}
s2mu004_write_reg(i2c, S2MU004_REG_TX_BYTE1, tx_data);
s2mu004_hv_muic_afc_control_ping(muic_data, true);
}
}
int muic_afc_set_voltage(int vol)
{
if (vol == 5) {
hv_muic_change_afc_voltage(MUIC_HV_5V);
} else if (vol == 9) {
hv_muic_change_afc_voltage(MUIC_HV_9V);
} else {
pr_warn("%s invalid value\n", __func__);
return 0;
}
return 1;
}
#endif /* CONFIG_HV_MUIC_VOLTAGE_CTRL */
void s2mu004_hv_muic_charger_init(void)
{
pr_info("%s\n", __func__);
if (afc_init_data.muic_data) {
afc_init_data.muic_data->is_charger_ready = true;
schedule_work(&afc_init_data.muic_afc_init_work);
}
}
void s2mu004_hv_muic_init_detect(struct s2mu004_muic_data *muic_data)
{
pr_info("%s\n", __func__);
/* TBD */
#if 0
mutex_lock(&muic_data->muic_mutex);
mutex_unlock(&muic_data->muic_mutex);
#endif
}
static irqreturn_t s2mu004_muic_hv_irq(int irq, void *data)
{
struct s2mu004_muic_data *muic_data = data;
int ret;
u8 val_vbadc;
mutex_lock(&muic_data->muic_mutex);
if (muic_data->is_afc_muic_ready == false)
pr_info("%s not ready yet(afc_muic_ready[%c])\n",
__func__, (muic_data->is_afc_muic_ready ? 'T' : 'F'));
else if (muic_data->is_charger_ready == false && irq != muic_data->irq_vdnmon)
pr_info("%s not ready yet(charger_ready[%c])\n",
__func__, (muic_data->is_charger_ready ? 'T' : 'F'));
else if (muic_data->pdata->afc_disable)
pr_info("%s AFC disable by USER (afc_disable[%c]\n",
__func__, (muic_data->pdata->afc_disable ? 'T' : 'F'));
else {
muic_data->afc_irq = irq;
/* Re-check vbadc voltage, if vbadc 9v interrupt does not occur when send pings. */
if (irq == muic_data->irq_vbadc && muic_data->afc_count >= 2) {
pr_err("%s: VbADC interrupt after sending master ping x3\n", __func__);
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);
val_vbadc = val_vbadc & STATUS_VBADC_MASK;
if (val_vbadc != VBADC_8_7V_9_3V) {
pr_err("%s: vbadc: %d\n", __func__, val_vbadc);
schedule_delayed_work(&muic_data->afc_check_vbadc, msecs_to_jiffies(100));
}
}
/* After ping , if there is response then cancle mpnack work */
if ((irq == muic_data->irq_mrxrdy) || (irq == muic_data->irq_mpnack) ||
(irq == muic_data->irq_mrxtrf)) {
cancel_delayed_work(&muic_data->afc_send_mpnack);
cancel_delayed_work(&muic_data->afc_control_ping_retry);
if (irq == muic_data->irq_mrxtrf)
muic_data->is_mrxtrf_in = true;
}
if ((irq == muic_data->irq_vbadc) && (muic_data->qc_prepare == 1)) {
muic_data->qc_prepare = 0;
cancel_delayed_work(&muic_data->afc_qc_retry);
}
s2mu004_hv_muic_detect_dev(muic_data, muic_data->afc_irq);
}
mutex_unlock(&muic_data->muic_mutex);
return IRQ_HANDLED;
}
#define REQUEST_HV_IRQ(_irq, _dev_id, _name) \
do { \
ret = request_threaded_irq(_irq, NULL, s2mu004_muic_hv_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)
int s2mu004_afc_muic_irq_init(struct s2mu004_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 AFC MUIC IRQ */
muic_data->irq_vdnmon = irq_base + S2MU004_AFC_IRQ_VDNMon;
REQUEST_HV_IRQ(muic_data->irq_vdnmon, muic_data, "muic-vdnmon");
muic_data->irq_mrxrdy = irq_base + S2MU004_AFC_IRQ_MRxRdy;
REQUEST_HV_IRQ(muic_data->irq_mrxrdy, muic_data, "muic-mrxrdy");
muic_data->irq_vbadc = irq_base + S2MU004_AFC_IRQ_VbADC;
REQUEST_HV_IRQ(muic_data->irq_vbadc, muic_data, "muic-vbadc");
muic_data->irq_mpnack = irq_base + S2MU004_AFC_IRQ_MPNack;
REQUEST_HV_IRQ(muic_data->irq_mpnack, muic_data, "muic-mpnack");
muic_data->irq_mrxtrf = irq_base + S2MU004_AFC_IRQ_MRxTrf;
REQUEST_HV_IRQ(muic_data->irq_mrxtrf, muic_data, "muic-mrxtrf");
pr_info("mrxrdy(%d), mpnack(%d), vbadc(%d), vdnmon(%d) mrxtrf(%d)\n",
muic_data->irq_dnres, muic_data->irq_mrxrdy,
muic_data->irq_vbadc, muic_data->irq_vdnmon,
muic_data->irq_mrxtrf);
}
return ret;
}
#define FREE_HV_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)
void s2mu004_hv_muic_free_irqs(struct s2mu004_muic_data *muic_data)
{
pr_info("%s\n", __func__);
/* free MUIC IRQ */
FREE_HV_IRQ(muic_data->irq_vdnmon, muic_data, "muic-vdnmon");
FREE_HV_IRQ(muic_data->irq_mrxrdy, muic_data, "muic-mrxrdy");
FREE_HV_IRQ(muic_data->irq_mpnack, muic_data, "muic-mpnack");
FREE_HV_IRQ(muic_data->irq_vbadc, muic_data, "muic-vbadc");
FREE_HV_IRQ(muic_data->irq_mrxtrf, muic_data, "muic-mrxtrf");
}
void s2mu004_hv_muic_initialize(struct s2mu004_muic_data *muic_data)
{
pr_info("%s\n", __func__);
muic_data->is_afc_handshaking = false;
muic_data->is_afc_muic_prepare = false;
muic_data->is_charger_ready = true;
muic_data->pdata->afc_disable = false;
muic_data->is_afcblock_ready = false;
muic_data->is_handling_afc = false;
muic_data->is_mrxtrf_in = false;
s2mu004_write_reg(muic_data->i2c, 0xd8, 0x84); /* OTP */
s2mu004_write_reg(muic_data->i2c, 0x2c, 0x55); /* OTP */
s2mu004_write_reg(muic_data->i2c, 0xc3, 0x88); /* OTP */
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4b, 0x00);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x49, 0x00);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x4a, 0x00);
s2mu004_hv_muic_write_reg(muic_data->i2c, 0x5f, 0x01);
s2mu004_mrxtrf_irq_mask(muic_data, 0);
afc_init_data.muic_data = muic_data;
INIT_WORK(&afc_init_data.muic_afc_init_work, s2mu004_hv_muic_detect_after_charger_init);
INIT_DELAYED_WORK(&muic_data->afc_check_vbadc, s2mu004_muic_afc_check_vbadc);
INIT_DELAYED_WORK(&muic_data->afc_cable_type_work, s2mu004_muic_afc_cable_type_work);
INIT_DELAYED_WORK(&muic_data->afc_send_mpnack, s2mu004_muic_afc_send_mpnack);
INIT_DELAYED_WORK(&muic_data->afc_control_ping_retry, s2mu004_muic_afc_control_ping_retry);
INIT_DELAYED_WORK(&muic_data->afc_qc_retry, s2mu004_muic_afc_qc_retry);
INIT_DELAYED_WORK(&muic_data->afc_after_prepare, s2mu004_muic_afc_after_prepare);
INIT_DELAYED_WORK(&muic_data->prepare_afc_charger, s2mu004_muic_prepare_afc_charger);
mutex_init(&muic_data->afc_mutex);
}
void s2mu004_hv_muic_remove(struct s2mu004_muic_data *muic_data)
{
pr_info("%s\n", __func__);
/* Set digital IVR when power off under revision EVT3 */
if (muic_data->ic_rev_id < 3)
s2mu004_muic_hv_update_reg(muic_data->i2c, 0xB3, 0x00, 0x08, 0);
cancel_work_sync(&afc_init_data.muic_afc_init_work);
s2mu004_hv_muic_free_irqs(muic_data);
/* Afc reset */
s2mu004_muic_hv_update_reg(muic_data->i2c, 0x5f, 0x80, 0x80, 0);
}
#if 0
void s2mu004_hv_muic_remove_wo_free_irq(struct s2mu004_muic_data *muic_data)
{
pr_info("%s\n", __func__);
cancel_work_sync(&afc_init_data.muic_afc_init_work);
}
#endif