/* * max77775.c - mfd core driver for the Maxim 77775 * * Copyright (C) 2016 Samsung Electronics * Insun Choi * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * This driver is based on max8997.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_HV_MUIC_MAX77775_AFC) #include #endif #define FW_BIN_NAME "max77775-fw.bin" #define EXTRA_FW_BIN_NAME "max77775-extra-fw.bin" #if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) #include #endif #if defined(CONFIG_OF) #include #include #endif /* CONFIG_OF */ #if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) #include #endif #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) #include #endif #if IS_ENABLED(CONFIG_SEC_MPARAM) || (IS_MODULE(CONFIG_SEC_PARAM) && defined(CONFIG_ARCH_EXYNOS)) extern int factory_mode; #else static int __read_mostly factory_mode; module_param(factory_mode, int, 0444); #endif #define I2C_ADDR_PMIC (0xCC >> 1) #define I2C_ADDR_MUIC (0x4A >> 1) #define I2C_ADDR_CHG (0xD2 >> 1) #define I2C_ADDR_FG (0x6C >> 1) #define I2C_ADDR_TESTSID (0xC4 >> 1) #define I2C_RETRY_CNT 3 #define MD75_FIRMWARE_TIMEOUT_SEC 5 #define MD75_FIRMWARE_TIMEOUT_START 1 #define MD75_FIRMWARE_TIMEOUT_PASS 2 #define MD75_FIRMWARE_TIMEOUT_FAIL 3 #define MD75_FIRMWARE_TIMEOUT_COMPLETE 4 /* * pmic revision information */ struct max77775_revision_struct { u8 id; u8 rev; u8 logical_id; }; static struct max77775_revision_struct max77775_revision[] = { { 0x75, 0x01, MAX77775_PASS1}, /* MD75 PASS1 */ { 0x75, 0x02, MAX77775_PASS2}, /* MD75 PASS2 */ { 0x75, 0x03, MAX77775_PASS3}, /* MD75 PASS3 */ { 0x75, 0x04, MAX77775_PASS4}, /* MD75 PASS4 */ }; static struct mfd_cell max77775_devs[] = { #if IS_ENABLED(CONFIG_CCIC_MAX77775) { .name = "max77775-usbc", }, #endif #if IS_ENABLED(CONFIG_FUELGAUGE_MAX77775) { .name = "max77775-fuelgauge", }, #endif #if IS_ENABLED(CONFIG_CHARGER_MAX77775) { .name = "max77775-charger", }, #endif }; static int max77775_firmware_timeout_state; static int firmware_timeout_count; static struct platform_device *pdev; static int max77775_get_facmode(void) { return factory_mode; } int max77775_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int ret, i; if (max77775->shutdown) { md75_err_usb("%s:%s shutdown. i2c command is skiped\n", MFD_DEV_NAME, __func__); return 0; } mutex_lock(&max77775->i2c_lock); for (i = 0; i < I2C_RETRY_CNT; ++i) { ret = i2c_smbus_read_byte_data(i2c, reg); if (ret >= 0) break; md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); } mutex_unlock(&max77775->i2c_lock); if (ret < 0) { md75_info_usb("%s:%s reg(0x%x), ret(%d)\n", MFD_DEV_NAME, __func__, reg, ret); #if defined(CONFIG_USB_HW_PARAM) if (o_notify) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); #endif #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); #else sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); #endif #endif return ret; } ret &= 0xff; *dest = ret; return 0; } EXPORT_SYMBOL_GPL(max77775_read_reg); int max77775_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int ret, i; if (max77775->shutdown) { md75_err_usb("%s:%s shutdown. i2c command is skiped\n", MFD_DEV_NAME, __func__); return 0; } mutex_lock(&max77775->i2c_lock); for (i = 0; i < I2C_RETRY_CNT; ++i) { ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); if (ret >= 0) break; md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); } mutex_unlock(&max77775->i2c_lock); if (ret < 0) { #if defined(CONFIG_USB_HW_PARAM) if (o_notify) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); #endif #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); #else sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); #endif #endif return ret; } return 0; } EXPORT_SYMBOL_GPL(max77775_bulk_read); int max77775_read_word(struct i2c_client *i2c, u8 reg) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int ret, i; if (max77775->shutdown) { md75_err_usb("%s:%s shutdown. i2c command is skiped\n", MFD_DEV_NAME, __func__); return 0; } mutex_lock(&max77775->i2c_lock); for (i = 0; i < I2C_RETRY_CNT; ++i) { ret = i2c_smbus_read_word_data(i2c, reg); if (ret >= 0) break; md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); } mutex_unlock(&max77775->i2c_lock); #if defined(CONFIG_USB_HW_PARAM) if (ret < 0) { if (o_notify) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); } #endif #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) if (ret < 0) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); #else sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); #endif #endif return ret; } EXPORT_SYMBOL_GPL(max77775_read_word); int max77775_write_reg(struct i2c_client *i2c, u8 reg, u8 value) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int ret = -EIO, i; int timeout = 2000; /* 2sec */ int interval = 100; if (max77775->shutdown) { md75_err_usb("%s:%s shutdown. i2c command is skiped\n", MFD_DEV_NAME, __func__); return 0; } while (ret == -EIO) { mutex_lock(&max77775->i2c_lock); for (i = 0; i < I2C_RETRY_CNT; ++i) { ret = i2c_smbus_write_byte_data(i2c, reg, value); if ((ret >= 0) || (ret == -EIO)) break; md75_info_usb("%s:%s reg(0x%02x), ret(%d), i2c_retry_cnt(%d/%d)\n", MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); } mutex_unlock(&max77775->i2c_lock); if (ret < 0) { md75_info_usb("%s:%s reg(0x%x), ret(%d), timeout %d\n", MFD_DEV_NAME, __func__, reg, ret, timeout); if (timeout < 0) break; msleep(interval); timeout -= interval; } } #if defined(CONFIG_USB_HW_PARAM) if (o_notify && ret < 0) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); #endif #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) if (ret < 0) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); #else sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); #endif #endif return ret; } EXPORT_SYMBOL_GPL(max77775_write_reg); int max77775_write_reg_nolock(struct i2c_client *i2c, u8 reg, u8 value) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int ret = -EIO; int timeout = 2000; /* 2sec */ int interval = 100; if (max77775->shutdown) { md75_err_usb("%s:%s shutdown. i2c command is skiped\n", MFD_DEV_NAME, __func__); return 0; } while (ret == -EIO) { ret = i2c_smbus_write_byte_data(i2c, reg, value); if (ret < 0) { md75_info_usb("%s:%s reg(0x%x), ret(%d), timeout %d\n", MFD_DEV_NAME, __func__, reg, ret, timeout); if (timeout < 0) break; msleep(interval); timeout -= interval; } } #if defined(CONFIG_USB_HW_PARAM) if (o_notify && ret < 0) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); #endif #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) if (ret < 0) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); #else sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); #endif #endif return ret; } EXPORT_SYMBOL_GPL(max77775_write_reg_nolock); int max77775_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int ret = -EIO, i; int timeout = 2000; /* 2sec */ int interval = 100; if (max77775->shutdown) { md75_err_usb("%s:%s shutdown. i2c command is skiped\n", MFD_DEV_NAME, __func__); return 0; } while (ret == -EIO) { mutex_lock(&max77775->i2c_lock); for (i = 0; i < I2C_RETRY_CNT; ++i) { ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); if ((ret >= 0) || (ret == -EIO)) break; md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); } mutex_unlock(&max77775->i2c_lock); if (ret < 0) { md75_info_usb("%s:%s reg(0x%x), ret(%d), timeout %d\n", MFD_DEV_NAME, __func__, reg, ret, timeout); if (timeout < 0) break; msleep(interval); timeout -= interval; } } #if defined(CONFIG_USB_HW_PARAM) if (o_notify && ret < 0) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); #endif #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) if (ret < 0) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); #else sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); #endif #endif return ret; } EXPORT_SYMBOL_GPL(max77775_bulk_write); int max77775_write_word(struct i2c_client *i2c, u8 reg, u16 value) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int ret, i; if (max77775->shutdown) { md75_err_usb("%s:%s shutdown. i2c command is skiped\n", MFD_DEV_NAME, __func__); return 0; } mutex_lock(&max77775->i2c_lock); for (i = 0; i < I2C_RETRY_CNT; ++i) { ret = i2c_smbus_write_word_data(i2c, reg, value); if (ret >= 0) break; md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); } mutex_unlock(&max77775->i2c_lock); if (ret < 0) { #if defined(CONFIG_USB_HW_PARAM) if (o_notify) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); #endif #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); #else sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); #endif #endif return ret; } return 0; } EXPORT_SYMBOL_GPL(max77775_write_word); int max77775_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int ret, i; u8 old_val, new_val; if (max77775->shutdown) { md75_err_usb("%s:%s shutdown. i2c command is skiped\n", MFD_DEV_NAME, __func__); return 0; } mutex_lock(&max77775->i2c_lock); for (i = 0; i < I2C_RETRY_CNT; ++i) { ret = i2c_smbus_read_byte_data(i2c, reg); if (ret >= 0) break; md75_info_usb("%s:%s read reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); } if (ret < 0) { #if defined(CONFIG_USB_HW_PARAM) if (o_notify) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); #endif goto err; } if (ret >= 0) { old_val = ret & 0xff; new_val = (val & mask) | (old_val & (~mask)); for (i = 0; i < I2C_RETRY_CNT; ++i) { ret = i2c_smbus_write_byte_data(i2c, reg, new_val); if (ret >= 0) break; md75_info_usb("%s:%s write reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); } if (ret < 0) { #if defined(CONFIG_USB_HW_PARAM) if (o_notify) inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); #endif goto err; } } err: mutex_unlock(&max77775->i2c_lock); #if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) if (ret < 0) #if IS_ENABLED(CONFIG_SEC_FACTORY) sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); #else sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); #endif #endif return ret; } EXPORT_SYMBOL_GPL(max77775_update_reg); #if defined(CONFIG_OF) static int of_max77775_dt(struct device *dev, struct max77775_platform_data *pdata) { struct device_node *np_max77775 = dev->of_node; struct device_node *np_battery; #if defined(CONFIG_SEC_FACTORY) struct device_node *np_fuelgauge; #endif int ret, val; if (!np_max77775) return -EINVAL; pdata->irq_gpio = of_get_named_gpio(np_max77775, "max77775,irq-gpio", 0); if (of_property_read_u32(np_max77775, "max77775,rev", &pdata->rev)) pdata->rev = 0; if (of_property_read_u32(np_max77775, "max77775,fw_product_id", &pdata->fw_product_id)) pdata->fw_product_id = 0; #if defined(CONFIG_SEC_FACTORY) pdata->blocking_waterevent = 0; #else pdata->blocking_waterevent = of_property_read_bool(np_max77775, "max77775,blocking_waterevent"); #endif ret = of_property_read_u32(np_max77775, "max77775,extra_fw_enable", &val); if (ret) pdata->extra_fw_enable = 0; else pdata->extra_fw_enable = val; np_battery = of_find_node_by_name(NULL, "mfc-charger"); if (!np_battery) { md75_info_usb("%s: np_battery NULL\n", __func__); } else { pdata->wpc_en = of_get_named_gpio(np_battery, "battery,wpc_en", 0); if (pdata->wpc_en < 0) { md75_info_usb("%s: can't get wpc_en (%d)\n", __func__, pdata->wpc_en); pdata->wpc_en = 0; } ret = of_property_read_string(np_battery, "battery,wireless_charger_name", (char const **)&pdata->wireless_charger_name); if (ret) md75_info_usb("%s: Wireless charger name is Empty\n", __func__); } pdata->support_audio = of_property_read_bool(np_max77775, "max77775,support-audio"); #if defined(CONFIG_SEC_FACTORY) np_fuelgauge = of_find_node_by_name(NULL, "max77775-fuelgauge"); if (!np_fuelgauge) { md75_info_usb("%s: np_fuelgauge NULL\n", __func__); pdata->fg_resistor = 1; } else { ret = of_property_read_u32(np_fuelgauge, "fuelgauge,fg_resistor", &pdata->fg_resistor); if (ret) { pr_err("%s: error reading fg_resistor %d\n", __func__, ret); pdata->fg_resistor = 1; } } #endif return 0; } #endif /* CONFIG_OF */ static void max77775_print_pdata_property(struct max77775_platform_data *pdata) { md75_info_usb("%s: irq-gpio: %u\n", __func__, pdata->irq_gpio); md75_info_usb("%s: extra_fw_enable: %d\n", __func__, pdata->extra_fw_enable); md75_info_usb("%s: support_audio %d\n", __func__, pdata->support_audio); } /* samsung */ #if IS_ENABLED(CONFIG_CCIC_MAX77775) static void max77775_reset_ic(struct max77775_dev *max77775) { md75_info_usb("%s: Reset!!\n", __func__); max77775_write_reg(max77775->muic, MAX77775_USBC_REG_UIC_SWRST, 0x0F); msleep(150); } static void max77775_usbc_wait_response_q(struct work_struct *work) { struct max77775_dev *max77775; u8 read_value = 0x00; u8 dummy[2] = { 0, }; max77775 = container_of(work, struct max77775_dev, fw_work); while (max77775->fw_update_state == FW_UPDATE_WAIT_RESP_START) { max77775_bulk_read(max77775->muic, REG_UIC_INT, 1, dummy); read_value = dummy[0]; if ((read_value & BIT_APCmdResI) == BIT_APCmdResI) break; } complete_all(&max77775->fw_completion); } static int max77775_usbc_wait_response(struct max77775_dev *max77775) { unsigned long time_remaining = 0; max77775->fw_update_state = FW_UPDATE_WAIT_RESP_START; init_completion(&max77775->fw_completion); queue_work(max77775->fw_workqueue, &max77775->fw_work); time_remaining = wait_for_completion_timeout( &max77775->fw_completion, msecs_to_jiffies(FW_WAIT_TIMEOUT)); max77775->fw_update_state = FW_UPDATE_WAIT_RESP_STOP; if (!time_remaining) { md75_info_usb("%s: Failed to update due to timeout\n", __func__); cancel_work_sync(&max77775->fw_work); return FW_UPDATE_TIMEOUT_FAIL; } return 0; } static int __max77775_usbc_fw_update( struct max77775_dev *max77775, const u8 *fw_bin) { u8 fw_cmd = FW_CMD_END; u8 fw_len = 0; u8 fw_opcode = 0; u8 fw_data_len = 0; u8 fw_data[FW_CMD_WRITE_SIZE] = { 0, }; u8 verify_data[FW_VERIFY_DATA_SIZE] = { 0, }; int ret = -FW_UPDATE_CMD_FAIL; /* * fw_bin[0] = Write Command (0x01) * or * fw_bin[0] = Read Command (0x03) * or * fw_bin[0] = End Command (0x00) */ fw_cmd = fw_bin[0]; /* * Check FW Command */ if (fw_cmd == FW_CMD_END) { max77775_reset_ic(max77775); max77775->fw_update_state = FW_UPDATE_END; return FW_UPDATE_END; } /* * fw_bin[1] = Length ( OPCode + Data ) */ fw_len = fw_bin[1]; /* * Check fw data length * We support 0x22 or 0x04 only */ if (fw_len != 0x22 && fw_len != 0x04) return FW_UPDATE_MAX_LENGTH_FAIL; /* * fw_bin[2] = OPCode */ fw_opcode = fw_bin[2]; /* * In case write command, * fw_bin[35:3] = Data * * In case read command, * fw_bin[5:3] = Data */ fw_data_len = fw_len - 1; /* exclude opcode */ memcpy(fw_data, &fw_bin[3], fw_data_len); switch (fw_cmd) { case FW_CMD_WRITE: if (fw_data_len > I2C_SMBUS_BLOCK_MAX) { /* write the half data */ max77775_bulk_write(max77775->muic, fw_opcode, I2C_SMBUS_BLOCK_HALF, fw_data); max77775_bulk_write(max77775->muic, fw_opcode + I2C_SMBUS_BLOCK_HALF, fw_data_len - I2C_SMBUS_BLOCK_HALF, &fw_data[I2C_SMBUS_BLOCK_HALF]); } else max77775_bulk_write(max77775->muic, fw_opcode, fw_data_len, fw_data); ret = max77775_usbc_wait_response(max77775); if (ret) return ret; /* * Why do we need 1ms sleep in case MQ81? */ /* msleep(1); */ return FW_CMD_WRITE_SIZE; case FW_CMD_READ: max77775_bulk_read(max77775->muic, fw_opcode, fw_data_len, verify_data); /* * Check fw data sequence number * It should be increased from 1 step by step. */ if (memcmp(verify_data, &fw_data[1], 2)) { md75_info_usb("%s: [0x%02x 0x%02x], [0x%02x, 0x%02x], [0x%02x, 0x%02x]\n", __func__, verify_data[0], fw_data[0], verify_data[1], fw_data[1], verify_data[2], fw_data[2]); if (verify_data[0] == 0xF5 && verify_data[1] == 0x10) return FW_UPDATE_ERR_CODE_FAIL; else return FW_UPDATE_VERIFY_FAIL; } return FW_CMD_READ_SIZE; } md75_info_usb("%s: Command error\n", __func__); return ret; } int max77775_write_fw_noautoibus(struct max77775_dev *max77775) { u8 write_values[OPCODE_MAX_LENGTH] = { 0, }; int ret = 0; int length = 0x1; int i = 0; write_values[0] = OPCODE_SAMSUNG_FW_AUTOIBUS; write_values[1] = 0x3; /* usbc fw off & auto off(manual on) */ for (i = 0; i < length + OPCODE_SIZE; i++) md75_info_usb("%s: [%d], 0x[%x]\n", __func__, i, write_values[i]); /* Write opcode and data */ ret = max77775_bulk_write(max77775->muic, OPCODE_WRITE, length + OPCODE_SIZE, write_values); /* Write end of data by 0x00 */ if (length < OPCODE_DATA_LENGTH) max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); return 0; } static int max77775_fuelgauge_read_vcell(struct max77775_dev *max77775) { u8 data[2]; u32 vcell; u16 w_data; u32 temp; u32 temp2; if (max77775_bulk_read(max77775->fuelgauge, MAX77775_FG_REG_VCELL, 2, data) < 0) { md75_err_usb("%s: Failed to read VCELL\n", __func__); return -1; } w_data = (data[1] << 8) | data[0]; temp = (w_data & 0xFFF) * 78125; vcell = temp / 1000000; temp = ((w_data & 0xF000) >> 4) * 78125; temp2 = temp / 1000000; vcell += (temp2 << 4); return vcell; } static int max77775_fuelgauge_read_vbyp(struct max77775_dev *max77775) { u8 data[2]; u32 vbyp, temp; u16 w_data; if (max77775_bulk_read(max77775->fuelgauge, MAX77775_FG_REG_VBYP, 2, data) < 0) { pr_err("%s: Failed to read MAX77775_FG_REG_VBYP\n", __func__); return -1; } w_data = (data[1] << 8) | data[0]; temp = (w_data & 0xFFF) * 427246; vbyp = temp / 1000000; temp = ((w_data & 0xF000) >> 4) * 427246; temp /= 1000000; vbyp += (temp << 4); return vbyp; } #if defined(CONFIG_SEC_FACTORY) static int max77775_fuelgauge_read_current_ma(struct max77775_dev *max77775) { u8 data1[2]; u32 temp, sign; s32 i_current; if (max77775_bulk_read(max77775->fuelgauge, MAX77775_FG_REG_CURRENT, 2, data1) < 0) { pr_err("%s: Failed to read MAX77775_FG_REG_CURRENT\n", __func__); return -1; } temp = ((data1[1] << 8) | data1[0]) & 0xFFFF; /* Debug log for abnormal current case */ if (temp & (0x1 << 15)) { sign = 1; temp = (~temp & 0xFFFF) + 1; } else { sign = 0; } i_current = temp * 15625 * (max77775->pdata->fg_resistor) / 100000; if (sign) i_current *= -1; return i_current; } #endif static void max77775_wc_control(struct max77775_dev *max77775, bool enable) { #if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) union power_supply_propval value = {0, }; char wpc_en_status[2]; int ret = 0; wpc_en_status[0] = WPC_EN_CCIC; wpc_en_status[1] = enable ? true : false; value.strval = wpc_en_status; ret = psy_do_property(max77775->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_WPC_EN, value); if (ret < 0) { if (max77775->pdata->wpc_en) { if (enable) { gpio_direction_output(max77775->pdata->wpc_en, 0); md75_info_usb("%s: WC CONTROL: ENABLE\n", __func__); } else { gpio_direction_output(max77775->pdata->wpc_en, 1); md75_info_usb("%s: WC CONTROL: DISABLE\n", __func__); } } else { md75_info_usb("%s : no wpc_en\n", __func__); } } else { md75_info_usb("%s: WC CONTROL: %s\n", __func__, wpc_en_status[1] ? "Enable" : "Disable"); } md75_info_usb("%s: wpc_en(%d)\n", __func__, gpio_get_value(max77775->pdata->wpc_en)); #endif } #if defined(CONFIG_SEC_FACTORY) bool max77775_is_factory(struct max77775_dev *max77775) { bool is_factory = false; u8 chgin_dtls; u8 fctid = 7; u8 uidadc = 7; max77775_read_reg(max77775->charger, MAX77775_CHG_REG_DETAILS_00, &chgin_dtls); chgin_dtls = ((chgin_dtls & 0x60) >> 5); if (max77775->FW_Revision == 0xFF) { is_factory = (chgin_dtls == 3) ? true : false; md75_info_usb("%s: FW_Revision=0x%02X chgin_dtls=0x%02X is_factory=%d(forced)\n", __func__, max77775->FW_Revision, chgin_dtls, is_factory); return is_factory; } max77775_read_reg(max77775->muic, MAX77775_USBC_REG_USBC_STATUS1, &uidadc); uidadc = uidadc & 0x07; switch (uidadc) { case 3: /* 255K */ case 4: /* 301K */ case 5: /* 523K */ case 6: /* 619K */ if (chgin_dtls == 3) is_factory = true; break; default: break; } max77775_read_reg(max77775->muic, MAX77775_USBC_REG_PD_STATUS2, &fctid); fctid = fctid & 0x0F; switch (fctid) { case 3: /* 255K */ case 4: /* 301K */ case 5: /* 523K */ case 6: /* 619K */ if (chgin_dtls == 3) is_factory = true; break; default: break; } md75_info_usb("%s: FW_Revision=0x%02X, chgin_dtls=%x, uidadc=%x, fctid=%x, is_factory=%d\n", __func__, max77775->FW_Revision, chgin_dtls, uidadc, fctid, (int)is_factory); return is_factory; } bool max77775_is_uid(struct max77775_dev *max77775) { bool is_uid = false; u8 uidadc = 7; max77775_read_reg(max77775->muic, MAX77775_USBC_REG_USBC_STATUS1, &uidadc); uidadc = uidadc & 0x07; switch (uidadc) { case 3: /* 255K */ case 4: /* 301K */ case 5: /* 523K */ case 6: /* 619K */ is_uid = true; break; default: break; } md75_info_usb("%s: uidadc=%x, is_uid=%d\n", __func__, uidadc, (int)is_uid); return is_uid; } #endif /* CONFIG_SEC_FACTORY */ void max77775_set_dpdm_gnd(struct max77775_dev *max77775) { md75_info_usb("%s : Set DPDN GND\n", __func__); max77775_write_reg(max77775->muic, OPCODE_WRITE, 0x04); max77775_write_reg(max77775->muic, OPCODE_DATAOUT1, 0x10); max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); msleep(150); } int max77775_usbc_fw_update(struct max77775_dev *max77775, const u8 *fw_bin, int fw_bin_len, int enforce_do) { max77775_fw_header *fw_header; #if defined(CONFIG_USB_HW_PARAM) struct otg_notify *o_notify = get_otg_notify(); #endif int offset = 0; unsigned long duration = 0; int size = 0; int try_count = 0; int ret = 0; u8 pmicrev = 0x00; u8 usbc_status1 = 0x0; u8 pd_status2 = 0x0; static u8 fct_id; /* FCT cable */ u8 uidadc; /* FCT cable */ u8 try_command = 0; #ifdef CONFIG_USB_NOTIFY_PROC_LOG u8 sw_boot = 0; #endif u8 chg_cnfg_00 = 0; u8 chg_cnfg_11 = 0; u8 chg_cnfg_12 = 0; bool recovery_needed = false; bool wpc_en_changed = 0; int vcell = 0; #if defined(CONFIG_SEC_FACTORY) s32 i_current = 0; #endif int vbyp = 0; u8 recv_opcode = 0; u8 noAutoIbus = 0; u8 chgin_lim = 0; u8 chgin_dtls = 0; u8 wcin_dtls = 0; u8 vbadc = 0; bool is_factory = false; bool is_skip = false; int error = 0; bool is_testsid = false; #if defined(CONFIG_SEC_FACTORY) int current_fct_id = 0; #endif u8 bc_status = 0; u8 chg_type = 0; u8 set_dpdn_gnd = 0; bool is_err_code = false; int fwupdate_retry_cnt = FW_VERIFY_TRY_COUNT; int opcode_retry_cnt = 0; int opcode2_retry_cnt = 0; max77775->fw_size = fw_bin_len; fw_header = (max77775_fw_header *)fw_bin; md75_info_usb("%s: FW_CHK: magic/%x/ major/%x/ minor/%x/ id/%x/ rev/%x/\n", __func__, fw_header->magic, fw_header->major, fw_header->minor, fw_header->id, fw_header->rev); max77775_read_reg(max77775->i2c, MAX77775_PMIC_REG_PMICREV, &pmicrev); if (max77775->required_hw_rev != (pmicrev & 0x7)) { md75_info_usb("%s: FW_SKIP: hw_rev mismatch. required_hw_rev=%x:pmicrev=%x\n", __func__, max77775->required_hw_rev, pmicrev); return 0; } if (max77775->required_fw_pid != fw_header->id) { md75_info_usb("%s: FW_SKIP: product id mismatch. required_fw_pid=%x:fw_header:%x\n", __func__, max77775->required_fw_pid, fw_header->id); return 0; } if (fw_header->magic == MAX77775_SIGN) md75_info_usb("%s: FW_MAGIC: matched\n", __func__); max77775_read_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, &chg_cnfg_00); max77775_read_reg(max77775->charger, MAX77775_CHG_REG_CNFG_11, &chg_cnfg_11); max77775_read_reg(max77775->charger, MAX77775_CHG_REG_CNFG_12, &chg_cnfg_12); md75_info_usb("%s: FW_INFO: chg_cnfg_00=0x%02X | chg_cnfg_11=0x%02X | chg_cnfg_12=0x%02X\n", __func__, chg_cnfg_00, chg_cnfg_11, chg_cnfg_12); #if defined(CONFIG_SEC_FACTORY) max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); is_factory = max77775_is_factory(max77775); if (is_factory) { if (max77775->FW_Revision != 0xFF) { max77775_read_reg(max77775->muic, REG_USBC_STATUS1, &usbc_status1); vbadc = (usbc_status1 & BIT_VBADC) >> FFS(BIT_VBADC); } else md75_info_usb("%s: vbadc check was skipped\n", __func__); } is_skip = max77775_is_uid(max77775); #endif /* CONFIG_SEC_FACTORY */ max77775_read_reg(max77775->muic, REG_BC_STATUS, &bc_status); chg_type = (bc_status & BIT_ChgTyp) >> FFS(BIT_ChgTyp); disable_irq(max77775->irq); retry: md75_info_usb("%s: FW_TRY: try_count=%d, try_command=%d\n", __func__, try_count, try_command); max77775_write_reg(max77775->muic, REG_PD_INT_M, 0xFF); max77775_write_reg(max77775->muic, REG_CC_INT_M, 0xFF); max77775_write_reg(max77775->muic, REG_UIC_INT_M, 0xFF); max77775_write_reg(max77775->muic, REG_VDM_INT_M, 0xFF); #if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) max77775_write_reg(max77775->muic, REG_SPARE_INT_M, 0xFF); #endif offset = 0; duration = 0; size = 0; ret = 0; opcode_retry_cnt = 0; opcode2_retry_cnt = 0; ret = max77775_read_reg(max77775->muic, REG_PRODUCT_ID, &max77775->FW_Product_ID); ret = max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); ret = max77775_read_reg(max77775->muic, REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); max77775->FW_Product_ID_bin = fw_header->id; max77775->FW_Revision_bin = fw_header->major; max77775->FW_Minor_Revision_bin = fw_header->minor; if (ret < 0 && (try_count == 0 && try_command == 0)) { md75_info_usb("%s: FW_READFAILED: Failed to read FW_REV\n", __func__); error = -EIO; goto out; } duration = jiffies; md75_info_usb("%s: FW_INFO: chip : %02X.%02X(PID%02X), bin : %02X.%02X(PID%02X)\n", __func__, max77775->FW_Revision, max77775->FW_Minor_Revision, max77775->FW_Product_ID, fw_header->major, fw_header->minor, fw_header->id); #ifdef CONFIG_USB_NOTIFY_PROC_LOG store_ccic_bin_version(&fw_header->major, &sw_boot); #endif if ((max77775->FW_Revision == 0xff) || enforce_do || (!is_skip && ((max77775->FW_Revision != fw_header->major) || (max77775->FW_Minor_Revision != fw_header->minor) || (max77775->FW_Product_ID != fw_header->id)))) { if (IS_ENABLED(CONFIG_SEC_FACTORY_INTERPOSER) && !enforce_do) { md75_err_usb("%s: Skip fw update on secondary factory binary\n", __func__); error = -EINVAL; goto out; } #if defined(CONFIG_SEC_FACTORY) max77775_read_reg(max77775->muic, MAX77775_USBC_REG_USBC_STATUS1, &usbc_status1); uidadc = (usbc_status1 & BIT_UIDADC) >> FFS(BIT_UIDADC); max77775_read_reg(max77775->muic, REG_PD_STATUS2, &pd_status2); fct_id = (pd_status2 & BIT_FCT_ID) >> FFS(BIT_FCT_ID); if ((is_testsid == false) && ((uidadc == 6) || (uidadc == 5) || (uidadc == 4) || (fct_id == 4) || (fct_id == 5))) { ret = max77775_write_reg(max77775->i2c, 0xFE, 0xC5); ret = max77775_write_reg(max77775->testsid, 0x9B, 0x20); ret = max77775_write_reg(max77775->i2c, 0xFE, 0x00); is_testsid = true; current_fct_id = fct_id; md75_info_usb("%s: testsid(%d)\n", __func__, (int)is_testsid); } else if (is_factory && (max77775->FW_Revision == 0xFF)) { /* Check VCell */ vcell = max77775_fuelgauge_read_vcell(max77775); pr_info("%s: vcell(%dmv)\n", __func__, vcell); /* Check Current */ i_current = max77775_fuelgauge_read_current_ma(max77775); pr_info("%s: current(%dmA)\n", __func__, i_current); /* Check the No Battery Status */ if (vcell < 2000 && (i_current < 20 && i_current > -20)) { max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_09, 0x7F, 0x7F); ret = max77775_write_reg(max77775->i2c, 0xFE, 0xC5); ret = max77775_write_reg(max77775->testsid, 0x9B, 0x20); ret = max77775_write_reg(max77775->i2c, 0xFE, 0x00); is_testsid = true; pr_info("%s : testsid(%d)\n", __func__, (int)is_testsid); } } #endif /* CONFIG_SEC_FACTORY */ if (!enforce_do) { /* on Booting time */ max77775_read_reg(max77775->muic, REG_PD_STATUS2, &pd_status2); fct_id = (pd_status2 & BIT_FCT_ID) >> FFS(BIT_FCT_ID); max77775_read_reg(max77775->muic, REG_USBC_STATUS1, &usbc_status1); uidadc = (usbc_status1 & BIT_UIDADC) >> FFS(BIT_UIDADC); md75_info_usb("%s: FCT_ID: 0x%x UIDADC: 0x%x\n", __func__, fct_id, uidadc); } max77775_read_reg(max77775->charger, MAX77775_CHG_REG_DETAILS_00, &wcin_dtls); wcin_dtls = (wcin_dtls & 0x18) >> 3; wpc_en_changed = true; max77775_wc_control(max77775, false); max77775_read_reg(max77775->charger, MAX77775_CHG_REG_DETAILS_00, &chgin_dtls); chgin_dtls = ((chgin_dtls & 0x60) >> 5); vbyp = max77775_fuelgauge_read_vbyp(max77775); md75_info_usb("%s: FW_INFO: chgin_dtls:0x%x, wcin_dtls:0x%x, vbadc=%x, is_factory=%d, vbyp=%d\n", __func__, chgin_dtls, wcin_dtls, vbadc, (int)is_factory, vbyp); if (try_count == 0 && try_command == 0) { if (is_factory) { if ((chgin_dtls == 3) && (vbyp > 4500 && vbyp < 5800)) { /* In case of : TA only connected, REV is OK or 0xFF * the vbus would be 5V by ic-reset if REV was 0xFF in pre boot-up * vbadc limit was changed to include 3 by SS request */ if (max77775->FW_Revision == 0xFF) { /* Set ChgMode to 0x04 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x04, 0x0F); md75_info_usb("%s: FW_INFO: +change chg_mode(4), vbadc(%d), vbyp(%d)\n", __func__, vbadc, vbyp); recovery_needed = true; } else { max77775_write_reg(max77775->muic, OPCODE_WRITE, OPCODE_CHG_CONTROL1_R); max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); wait_opcode1: msleep(5); max77775_read_reg(max77775->muic, OPCODE_READ, &recv_opcode); if (recv_opcode == OPCODE_CHG_CONTROL1_R) { max77775_read_reg(max77775->muic, MAX77775_USBC_REG_AP_DATAIN1, &noAutoIbus); noAutoIbus = ((noAutoIbus & 0x10) >> 4); if (noAutoIbus) { pr_info("%s: Read ILIM is controlled by CHG\n", __func__); max77775_read_reg(max77775->charger, MAX77775_CHG_REG_CNFG_09, &chgin_lim); } else { recv_opcode = 0; pr_info("%s: Read ILIM is controlled by USB\n", __func__); max77775_write_reg(max77775->muic, OPCODE_WRITE, OPCODE_CHGIN_ILIM_R); max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); wait_opcode2: msleep(5); max77775_read_reg(max77775->muic, OPCODE_READ, &recv_opcode); if (recv_opcode == OPCODE_CHGIN_ILIM_R) max77775_read_reg(max77775->muic, MAX77775_USBC_REG_AP_DATAIN1, &chgin_lim); else { if (opcode2_retry_cnt < 5) { opcode2_retry_cnt++; pr_info("%s: Failed to receive OPCODE_CHGIN_ILIM_R : opcode2_retry_cnt(%d)\n", __func__, opcode2_retry_cnt); goto wait_opcode2; } else { if (++try_count < fwupdate_retry_cnt) { pr_info("%s: Failed to receive OPCODE_CHGIN_ILIM_R : try_count(%d)\n", __func__, try_count); goto retry; } else { pr_info("%s: Failed to receive OPCODE_CHGIN_ILIM_R\n", __func__); error = -EAGAIN; goto out; } } } } chgin_lim = (chgin_lim & 0x7F); /* Chgin_lim > 1500mA */ if (chgin_lim > 0x3B) { /* Set ChgMode to 0x04 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x04, 0x0F); pr_info("%s: FW_INFO: +change chg_mode(4), vbadc(%d), vbyp(%d)\n", __func__, vbadc, vbyp); recovery_needed = true; } else { pr_info("%s: chgin_lim is not valid(0x%02X)\n", __func__, chgin_lim); goto out; } } else { if (opcode_retry_cnt < 5) { opcode_retry_cnt++; pr_info("%s: Failed to receive OPCODE_CHG_CONTROL1_R : opcode_retry_cnt(%d)\n", __func__, opcode_retry_cnt); goto wait_opcode1; } else { if (++try_count < fwupdate_retry_cnt) { pr_info("%s: Failed to receive OPCODE_CHG_CONTROL1_R : try_count(%d)\n", __func__, try_count); goto retry; } else { pr_info("%s: Failed to receive OPCODE_CHG_CONTROL1_R : go out\n", __func__); error = -EAGAIN; goto out; } } } } } else { pr_info("%s: chgin is not valid(%d) or out of vbyp(%d)\n", __func__, chgin_dtls, vbyp); goto out; } } else { if (max77775->FW_Revision == 0xFF) { if (chgin_dtls == 3) { /* In case of : TA connected, REV is 0xFF */ md75_info_usb("%s: FW_INFO: Valid CHGIN_DTLS\n", __func__); /* Set the CHG_ILIM 500mA */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_09, 0x13, 0x7F); md75_info_usb("%s: FW_INFO: CHG_ILIM 500mA\n", __func__); max77775_write_reg(max77775->i2c, 0xFE, 0xC5); max77775_write_reg(max77775->testsid, 0x9B, 0x20); max77775_write_reg(max77775->i2c, 0xFE, 0x00); is_testsid = true; md75_info_usb("%s : testsid(%d)\n", __func__, (int)is_testsid); /* Set ChgMode to 0x00 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x00, 0x0F); md75_info_usb("%s: FW_INFO: +change chg_mode(0)\n", __func__); usleep_range(100, 200); /* Set ChgMode to 0x05 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x05, 0x0F); md75_info_usb("%s: FW_INFO: +change chg_mode(5)\n", __func__); recovery_needed = true; if (vbyp > 6000) { pr_info("%s: FW_SKIP: byp(%dmv) > 6000mv\n", __func__, vbyp); error = -EAGAIN; goto out; } md75_info_usb("%s: FW_INFO: byp(%dmv) < 6000mv\n", __func__, vbyp); } else { /* In case of : battery connected, REV is 0xFF */ md75_info_usb("%s: FW_INFO: Invalid CHGIN_DTLS\n", __func__); /* Set Chginsel and Wcinsel to 0 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_12, 0x00, 0x60); md75_info_usb("%s: FW_INFO: +disable CHGINSEL(0) / WCINSEL(0)\n", __func__); usleep_range(100, 200); /* Set ChgMode to 0x00 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x00, 0x0F); md75_info_usb("%s: FW_INFO: +change chg_mode(0)\n", __func__); /* Set VBypSet to 0x00 */ max77775_write_reg(max77775->charger, MAX77775_CHG_REG_CNFG_11, 0x00); md75_info_usb("%s: FW_INFO: +clear VBYPSET(0x00)\n", __func__); usleep_range(100, 200); /* Set ChgMode to 0x09 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x09, 0x0F); md75_info_usb("%s: FW_INFO: +change chg_mode(9)\n", __func__); recovery_needed = true; } } else { /* In case of : battery connected, REV is OK */ pr_info("%s: FW_INFO: REV is OK\n", __func__); /* Check VCell */ vcell = max77775_fuelgauge_read_vcell(max77775); if (vcell < 3600) { pr_info("%s: FW_SKIP: keep chg_mode(0x%x), vcell(%dmv) < 3600mv\n", __func__, chg_cnfg_00 & 0x0F, vcell); error = -EAGAIN; goto out; } pr_info("%s: FW_INFO: vcell(%dmv) > 3600mv\n", __func__, vcell); /* Set Chginsel and Wcinsel to 0 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_12, 0x00, 0x60); pr_info("%s: FW_INFO: +disable CHGINSEL(0) / WCINSEL(0)\n", __func__); usleep_range(100, 200); /* Set ChgMode to 0x00 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x00, 0x0F); pr_info("%s: FW_INFO: +change chg_mode(0)\n", __func__); /* Set VBypSet to 0x00 */ max77775_write_reg(max77775->charger, MAX77775_CHG_REG_CNFG_11, 0x00); pr_info("%s: FW_INFO: +clear VBYPSET(0x00)\n", __func__); usleep_range(100, 200); /* Set ChgMode to 0x09 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x09, 0x0F); pr_info("%s: FW_INFO: +change chg_mode(9)\n", __func__); recovery_needed = true; } } } if (chg_type == 0x03 /*CHGTYP_DEDICATED_CHARGER*/ && set_dpdn_gnd == 0x0 && max77775->FW_Revision != 0xFF) { pr_info("%s : Set DPDN GND\n", __func__); set_dpdn_gnd = 1; max77775_write_reg(max77775->muic, OPCODE_WRITE, 0x04); max77775_write_reg(max77775->muic, OPCODE_DATAOUT1, 0x10); max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); msleep(150); } if (is_err_code == false) { msleep(150); max77775_write_reg(max77775->muic, OPCODE_WRITE, 0xD0); max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); msleep(300); } max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); max77775_read_reg(max77775->muic, REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); md75_info_usb("%s: FW_START: (%02X.%02X)\n", __func__, max77775->FW_Revision, max77775->FW_Minor_Revision); if (max77775->FW_Revision != 0xFF) { if (++try_command < FW_SECURE_MODE_TRY_COUNT) { md75_info_usb("%s: FW_FAILED: the Fail to enter secure mode %d\n", __func__, try_command); max77775_reset_ic(max77775); if (is_err_code == true) { msleep(150); max77775_write_reg(max77775->muic, OPCODE_WRITE, 0xD0); max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); msleep(300); } goto retry; } else { md75_info_usb("%s: FW_FAILED: the Secure Update Fail!!\n", __func__); #if defined(CONFIG_USB_HW_PARAM) if (o_notify) inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); #endif error = -EIO; goto out; } } try_command = 0; for (offset = FW_HEADER_SIZE; offset < fw_bin_len && size != FW_UPDATE_END;) { size = __max77775_usbc_fw_update(max77775, &fw_bin[offset]); switch (size) { case FW_UPDATE_ERR_CODE_FAIL: pr_err("%s: FW_ERR_CODE_FAIL\n", __func__); if (is_err_code == false) { is_err_code = true; fwupdate_retry_cnt += FW_ERR_CODE_TRY_COUNT; pr_info("%s: FW_ERR_CODE_FAIL : Increased retry count (%d)\n", __func__, fwupdate_retry_cnt); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) fallthrough; #endif case FW_UPDATE_VERIFY_FAIL: md75_err_usb("%s: FW_VERIFY_FAIL\n", __func__); offset -= FW_CMD_WRITE_SIZE; #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) fallthrough; #endif case FW_UPDATE_TIMEOUT_FAIL: /* * Retry FW updating */ if (++try_count < fwupdate_retry_cnt) { md75_info_usb("%s: FW_TIMEOUT: Retry fw write. ret %d, count %d, offset %d\n", __func__, size, try_count, offset); max77775_reset_ic(max77775); goto retry; } else { md75_info_usb("%s: FW_TIMEOUT: Failed to update FW. ret %d, offset %d\n", __func__, size, (offset + size)); #if defined(CONFIG_USB_HW_PARAM) if (o_notify) inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); #endif error = -EIO; goto out; } break; case FW_UPDATE_CMD_FAIL: case FW_UPDATE_MAX_LENGTH_FAIL: md75_info_usb("%s: FW_LENGTH_FAIL: Failed to update FW. ret %d, offset %d\n", __func__, size, (offset + size)); #if defined(CONFIG_USB_HW_PARAM) if (o_notify) inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); #endif error = -EIO; goto out; case FW_UPDATE_END: /* 0x00 */ max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); max77775_read_reg(max77775->muic, REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); max77775_read_reg(max77775->muic, REG_PRODUCT_ID, &max77775->FW_Product_ID); md75_info_usb("%s: chip : %02X.%02X(PID%02X), bin : %02X.%02X(PID%02X)\n", __func__, max77775->FW_Revision, max77775->FW_Minor_Revision, max77775->FW_Product_ID, fw_header->major, fw_header->minor, fw_header->id); md75_info_usb("%s: FW_COMPLETED\n", __func__); if (max77775_get_facmode()) max77775_write_fw_noautoibus(max77775); break; default: offset += size; break; } if (offset == fw_bin_len) { max77775_reset_ic(max77775); max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); max77775_read_reg(max77775->muic, REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); max77775_read_reg(max77775->muic, REG_PRODUCT_ID, &max77775->FW_Product_ID); md75_info_usb("%s: FW_INFO: chip : %02X.%02X(PID%02X), bin : %02X.%02X(PID%02X)\n", __func__, max77775->FW_Revision, max77775->FW_Minor_Revision, max77775->FW_Product_ID, fw_header->major, fw_header->minor, fw_header->id); md75_info_usb("%s: FW COMPLETED via SYS path\n", __func__); } } } else { md75_info_usb("%s: FW_SKIP: Don't need to update!\n", __func__); goto out; } duration = jiffies - duration; md75_info_usb("%s: FW_OK Duration : %dms\n", __func__, jiffies_to_msecs(duration)); out: if (recovery_needed) { if (is_factory) { max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, chg_cnfg_00, 0x0F); md75_info_usb("%s: FW_INFO: -recover ChgMode(%d) -> chg_cnfg_00=0x%02X, vbadc(%d)\n", __func__, chg_cnfg_00 & 0x0F, chg_cnfg_00, vbadc); } else { /* Set ChgMode to 0x00 */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, 0x00, 0x0F); md75_info_usb("%s: FW_INFO: -recover chg_mode(0)\n", __func__); /* Recover VBypSet */ max77775_write_reg(max77775->charger, MAX77775_CHG_REG_CNFG_11, chg_cnfg_11); md75_info_usb("%s: FW_INFO: -recover VBYPSET(0x%02X)\n", __func__, chg_cnfg_11); usleep_range(100, 200); /* Recover ChgMode */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, chg_cnfg_00, 0x0F); md75_info_usb("%s: FW_INFO: -recover ChgMode(%d) -> chg_cnfg_00=0x%02X\n", __func__, chg_cnfg_00 & 0x0F, chg_cnfg_00); usleep_range(100, 200); /* Recover Chginsel and Wcinsel */ max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_12, chg_cnfg_12, 0x60); pr_info("%s: FW_INFO: -recover CHGINSEL(%d) / WCINSEL(%d) -> chg_cnfg_12=0x%02X\n", __func__, chg_cnfg_12 & 0x20 ? 1 : 0, chg_cnfg_12 & 0x40 ? 1 : 0, chg_cnfg_12); usleep_range(100, 200); } } if (wpc_en_changed) { max77775_wc_control(max77775, true); } enable_irq(max77775->irq); if (is_testsid == true) { #if defined(CONFIG_SEC_FACTORY) if (current_fct_id == 4 || current_fct_id == 5) { pr_info("%s : Apply 2sec, fct_id (%d)\n", __func__, fct_id); msleep(2000); } #endif /* CONFIG_SEC_FATORY */ max77775_write_reg(max77775->i2c, 0xFE, 0xC5); max77775_write_reg(max77775->testsid, 0x9B, 0x00); max77775_write_reg(max77775->i2c, 0xFE, 0x00); is_testsid = false; md75_info_usb("%s: testsid(%d)\n", __func__, (int)is_testsid); } return error; } EXPORT_SYMBOL_GPL(max77775_usbc_fw_update); static void __max77775_usbc_fw_setting(struct max77775_dev *max77775, const struct firmware *fw, char *fw_bin_name, int enforce_do) { md75_info_usb("%s: fw update (name=%s size=%lu enforce_do=%d)\n", __func__, fw_bin_name, fw->size, enforce_do); max77775_usbc_fw_update(max77775, fw->data, fw->size, enforce_do); } int max77775_usbc_fw_setting(struct max77775_dev *max77775, int enforce_do) { int err = 0; const struct firmware *fw; char *fw_bin_name = FW_BIN_NAME; if (max77775->pdata->extra_fw_enable) fw_bin_name = EXTRA_FW_BIN_NAME; err = request_firmware(&fw, fw_bin_name, max77775->dev); if (!err) { __max77775_usbc_fw_setting(max77775, fw, fw_bin_name, enforce_do); release_firmware(fw); } return err; } EXPORT_SYMBOL_GPL(max77775_usbc_fw_setting); #endif /* CONFIG_CCIC_MAX77775 */ static u8 max77775_revision_check(u8 pmic_id, u8 pmic_rev) { int i, logical_id = 0; int pmic_arrary = ARRAY_SIZE(max77775_revision); md75_info_usb("%s: pmic_id(0x%02X) pmic_rev(0x%02X)\n", __func__, pmic_id, pmic_rev); for (i = 0; i < pmic_arrary; i++) { md75_info_usb("%s: max77775_revision[%d].id(0x%02X) max77775_revision[%d].rev(0x%02X)\n", __func__, i, max77775_revision[i].id, i, max77775_revision[i].rev); if (max77775_revision[i].id == pmic_id && max77775_revision[i].rev == pmic_rev) logical_id = max77775_revision[i].logical_id; } md75_info_usb("%s: logical_id(0x%02X)\n", __func__, logical_id); return logical_id; } void max77775_register_pdmsg_func(struct max77775_dev *max77775, void (*check_pdmsg)(void *data, u8 pdmsg), void *data) { if (!max77775) { md75_err_usb("%s max77775 is null\n", __func__); return; } max77775->check_pdmsg = check_pdmsg; max77775->usbc_data = data; } EXPORT_SYMBOL_GPL(max77775_register_pdmsg_func); static int max77775_check_deferred_probe(struct max77775_dev *max77775, int ret) { static int fw_setting_try_count; if (ret && max77775_firmware_timeout_state == 0 && !is_recovery_mode_pdic_param()) { fw_setting_try_count++; md75_info_usb("%s: ret = (%d), return -EPROBE_DEFER(%d)\n", __func__, ret, fw_setting_try_count); return -EPROBE_DEFER; } max77775_firmware_timeout_state = MD75_FIRMWARE_TIMEOUT_COMPLETE; return 0; } static int max77775_firmware_timeout_probe(struct platform_device *pdev) { md75_info_usb("%s firmware_timeout_count=%d\n", __func__, firmware_timeout_count); return 0; } static int max77775_firmware_timeout_remove(struct platform_device *pdev) { return 0; } static struct platform_driver max77775_firmware_timeout_driver = { .driver = { .name = "max77775-firmware-timeout", }, .probe = max77775_firmware_timeout_probe, .remove = max77775_firmware_timeout_remove, }; static void firmware_load_timeout_del(void) { platform_device_unregister(pdev); platform_driver_unregister(&max77775_firmware_timeout_driver); } static void max77775_firmware_load_timeout(struct work_struct *unused) { int error = 0; md75_info_usb("%s enter\n", __func__); if (max77775_firmware_timeout_state == MD75_FIRMWARE_TIMEOUT_COMPLETE) { md75_info_usb("%s already complete\n", __func__); goto done; } max77775_firmware_timeout_state = MD75_FIRMWARE_TIMEOUT_START; while (1) { firmware_timeout_count++; error = platform_driver_register(&max77775_firmware_timeout_driver); if (error) { md75_err_usb("%s platform_driver_register error %d\n", __func__, error); goto err1; } pdev = platform_device_alloc("max77775-firmware-timeout", PLATFORM_DEVID_AUTO); if (!pdev) { md75_err_usb("%s pdev error\n", __func__); goto err2; } error = platform_device_add(pdev); if (error) { md75_err_usb("%s platform_device_add error %d\n", __func__, error); goto err3; } max77775_firmware_timeout_state = MD75_FIRMWARE_TIMEOUT_PASS; msleep(1000); firmware_load_timeout_del(); /* 5 times retry. it will call deferred probe */ if (firmware_timeout_count >= 5 || max77775_firmware_timeout_state == MD75_FIRMWARE_TIMEOUT_COMPLETE) break; } done: return; err3: platform_device_put(pdev); err2: platform_driver_unregister(&max77775_firmware_timeout_driver); err1: max77775_firmware_timeout_state = MD75_FIRMWARE_TIMEOUT_FAIL; md75_info_usb("%s fail\n", __func__); return; } static DECLARE_DELAYED_WORK(firmware_load_work, max77775_firmware_load_timeout); #if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) static int max77775_i2c_probe(struct i2c_client *i2c) #else static int max77775_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *dev_id) #endif { struct max77775_dev *max77775; struct max77775_platform_data *pdata = i2c->dev.platform_data; int ret = 0; u8 pmic_id, pmic_rev = 0; #if IS_ENABLED(CONFIG_CCIC_MAX77775) const struct firmware *fw = NULL; char *fw_bin_name = FW_BIN_NAME; #endif md75_info_usb("%s:%s\n", MFD_DEV_NAME, __func__); #ifdef CONFIG_USB_USING_ADVANCED_USBLOG store_tcpc_name(MFD_DEV_NAME); #endif max77775 = devm_kzalloc(&i2c->dev, sizeof(struct max77775_dev), GFP_KERNEL); if (!max77775) { ret = -ENOMEM; goto err; } if (i2c->dev.of_node) { pdata = devm_kzalloc(&i2c->dev, sizeof(struct max77775_platform_data), GFP_KERNEL); if (!pdata) { ret = -ENOMEM; goto err; } #if defined(CONFIG_OF) ret = of_max77775_dt(&i2c->dev, pdata); if (ret < 0) { dev_err(&i2c->dev, "Failed to get device of_node\n"); goto err; } #endif i2c->dev.platform_data = pdata; } else pdata = i2c->dev.platform_data; max77775->dev = &i2c->dev; max77775->i2c = i2c; max77775->irq = i2c->irq; if (pdata) { max77775->pdata = pdata; pdata->irq_base = devm_irq_alloc_descs(&i2c->dev, -1, 0, MAX77775_IRQ_NR, -1); if (pdata->irq_base < 0) { md75_err_usb("%s:%s irq_alloc_descs Fail! ret(%d)\n", MFD_DEV_NAME, __func__, pdata->irq_base); ret = -EINVAL; goto err; } else max77775->irq_base = pdata->irq_base; max77775->irq_gpio = pdata->irq_gpio; max77775->blocking_waterevent = pdata->blocking_waterevent; max77775->required_hw_rev = pdata->rev; max77775->required_fw_pid = pdata->fw_product_id; } else { ret = -EINVAL; goto err; } #if IS_ENABLED(CONFIG_CCIC_MAX77775) if (max77775->pdata->extra_fw_enable) fw_bin_name = EXTRA_FW_BIN_NAME; ret = request_firmware(&fw, fw_bin_name, max77775->dev); ret = max77775_check_deferred_probe(max77775, ret); if (ret) goto err; #endif max77775_print_pdata_property(pdata); max77775->ws.name = MFD_DEV_NAME; wakeup_source_add(&max77775->ws); mutex_init(&max77775->i2c_lock); init_waitqueue_head(&max77775->suspend_wait); i2c_set_clientdata(i2c, max77775); if (max77775_read_reg(i2c, MAX77775_PMIC_REG_PMICID, &pmic_id) < 0) { dev_err(max77775->dev, "device not found on this channel (this is not an error)\n"); ret = -ENODEV; goto err_w_lock; } if (max77775_read_reg(i2c, MAX77775_PMIC_REG_PMICREV, &pmic_rev) < 0) { dev_err(max77775->dev, "device not found on this channel (this is not an error)\n"); ret = -ENODEV; goto err_w_lock; } md75_info_usb("%s:%s pmic_id:%x, pmic_rev:%x\n", MFD_DEV_NAME, __func__, pmic_id, pmic_rev); max77775->pmic_id = pmic_id; max77775->pmic_rev = max77775_revision_check(pmic_id, pmic_rev & 0x7); if (max77775->pmic_rev == 0) { dev_err(max77775->dev, "Can not find matched revision\n"); ret = -ENODEV; goto err_w_lock; } /* print rev */ md75_info_usb("%s:%s device found: id:%x rev:%x\n", MFD_DEV_NAME, __func__, max77775->pmic_id, max77775->pmic_rev); /* No active discharge on safeout ldo 1,2 */ /* max77775_update_reg(i2c, MAX77775_PMIC_REG_SAFEOUT_CTRL, 0x00, 0x30); */ #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) max77775->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC); #else max77775->muic = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_MUIC); #endif i2c_set_clientdata(max77775->muic, max77775); #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) max77775->charger = i2c_new_dummy(i2c->adapter, I2C_ADDR_CHG); #else max77775->charger = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_CHG); #endif i2c_set_clientdata(max77775->charger, max77775); #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) max77775->fuelgauge = i2c_new_dummy(i2c->adapter, I2C_ADDR_FG); #else max77775->fuelgauge = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_FG); #endif i2c_set_clientdata(max77775->fuelgauge, max77775); #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) max77775->testsid = i2c_new_dummy(i2c->adapter, I2C_ADDR_TESTSID); #else max77775->testsid = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_TESTSID); #endif i2c_set_clientdata(max77775->testsid, max77775); /* read PRODUCT_ID, FW_REV, FW_REV2 */ ret = max77775_read_reg(max77775->muic, REG_PRODUCT_ID, &max77775->FW_Product_ID); ret += max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); ret += max77775_read_reg(max77775->muic, REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); if (ret) { md75_err_usb("%s: Failed to PRODUCT_ID, UIC_FW_REV and UIC_FW_REV2\n", __func__); goto err_i2c; } #if IS_ENABLED(CONFIG_CCIC_MAX77775) init_completion(&max77775->fw_completion); max77775->fw_workqueue = create_singlethread_workqueue("fw_update"); if (max77775->fw_workqueue == NULL) { ret = -ENOMEM; goto err_i2c; } INIT_WORK(&max77775->fw_work, max77775_usbc_wait_response_q); if (fw) { __max77775_usbc_fw_setting(max77775, fw, fw_bin_name, 0); release_firmware(fw); fw = NULL; } #endif disable_irq(max77775->irq); ret = max77775_irq_init(max77775); if (ret < 0) goto err_i2c; ret = mfd_add_devices(max77775->dev, -1, max77775_devs, ARRAY_SIZE(max77775_devs), NULL, 0, NULL); if (ret < 0) goto err_irq_init; ret = device_init_wakeup(max77775->dev, true); if (ret < 0) goto err_mfd; return ret; err_mfd: mfd_remove_devices(max77775->dev); err_irq_init: max77775_irq_exit(max77775); err_i2c: i2c_unregister_device(max77775->muic); i2c_unregister_device(max77775->charger); i2c_unregister_device(max77775->fuelgauge); err_w_lock: mutex_destroy(&max77775->i2c_lock); wakeup_source_remove(&max77775->ws); #if IS_ENABLED(CONFIG_CCIC_MAX77775) if (fw) release_firmware(fw); #endif err: return ret; } #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) static int max77775_i2c_remove(struct i2c_client *i2c) #else static void max77775_i2c_remove(struct i2c_client *i2c) #endif { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); device_init_wakeup(max77775->dev, false); max77775_irq_exit(max77775); mfd_remove_devices(max77775->dev); i2c_unregister_device(max77775->muic); i2c_unregister_device(max77775->charger); i2c_unregister_device(max77775->fuelgauge); mutex_destroy(&max77775->i2c_lock); wakeup_source_remove(&max77775->ws); #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) return 0; #else return; #endif } static void max77775_i2c_shutdown(struct i2c_client *i2c) { struct max77775_dev *max77775 = i2c_get_clientdata(i2c); max77775_irq_exit(max77775); max77775->shutdown = 1; } static const struct i2c_device_id max77775_i2c_id[] = { { MFD_DEV_NAME, TYPE_MAX77775 }, { } }; MODULE_DEVICE_TABLE(i2c, max77775_i2c_id); #if defined(CONFIG_OF) static const struct of_device_id max77775_i2c_dt_ids[] = { { .compatible = "maxim,max77775" }, { }, }; MODULE_DEVICE_TABLE(of, max77775_i2c_dt_ids); #endif /* CONFIG_OF */ #if defined(CONFIG_PM) static int max77775_suspend(struct device *dev) { struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); struct max77775_dev *max77775 = i2c_get_clientdata(i2c); md75_info_usb("%s:%s\n", MFD_DEV_NAME, __func__); synchronize_irq(max77775->irq); max77775->suspended = true; return 0; } static int max77775_resume(struct device *dev) { struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); struct max77775_dev *max77775 = i2c_get_clientdata(i2c); md75_info_usb("%s:%s\n", MFD_DEV_NAME, __func__); max77775->suspended = false; wake_up_interruptible(&max77775->suspend_wait); return 0; } #else #define max77775_suspend NULL #define max77775_resume NULL #endif /* CONFIG_PM */ const struct dev_pm_ops max77775_pm = { .suspend = max77775_suspend, .resume = max77775_resume, }; static struct i2c_driver max77775_i2c_driver = { .driver = { .name = MFD_DEV_NAME, .owner = THIS_MODULE, #if defined(CONFIG_PM) .pm = &max77775_pm, #endif /* CONFIG_PM */ #if defined(CONFIG_OF) .of_match_table = max77775_i2c_dt_ids, #endif /* CONFIG_OF */ }, .probe = max77775_i2c_probe, .remove = max77775_i2c_remove, .shutdown = max77775_i2c_shutdown, .id_table = max77775_i2c_id, }; static int __init max77775_i2c_init(void) { md75_info_usb("%s:%s\n", MFD_DEV_NAME, __func__); schedule_delayed_work(&firmware_load_work, MD75_FIRMWARE_TIMEOUT_SEC * HZ); return i2c_add_driver(&max77775_i2c_driver); } /* init early so consumer devices can complete system boot */ subsys_initcall(max77775_i2c_init); static void __exit max77775_i2c_exit(void) { cancel_delayed_work_sync(&firmware_load_work); i2c_del_driver(&max77775_i2c_driver); } module_exit(max77775_i2c_exit); MODULE_AUTHOR("Samsung USB Team"); MODULE_DESCRIPTION("Max77775 MFD driver"); MODULE_LICENSE("GPL");