Files
2025-08-12 22:16:57 +02:00

2079 lines
58 KiB
C
Executable File

/*
* max77775.c - mfd core driver for the Maxim 77775
*
* Copyright (C) 2016 Samsung Electronics
* Insun Choi <insun77.choi@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This driver is based on max8997.c
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/mfd/core.h>
#include <linux/mfd/max77775_log.h>
#include <linux/mfd/max77775.h>
#include <linux/mfd/max77775-private.h>
#include <linux/regulator/machine.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/version.h>
#include <linux/firmware.h>
#include <linux/usb/typec/common/pdic_param.h>
#if defined(CONFIG_HV_MUIC_MAX77775_AFC)
#include <linux/muic/common/muic.h>
#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 <linux/usb_notify.h>
#endif
#if defined(CONFIG_OF)
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#endif /* CONFIG_OF */
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
#include <linux/battery/sec_battery_common.h>
#endif
#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT)
#include <linux/sti/abc_common.h>
#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");