/* * 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 #include #include #include #include #include #include #include #include #include #include #include /* MUIC header file */ #include #include #include #ifdef CONFIG_MUIC_MANAGER #include #endif #if defined(CONFIG_MUIC_NOTIFIER) #include #endif /* CONFIG_MUIC_NOTIFIER */ #include 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, ®_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