blob: 9beab4bc640ee52ab686981c749c6135056d8655 [file] [log] [blame]
/*
* max77705.c - mfd core driver for the Maxim 77705
*
* 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/max77705.h>
#include <linux/mfd/max77705-private.h>
#include <linux/regulator/machine.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/muic/muic.h>
#include <linux/muic/max77705-muic.h>
#include <linux/ccic/max77705_usbc.h>
//#include <linux/ccic/max77705_pass2.h>
#include <linux/ccic/max77705_pass3.h>
#include <linux/ccic/max77705_pass4.h>
#if defined(CONFIG_MAX77705_FW_PID03_SUPPORT)
#include <linux/ccic/max77705C_pass2_PID03.h>
#elif defined(CONFIG_MAX77705_FW_PID07_SUPPORT)
#include <linux/ccic/max77705C_pass2_PID07.h>
#else
#include <linux/ccic/max77705C_pass2.h>
#endif
#include <linux/usb_notify.h>
#if defined(CONFIG_OF)
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#endif /* CONFIG_OF */
#include "../battery_v2/include/sec_charging_common.h"
#define I2C_ADDR_PMIC (0xCC >> 1) /* Top sys, Haptic */
#define I2C_ADDR_MUIC (0x4A >> 1)
#define I2C_ADDR_CHG (0xD2 >> 1)
#define I2C_ADDR_FG (0x6C >> 1)
#define I2C_ADDR_DEBUG (0xC4 >> 1)
#define I2C_RETRY_CNT 3
/*
* pmic revision information
*/
struct max77705_revision_struct {
u8 id;
u8 rev;
u8 logical_id;
};
static struct max77705_revision_struct max77705_revision[3] = {
{ 0x5, 0x3, 0x3}, // MD05 PASS3
{ 0x5, 0x4, 0x4}, // MD05 PASS4
{ 0x15,0x2, 0x5}, // MD15 PASS2
};
static struct mfd_cell max77705_devs[] = {
#if defined(CONFIG_CCIC_MAX77705)
{ .name = "max77705-usbc", },
#endif
#if defined(CONFIG_REGULATOR_MAX77705)
{ .name = "max77705-safeout", },
#endif /* CONFIG_REGULATOR_MAX77705 */
#if defined(CONFIG_FUELGAUGE_MAX77705)
{ .name = "max77705-fuelgauge", },
#endif
#if defined(CONFIG_CHARGER_MAX77705)
{ .name = "max77705-charger", },
#endif
#if defined(CONFIG_MOTOR_DRV_MAX77705)
{ .name = "max77705-haptic", },
#endif /* CONFIG_MAX77705_HAPTIC */
#if defined(CONFIG_LEDS_MAX77705_RGB)
{ .name = "leds-max77705-rgb", },
#endif /* CONFIG_LEDS_MAX77705_RGB */
#if defined(CONFIG_LEDS_MAX77705_FLASH)
{ .name = "max77705-flash", },
#endif
};
int max77705_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest)
{
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
struct otg_notify *o_notify = get_otg_notify();
int ret, i;
mutex_lock(&max77705->i2c_lock);
for (i = 0; i < I2C_RETRY_CNT; ++i) {
ret = i2c_smbus_read_byte_data(i2c, reg);
if (ret >= 0)
break;
pr_info("%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(&max77705->i2c_lock);
if (ret < 0) {
pr_info("%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
return ret;
}
ret &= 0xff;
*dest = ret;
return 0;
}
EXPORT_SYMBOL_GPL(max77705_read_reg);
int max77705_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
{
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
struct otg_notify *o_notify = get_otg_notify();
int ret, i;
mutex_lock(&max77705->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;
pr_info("%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(&max77705->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
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(max77705_bulk_read);
int max77705_read_word(struct i2c_client *i2c, u8 reg)
{
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
struct otg_notify *o_notify = get_otg_notify();
int ret, i;
mutex_lock(&max77705->i2c_lock);
for (i = 0; i < I2C_RETRY_CNT; ++i) {
ret = i2c_smbus_read_word_data(i2c, reg);
if (ret >= 0)
break;
pr_info("%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(&max77705->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
return ret;
}
EXPORT_SYMBOL_GPL(max77705_read_word);
int max77705_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
{
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
struct otg_notify *o_notify = get_otg_notify();
int ret = -EIO, i;
int timeout = 2000; /* 2sec */
int interval = 100;
while (ret == -EIO) {
mutex_lock(&max77705->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;
pr_info("%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(&max77705->i2c_lock);
if (ret < 0) {
pr_info("%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
return ret;
}
EXPORT_SYMBOL_GPL(max77705_write_reg);
int max77705_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
{
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
struct otg_notify *o_notify = get_otg_notify();
int ret = -EIO, i;
int timeout = 2000; /* 2sec */
int interval = 100;
while (ret == -EIO) {
mutex_lock(&max77705->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;
pr_info("%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(&max77705->i2c_lock);
if (ret < 0) {
pr_info("%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
return ret;
}
EXPORT_SYMBOL_GPL(max77705_bulk_write);
int max77705_write_word(struct i2c_client *i2c, u8 reg, u16 value)
{
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
struct otg_notify *o_notify = get_otg_notify();
int ret, i;
mutex_lock(&max77705->i2c_lock);
for (i = 0; i < I2C_RETRY_CNT; ++i) {
ret = i2c_smbus_write_word_data(i2c, reg, value);
if (ret >= 0)
break;
pr_info("%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(&max77705->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
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(max77705_write_word);
int max77705_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask)
{
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
struct otg_notify *o_notify = get_otg_notify();
int ret, i;
u8 old_val, new_val;
mutex_lock(&max77705->i2c_lock);
for (i = 0; i < I2C_RETRY_CNT; ++i) {
ret = i2c_smbus_read_byte_data(i2c, reg);
if (ret >= 0)
break;
pr_info("%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;
pr_info("%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(&max77705->i2c_lock);
return ret;
}
EXPORT_SYMBOL_GPL(max77705_update_reg);
#if defined(CONFIG_OF)
static int of_max77705_dt(struct device *dev, struct max77705_platform_data *pdata)
{
struct device_node *np_max77705 = dev->of_node;
struct device_node *np_battery;
int ret = 0;
if (!np_max77705)
return -EINVAL;
pdata->irq_gpio = of_get_named_gpio(np_max77705, "max77705,irq-gpio", 0);
pdata->wakeup = of_property_read_bool(np_max77705, "max77705,wakeup");
if (of_property_read_u32(np_max77705, "max77705,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_max77705, "max77705,blocking_waterevent");
#endif
pr_info("%s: irq-gpio: %u\n", __func__, pdata->irq_gpio);
np_battery = of_find_node_by_name(NULL, "battery");
if (!np_battery) {
pr_info("%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) {
pr_info("%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)
pr_info("%s: Wireless charger name is Empty\n", __func__);
}
pdata->support_audio = of_property_read_bool(np_max77705, "max77705,support-audio");
pr_info("%s: support_audio %d\n", __func__, pdata->support_audio);
return 0;
}
#endif /* CONFIG_OF */
/* samsung */
#ifdef CONFIG_CCIC_MAX77705
static void max77705_reset_ic(struct max77705_dev *max77705)
{
msg_maxim("Reset!!");
max77705_write_reg(max77705->muic, 0x80, 0x0F);
msleep(100);
}
static void max77705_usbc_wait_response_q(struct work_struct *work)
{
struct max77705_dev *max77705;
u8 read_value = 0x00;
u8 dummy[2] = { 0, };
max77705 = container_of(work, struct max77705_dev, fw_work);
while (max77705->fw_update_state == FW_UPDATE_WAIT_RESP_START) {
switch (max77705->pmic_rev) {
case MAX77705_PASS1:
max77705_bulk_read(max77705->muic, REG_UIC_INT-1, 2, dummy);
read_value = dummy[1];
break;
default:
max77705_bulk_read(max77705->muic, REG_UIC_INT, 1, dummy);
read_value = dummy[0];
break;
}
if ((read_value & BIT_APCmdResI) == BIT_APCmdResI)
break;
}
complete_all(&max77705->fw_completion);
}
static int max77705_usbc_wait_response(struct max77705_dev *max77705)
{
unsigned long time_remaining = 0;
max77705->fw_update_state = FW_UPDATE_WAIT_RESP_START;
init_completion(&max77705->fw_completion);
queue_work(max77705->fw_workqueue, &max77705->fw_work);
time_remaining = wait_for_completion_timeout(
&max77705->fw_completion,
msecs_to_jiffies(FW_WAIT_TIMEOUT));
max77705->fw_update_state = FW_UPDATE_WAIT_RESP_STOP;
if (!time_remaining) {
msg_maxim("Failed to update due to timeout");
cancel_work_sync(&max77705->fw_work);
return FW_UPDATE_TIMEOUT_FAIL;
}
return 0;
}
static int __max77705_usbc_fw_update(
struct max77705_dev *max77705, 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) {
max77705_reset_ic(max77705);
max77705->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 */
max77705_bulk_write(max77705->muic,
fw_opcode,
I2C_SMBUS_BLOCK_HALF,
fw_data);
max77705_bulk_write(max77705->muic,
fw_opcode + I2C_SMBUS_BLOCK_HALF,
fw_data_len - I2C_SMBUS_BLOCK_HALF,
&fw_data[I2C_SMBUS_BLOCK_HALF]);
} else
max77705_bulk_write(max77705->muic,
fw_opcode,
fw_data_len,
fw_data);
ret = max77705_usbc_wait_response(max77705);
if (ret)
return ret;
/*
* Why do we need 1ms sleep in case MQ81?
*/
/* msleep(1); */
return FW_CMD_WRITE_SIZE;
case FW_CMD_READ:
max77705_bulk_read(max77705->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)) {
msg_maxim("[0x%02x 0x%02x], [0x%02x, 0x%02x], [0x%02x, 0x%02x]",
verify_data[0], fw_data[0],
verify_data[1], fw_data[1],
verify_data[2], fw_data[2]);
return FW_UPDATE_VERIFY_FAIL;
}
return FW_CMD_READ_SIZE;
}
msg_maxim("Command error");
return ret;
}
int max77705_write_jig_on(struct max77705_dev *max77705)
{
u8 write_values[OPCODE_MAX_LENGTH] = { 0, };
int ret = 0;
int length = 0x2;
int i = 0;
write_values[0] = OPCODE_CTRL3_W;
write_values[1] = 0x1;
write_values[2] = 0x1;
for (i = 0; i < length + OPCODE_SIZE; i++)
msg_maxim("[%d], 0x[%x]", i, write_values[i]);
/* Write opcode and data */
ret = max77705_bulk_write(max77705->muic, OPCODE_WRITE,
length + OPCODE_SIZE, write_values);
/* Write end of data by 0x00 */
if (length < OPCODE_DATA_LENGTH)
max77705_write_reg(max77705->muic, OPCODE_WRITE_END, 0x00);
return 0;
}
static int max77705_fuelgauge_read_vcell(struct max77705_dev *max77705)
{
u8 data[2];
u32 vcell;
u16 w_data;
u32 temp;
u32 temp2;
if (max77705_bulk_read(max77705->fuelgauge, VCELL_REG, 2, data) < 0) {
pr_err("%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 void max77705_wc_control(struct max77705_dev *max77705, bool enable)
{
struct power_supply *psy = NULL;
union power_supply_propval value = {0, };
char wpc_en_status[2];
psy = get_power_supply_by_name(max77705->pdata->wireless_charger_name);
if (psy) {
wpc_en_status[0] = WPC_EN_CCIC;
wpc_en_status[1] = enable ? true : false;
value.strval= wpc_en_status;
if ((psy->desc->set_property != NULL) &&
(psy->desc->set_property(psy, (enum power_supply_property)POWER_SUPPLY_EXT_PROP_WPC_EN, &value) >= 0))
pr_info("%s: WC CONTROL: %s\n", __func__, wpc_en_status[1] ? "Enable" : "Disable");
power_supply_put(psy);
} else {
if (max77705->pdata->wpc_en) {
if (enable) {
gpio_direction_output(max77705->pdata->wpc_en, 0);
pr_info("%s: WC CONTROL: ENABLE\n", __func__);
} else {
gpio_direction_output(max77705->pdata->wpc_en, 1);
pr_info("%s: WC CONTROL: DISABLE\n", __func__);
}
} else {
pr_info("%s : no wpc_en\n", __func__);
}
}
pr_info("%s: wpc_en(%d)\n", __func__, gpio_get_value(max77705->pdata->wpc_en));
}
int max77705_usbc_fw_update(struct max77705_dev *max77705,
const u8 *fw_bin, int fw_bin_len, int enforce_do)
{
max77705_fw_header *fw_header;
struct otg_notify *o_notify = get_otg_notify();
int offset = 0;
unsigned long duration = 0;
int size = 0;
int try_count = 0;
int ret = 0;
u8 pd_status1 = 0x0;
static u8 fct_id; /* FCT cable */
u8 try_command = 0;
u8 sw_boot = 0;
u8 chg_cnfg_00 = 0;
bool chg_mode_changed = 0;
bool wpc_en_changed = 0;
int vcell = 0;
u8 vbvolt = 0;
u8 wcin_dtls = 0;
u8 chg_curr = 0;
u8 vchgin = 0;
int error = 0;
fw_header = (max77705_fw_header *)fw_bin;
msg_maxim("FW: magic/%x/ major/%x/ minor/%x/ product_id/%x/ rev/%x/ id/%x/",
fw_header->magic, fw_header->major, fw_header->minor, fw_header->product_id, fw_header->rev, fw_header->id);
if(max77705->device_product_id != fw_header->product_id) {
msg_maxim("product indicator mismatch");
return 0;
}
if(fw_header->magic == MAX77705_SIGN)
msg_maxim("FW: matched");
max77705_read_reg(max77705->charger, MAX77705_CHG_REG_CNFG_00, &chg_cnfg_00);
retry:
disable_irq(max77705->irq);
max77705_write_reg(max77705->muic, REG_PD_INT_M, 0xFF);
max77705_write_reg(max77705->muic, REG_CC_INT_M, 0xFF);
max77705_write_reg(max77705->muic, REG_UIC_INT_M, 0xFF);
max77705_write_reg(max77705->muic, REG_VDM_INT_M, 0xFF);
offset = 0;
duration = 0;
size = 0;
ret = 0;
/* to do (unmask interrupt) */
ret = max77705_read_reg(max77705->muic, REG_UIC_FW_REV, &max77705->FW_Revision);
ret = max77705_read_reg(max77705->muic, REG_UIC_FW_MINOR, &max77705->FW_Minor_Revision);
if (ret < 0 && (try_count == 0 && try_command == 0)) {
msg_maxim("Failed to read FW_REV");
error = -EIO;
goto out;
}
duration = jiffies;
max77705->FW_Product_ID = max77705->FW_Minor_Revision >> FW_PRODUCT_ID_REG;
max77705->FW_Minor_Revision &= MINOR_VERSION_MASK;
msg_maxim("chip : %02X.%02X(PID 0x%x), FW : %02X.%02X(PID 0x%x)",
max77705->FW_Revision, max77705->FW_Minor_Revision, max77705->FW_Product_ID,
fw_header->major, fw_header->minor, fw_header->product_id);
store_ccic_bin_version(&fw_header->major, &sw_boot);
#ifdef CONFIG_SEC_FACTORY
if ((max77705->FW_Revision != fw_header->major) || (max77705->FW_Minor_Revision != fw_header->minor)) {
#else
if ((max77705->FW_Revision == 0xff) || (max77705->FW_Revision < fw_header->major) ||
((max77705->FW_Revision == fw_header->major) && (max77705->FW_Minor_Revision < fw_header->minor)) ||
(max77705->FW_Product_ID != fw_header->product_id) ||
enforce_do) {
#endif
if (!enforce_do && max77705->pmic_rev > MAX77705_PASS1) { /* on Booting time */
max77705_read_reg(max77705->muic, REG_PD_STATUS1, &pd_status1);
fct_id = (pd_status1 & BIT_FCT_ID) >> FFS(BIT_FCT_ID);
msg_maxim("FCT_ID : 0x%x", fct_id);
}
if (try_count == 0 && try_command == 0) {
/* change chg_mode during FW update */
vcell = max77705_fuelgauge_read_vcell(max77705);
if (vcell < 3600) {
pr_info("%s: keep chg_mode(0x%x), vcell(%dmv)\n",
__func__, chg_cnfg_00 & 0x0F, vcell);
error = -EAGAIN;
goto out;
}
}
max77705_read_reg(max77705->charger, MAX77705_CHG_REG_DETAILS_00, &wcin_dtls);
wcin_dtls = (wcin_dtls & 0x18) >> 3;
wpc_en_changed = true;
max77705_wc_control(max77705, false);
max77705_read_reg(max77705->muic, MAX77705_USBC_REG_BC_STATUS, &vbvolt);
pr_info("%s: BC:0x%02x, vbvolt:0x%x, wcin_dtls:0x%x\n",
__func__, vbvolt, ((vbvolt & 0x80) >> 7), wcin_dtls);
if (!(vbvolt & 0x80) && (wcin_dtls != 0x3)) {
chg_mode_changed = true;
/* Switching Frequency : 3MHz */
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_08, 0x00, 0x3);
pr_info("%s: +set Switching Frequency 3Mhz\n", __func__);
/* Disable skip mode */
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_12, 0x1, 0x1);
pr_info("%s: +set Disable skip mode\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_00, 0x09, 0x0F);
pr_info("%s: +change chg_mode(0x9), vcell(%dmv)\n",
__func__, vcell);
} else {
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_12, 0x18, 0x18);
max77705_read_reg(max77705->charger, MAX77705_CHG_REG_CNFG_12, &vchgin);
pr_info("%s: -set aicl, (0x%02x)\n", __func__, vchgin);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_02, 0x0, 0x3F);
max77705_read_reg(max77705->charger, MAX77705_CHG_REG_CNFG_02, &chg_curr);
pr_info("%s: -set charge curr 100mA, (0x%02x)\n", __func__, chg_curr);
if (chg_mode_changed) {
chg_mode_changed = false;
/* Auto skip mode */
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_12, 0x0, 0x1);
pr_info("%s: -set Auto skip mode\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_12, 0x0, 0x20);
pr_info("%s: -disable CHGINSEL\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_00, 0x4, 0x0F);
pr_info("%s: -set chg_mode(0x4)\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_12, 0x20, 0x20);
pr_info("%s: -enable CHGINSEL\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_00, chg_cnfg_00, 0x0F);
pr_info("%s: -recover chg_mode(0x%x), vcell(%dmv)\n",
__func__, chg_cnfg_00 & 0x0F, vcell);
/* Switching Frequency : 1.5MHz */
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_08, 0x02, 0x3);
pr_info("%s: -set Switching Frequency 1.5MHz\n", __func__);
}
}
msleep(500);
max77705_write_reg(max77705->muic, 0x21, 0xD0);
max77705_write_reg(max77705->muic, 0x41, 0x00);
msleep(500);
max77705_read_reg(max77705->muic, REG_UIC_FW_REV, &max77705->FW_Revision);
max77705_read_reg(max77705->muic, REG_UIC_FW_MINOR, &max77705->FW_Minor_Revision);
max77705->FW_Minor_Revision &= MINOR_VERSION_MASK;
msg_maxim("Start FW updating (%02X.%02X)", max77705->FW_Revision, max77705->FW_Minor_Revision);
if (max77705->FW_Revision != 0xFF && try_command < 3) {
try_command++;
max77705_reset_ic(max77705);
msleep(1000);
goto retry;
}
if (max77705->FW_Revision != 0xFF) {
if (++try_count < FW_VERIFY_TRY_COUNT) {
msg_maxim("the Fail to enter secure mode %d",
try_count);
max77705_reset_ic(max77705);
msleep(1000);
goto retry;
} else {
msg_maxim("the Secure Update Fail!!");
#if defined(CONFIG_USB_HW_PARAM)
if (o_notify)
inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT);
#endif
error = -EIO;
goto out;
}
}
for (offset = FW_HEADER_SIZE;
offset < fw_bin_len && size != FW_UPDATE_END;) {
size = __max77705_usbc_fw_update(max77705, &fw_bin[offset]);
switch (size) {
case FW_UPDATE_VERIFY_FAIL:
offset -= FW_CMD_WRITE_SIZE;
/* FALLTHROUGH */
case FW_UPDATE_TIMEOUT_FAIL:
/*
* Retry FW updating
*/
if (++try_count < FW_VERIFY_TRY_COUNT) {
msg_maxim("Retry fw write. ret %d, count %d, offset %d",
size, try_count, offset);
max77705_reset_ic(max77705);
msleep(1000);
goto retry;
} else {
msg_maxim("Failed to update FW. ret %d, offset %d",
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:
msg_maxim("Failed to update FW. ret %d, offset %d",
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 */
/* JIG PIN for setting HIGH. */
if (fct_id == FCT_523Kohm && !enforce_do)
max77705_write_jig_on(max77705);
max77705_read_reg(max77705->muic,
REG_UIC_FW_REV, &max77705->FW_Revision);
max77705_read_reg(max77705->muic,
REG_UIC_FW_MINOR, &max77705->FW_Minor_Revision);
max77705->FW_Minor_Revision &= MINOR_VERSION_MASK;
msg_maxim("chip : %02X.%02X, FW : %02X.%02X",
max77705->FW_Revision, max77705->FW_Minor_Revision,
fw_header->major, fw_header->minor);
msg_maxim("Completed");
break;
default:
offset += size;
break;
}
if (offset == fw_bin_len) {
max77705_reset_ic(max77705);
max77705_read_reg(max77705->muic,
REG_UIC_FW_REV, &max77705->FW_Revision);
max77705_read_reg(max77705->muic,
REG_UIC_FW_MINOR, &max77705->FW_Minor_Revision);
max77705->FW_Minor_Revision &= MINOR_VERSION_MASK;
msg_maxim("chip : %02X.%02X, FW : %02X.%02X",
max77705->FW_Revision, max77705->FW_Minor_Revision,
fw_header->major, fw_header->minor);
msg_maxim("Completed via SYS path");
}
}
} else {
msg_maxim("Don't need to update!");
goto out;
}
duration = jiffies - duration;
msg_maxim("Duration : %dms", jiffies_to_msecs(duration));
out:
if (chg_mode_changed) {
vcell = max77705_fuelgauge_read_vcell(max77705);
/* Auto skip mode */
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_12, 0x0, 0x1);
pr_info("%s: -set Auto skip mode\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_12, 0x0, 0x20);
pr_info("%s: -disable CHGINSEL\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_00, 0x4, 0x0F);
pr_info("%s: -set chg_mode(0x4)\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_12, 0x20, 0x20);
pr_info("%s: -enable CHGINSEL\n", __func__);
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_00, chg_cnfg_00, 0x0F);
pr_info("%s: -recover chg_mode(0x%x), vcell(%dmv)\n",
__func__, chg_cnfg_00 & 0x0F, vcell);
/* Switching Frequency : 1.5MHz */
max77705_update_reg(max77705->charger,
MAX77705_CHG_REG_CNFG_08, 0x02, 0x3);
pr_info("%s: -set Switching Frequency 1.5MHz\n", __func__);
}
if (wpc_en_changed) {
max77705_wc_control(max77705, true);
}
enable_irq(max77705->irq);
return error;
}
EXPORT_SYMBOL_GPL(max77705_usbc_fw_update);
void max77705_usbc_fw_setting(struct max77705_dev *max77705, int enforce_do)
{
switch (max77705->pmic_rev) {
case MAX77705_PASS1:
pr_info("[MAX77705] Doesn't update the MAX77705_PASS1\n");
break;
case MAX77705_PASS2:
pr_info("[MAX77705] Doesn't update the MAX77705_PASS2\n");
break;
case MAX77705_PASS3:
max77705_usbc_fw_update(max77705, BOOT_FLASH_FW_PASS3, ARRAY_SIZE(BOOT_FLASH_FW_PASS3), enforce_do);
break;
case MAX77705_PASS4:
max77705_usbc_fw_update(max77705, BOOT_FLASH_FW_PASS4, ARRAY_SIZE(BOOT_FLASH_FW_PASS4), enforce_do);
break;
case MAX77705_PASS5:
max77705_usbc_fw_update(max77705, BOOT_FLASH_FW_PASS2, ARRAY_SIZE(BOOT_FLASH_FW_PASS2), enforce_do);
break;
default:
pr_info("[MAX77705] FAIL F/W ON BOOTING TIME and PMIC_REVISION isn't valid\n");
break;
};
}
EXPORT_SYMBOL_GPL(max77705_usbc_fw_setting);
#endif
static u8 max77705_revision_check(u8 pmic_id, u8 pmic_rev) {
int i, logical_id = 0;
int pmic_arrary = ARRAY_SIZE(max77705_revision);
for (i = 0; i < pmic_arrary; i++) {
if (max77705_revision[i].id == pmic_id &&
max77705_revision[i].rev == pmic_rev)
logical_id = max77705_revision[i].logical_id;
}
return logical_id;
}
void max77705_register_pdmsg_func(struct max77705_dev *max77705,
void (*check_pdmsg)(void *data, u8 pdmsg), void *data)
{
if (!max77705) {
pr_err("%s max77705 is null\n", __func__);
return;
}
max77705->check_pdmsg = check_pdmsg;
max77705->usbc_data = data;
}
EXPORT_SYMBOL_GPL(max77705_register_pdmsg_func);
static int max77705_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *dev_id)
{
struct max77705_dev *max77705;
struct max77705_platform_data *pdata = i2c->dev.platform_data;
int ret = 0;
u8 pmic_id, pmic_rev = 0;
pr_info("%s:%s\n", MFD_DEV_NAME, __func__);
max77705 = kzalloc(sizeof(struct max77705_dev), GFP_KERNEL);
if (!max77705)
return -ENOMEM;
if (i2c->dev.of_node) {
pdata = devm_kzalloc(&i2c->dev, sizeof(struct max77705_platform_data),
GFP_KERNEL);
if (!pdata) {
ret = -ENOMEM;
goto err;
}
ret = of_max77705_dt(&i2c->dev, pdata);
if (ret < 0) {
dev_err(&i2c->dev, "Failed to get device of_node\n");
goto err;
}
i2c->dev.platform_data = pdata;
} else
pdata = i2c->dev.platform_data;
max77705->dev = &i2c->dev;
max77705->i2c = i2c;
max77705->irq = i2c->irq;
if (pdata) {
max77705->pdata = pdata;
pdata->irq_base = irq_alloc_descs(-1, 0, MAX77705_IRQ_NR, -1);
if (pdata->irq_base < 0) {
pr_err("%s:%s irq_alloc_descs Fail! ret(%d)\n",
MFD_DEV_NAME, __func__, pdata->irq_base);
ret = -EINVAL;
goto err;
} else
max77705->irq_base = pdata->irq_base;
max77705->irq_gpio = pdata->irq_gpio;
max77705->wakeup = pdata->wakeup;
max77705->blocking_waterevent = pdata->blocking_waterevent;
max77705->device_product_id = pdata->fw_product_id;
} else {
ret = -EINVAL;
goto err;
}
mutex_init(&max77705->i2c_lock);
i2c_set_clientdata(i2c, max77705);
if (max77705_read_reg(i2c, MAX77705_PMIC_REG_PMICID1, &pmic_id) < 0) {
dev_err(max77705->dev, "device not found on this channel (this is not an error)\n");
ret = -ENODEV;
goto err_w_lock;
}
if (max77705_read_reg(i2c, MAX77705_PMIC_REG_PMICREV, &pmic_rev) < 0) {
dev_err(max77705->dev, "device not found on this channel (this is not an error)\n");
ret = -ENODEV;
goto err_w_lock;
}
pr_info("%s:%s pmic_id:%x, pmic_rev:%x\n",
MFD_DEV_NAME, __func__, pmic_id, pmic_rev);
max77705->pmic_rev = max77705_revision_check(pmic_id, pmic_rev & 0x7);
if (max77705->pmic_rev == 0) {
dev_err(max77705->dev, "Can not find matched revision\n");
ret = -ENODEV;
goto err_w_lock;
}
max77705->pmic_ver = ((pmic_rev & 0xF8) >> 0x3);
/* print rev */
pr_info("%s:%s device found: rev:%x ver:%x\n",
MFD_DEV_NAME, __func__, max77705->pmic_rev, max77705->pmic_ver);
/* No active discharge on safeout ldo 1,2 */
/* max77705_update_reg(i2c, MAX77705_PMIC_REG_SAFEOUT_CTRL, 0x00, 0x30); */
#ifdef CONFIG_CCIC_MAX77705
init_completion(&max77705->fw_completion);
max77705->fw_workqueue = create_singlethread_workqueue("fw_update");
if (max77705->fw_workqueue == NULL)
return -ENOMEM;
INIT_WORK(&max77705->fw_work, max77705_usbc_wait_response_q);
#endif
max77705->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC);
i2c_set_clientdata(max77705->muic, max77705);
max77705->charger = i2c_new_dummy(i2c->adapter, I2C_ADDR_CHG);
i2c_set_clientdata(max77705->charger, max77705);
max77705->fuelgauge = i2c_new_dummy(i2c->adapter, I2C_ADDR_FG);
i2c_set_clientdata(max77705->fuelgauge, max77705);
#ifdef CONFIG_CCIC_MAX77705
max77705_usbc_fw_setting(max77705, 0);
#endif
max77705->debug = i2c_new_dummy(i2c->adapter, I2C_ADDR_DEBUG);
i2c_set_clientdata(max77705->debug, max77705);
disable_irq(max77705->irq);
ret = max77705_irq_init(max77705);
if (ret < 0)
goto err_irq_init;
ret = mfd_add_devices(max77705->dev, -1, max77705_devs,
ARRAY_SIZE(max77705_devs), NULL, 0, NULL);
if (ret < 0)
goto err_mfd;
device_init_wakeup(max77705->dev, pdata->wakeup);
return ret;
err_mfd:
mfd_remove_devices(max77705->dev);
err_irq_init:
i2c_unregister_device(max77705->muic);
i2c_unregister_device(max77705->charger);
i2c_unregister_device(max77705->fuelgauge);
i2c_unregister_device(max77705->debug);
err_w_lock:
mutex_destroy(&max77705->i2c_lock);
err:
kfree(max77705);
return ret;
}
static int max77705_i2c_remove(struct i2c_client *i2c)
{
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
device_init_wakeup(max77705->dev, 0);
max77705_irq_exit(max77705);
mfd_remove_devices(max77705->dev);
i2c_unregister_device(max77705->muic);
i2c_unregister_device(max77705->charger);
i2c_unregister_device(max77705->fuelgauge);
i2c_unregister_device(max77705->debug);
kfree(max77705);
return 0;
}
static const struct i2c_device_id max77705_i2c_id[] = {
{ MFD_DEV_NAME, TYPE_MAX77705 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max77705_i2c_id);
#if defined(CONFIG_OF)
static const struct of_device_id max77705_i2c_dt_ids[] = {
{ .compatible = "maxim,max77705" },
{ },
};
MODULE_DEVICE_TABLE(of, max77705_i2c_dt_ids);
#endif /* CONFIG_OF */
#if defined(CONFIG_PM)
static int max77705_suspend(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
pr_info("%s:%s\n", MFD_DEV_NAME, __func__);
if (device_may_wakeup(dev))
enable_irq_wake(max77705->irq);
disable_irq(max77705->irq);
return 0;
}
static int max77705_resume(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
pr_info("%s:%s\n", MFD_DEV_NAME, __func__);
if (device_may_wakeup(dev))
disable_irq_wake(max77705->irq);
enable_irq(max77705->irq);
return 0;
}
#else
#define max77705_suspend NULL
#define max77705_resume NULL
#endif /* CONFIG_PM */
#ifdef CONFIG_HIBERNATION
#if 0
u8 max77705_dumpaddr_pmic[] = {
#if 0
MAX77705_LED_REG_IFLASH,
MAX77705_LED_REG_IFLASH1,
MAX77705_LED_REG_IFLASH2,
MAX77705_LED_REG_ITORCH,
MAX77705_LED_REG_ITORCHTORCHTIMER,
MAX77705_LED_REG_FLASH_TIMER,
MAX77705_LED_REG_FLASH_EN,
MAX77705_LED_REG_MAX_FLASH1,
MAX77705_LED_REG_MAX_FLASH2,
MAX77705_LED_REG_VOUT_CNTL,
MAX77705_LED_REG_VOUT_FLASH,
MAX77705_LED_REG_VOUT_FLASH1,
MAX77705_LED_REG_FLASH_INT_STATUS,
#endif
MAX77705_PMIC_REG_PMICID1,
MAX77705_PMIC_REG_PMICREV,
MAX77705_PMIC_REG_MAINCTRL1,
MAX77705_PMIC_REG_MCONFIG,
};
#endif
u8 max77705_dumpaddr_muic[] = {
MAX77705_MUIC_REG_INTMASK_MAIN,
MAX77705_MUIC_REG_INTMASK_BC,
MAX77705_MUIC_REG_INTMASK_FC,
MAX77705_MUIC_REG_INTMASK_GP,
MAX77705_MUIC_REG_STATUS1_BC,
MAX77705_MUIC_REG_STATUS2_BC,
MAX77705_MUIC_REG_STATUS_GP,
MAX77705_MUIC_REG_CONTROL1_BC,
MAX77705_MUIC_REG_CONTROL2_BC,
MAX77705_MUIC_REG_CONTROL1,
MAX77705_MUIC_REG_CONTROL2,
MAX77705_MUIC_REG_CONTROL3,
MAX77705_MUIC_REG_CONTROL4,
MAX77705_MUIC_REG_HVCONTROL1,
MAX77705_MUIC_REG_HVCONTROL2,
};
#if 0
u8 max77705_dumpaddr_haptic[] = {
MAX77705_HAPTIC_REG_CONFIG1,
MAX77705_HAPTIC_REG_CONFIG2,
MAX77705_HAPTIC_REG_CONFIG_CHNL,
MAX77705_HAPTIC_REG_CONFG_CYC1,
MAX77705_HAPTIC_REG_CONFG_CYC2,
MAX77705_HAPTIC_REG_CONFIG_PER1,
MAX77705_HAPTIC_REG_CONFIG_PER2,
MAX77705_HAPTIC_REG_CONFIG_PER3,
MAX77705_HAPTIC_REG_CONFIG_PER4,
MAX77705_HAPTIC_REG_CONFIG_DUTY1,
MAX77705_HAPTIC_REG_CONFIG_DUTY2,
MAX77705_HAPTIC_REG_CONFIG_PWM1,
MAX77705_HAPTIC_REG_CONFIG_PWM2,
MAX77705_HAPTIC_REG_CONFIG_PWM3,
MAX77705_HAPTIC_REG_CONFIG_PWM4,
};
#endif
u8 max77705_dumpaddr_led[] = {
MAX77705_RGBLED_REG_LEDEN,
MAX77705_RGBLED_REG_LED0BRT,
MAX77705_RGBLED_REG_LED1BRT,
MAX77705_RGBLED_REG_LED2BRT,
MAX77705_RGBLED_REG_LED3BRT,
MAX77705_RGBLED_REG_LEDBLNK,
MAX77705_RGBLED_REG_LEDRMP,
};
static int max77705_freeze(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
int i;
for (i = 0; i < ARRAY_SIZE(max77705_dumpaddr_pmic); i++)
max77705_read_reg(i2c, max77705_dumpaddr_pmic[i],
&max77705->reg_pmic_dump[i]);
for (i = 0; i < ARRAY_SIZE(max77705_dumpaddr_muic); i++)
max77705_read_reg(i2c, max77705_dumpaddr_muic[i],
&max77705->reg_muic_dump[i]);
for (i = 0; i < ARRAY_SIZE(max77705_dumpaddr_led); i++)
max77705_read_reg(i2c, max77705_dumpaddr_led[i],
&max77705->reg_led_dump[i]);
disable_irq(max77705->irq);
return 0;
}
static int max77705_restore(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct max77705_dev *max77705 = i2c_get_clientdata(i2c);
int i;
enable_irq(max77705->irq);
for (i = 0; i < ARRAY_SIZE(max77705_dumpaddr_pmic); i++)
max77705_write_reg(i2c, max77705_dumpaddr_pmic[i],
max77705->reg_pmic_dump[i]);
for (i = 0; i < ARRAY_SIZE(max77705_dumpaddr_muic); i++)
max77705_write_reg(i2c, max77705_dumpaddr_muic[i],
max77705->reg_muic_dump[i]);
for (i = 0; i < ARRAY_SIZE(max77705_dumpaddr_led); i++)
max77705_write_reg(i2c, max77705_dumpaddr_led[i],
max77705->reg_led_dump[i]);
return 0;
}
#endif
const struct dev_pm_ops max77705_pm = {
.suspend = max77705_suspend,
.resume = max77705_resume,
#ifdef CONFIG_HIBERNATION
.freeze = max77705_freeze,
.thaw = max77705_restore,
.restore = max77705_restore,
#endif
};
static struct i2c_driver max77705_i2c_driver = {
.driver = {
.name = MFD_DEV_NAME,
.owner = THIS_MODULE,
#if defined(CONFIG_PM)
.pm = &max77705_pm,
#endif /* CONFIG_PM */
#if defined(CONFIG_OF)
.of_match_table = max77705_i2c_dt_ids,
#endif /* CONFIG_OF */
},
.probe = max77705_i2c_probe,
.remove = max77705_i2c_remove,
.id_table = max77705_i2c_id,
};
static int __init max77705_i2c_init(void)
{
pr_info("%s:%s\n", MFD_DEV_NAME, __func__);
return i2c_add_driver(&max77705_i2c_driver);
}
/* init early so consumer devices can complete system boot */
subsys_initcall(max77705_i2c_init);
static void __exit max77705_i2c_exit(void)
{
i2c_del_driver(&max77705_i2c_driver);
}
module_exit(max77705_i2c_exit);
MODULE_DESCRIPTION("MAXIM 77705 multi-function core driver");
MODULE_AUTHOR("Insun Choi <insun77.choi@samsung.com>");
MODULE_LICENSE("GPL");