blob: b1f7b54014dde81068ff277c27b3adc1cf52004b [file] [log] [blame]
/*
* mfc_charger.c
* Samsung MFC IC Charger Driver
*
* Copyright (C) 2016 Samsung Electronics
* Jungmin Lee <jmru.lee@samsung.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include "include/charger/mfc_charger.h"
#include <linux/errno.h>
#include <linux/version.h>
#include <linux/device.h>
#include <linux/pm.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#include <linux/ctype.h>
#include <linux/firmware.h>
#define ENABLE 1
#define DISABLE 0
#define CMD_CNT 3
int mfc_otp_update = 0;
extern bool sleep_mode;
static enum power_supply_property mfc_charger_props[] = {
};
extern unsigned int lpcharge;
int mfc_get_firmware_version(struct mfc_charger_data *charger, int firm_mode);
static irqreturn_t mfc_wpc_det_irq_thread(int irq, void *irq_data);
static irqreturn_t mfc_wpc_irq_thread(int irq, void *irq_data);
static int mfc_reg_read(struct i2c_client *client, u16 reg, u8 *val)
{
struct mfc_charger_data *charger = i2c_get_clientdata(client);
int ret;
struct i2c_msg msg[2];
u8 wbuf[2];
u8 rbuf[2];
msg[0].addr = client->addr;
msg[0].flags = client->flags & I2C_M_TEN;
msg[0].len = 2;
msg[0].buf = wbuf;
wbuf[0] = (reg & 0xFF00) >> 8;
wbuf[1] = (reg & 0xFF);
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = rbuf;
mutex_lock(&charger->io_lock);
ret = i2c_transfer(client->adapter, msg, 2);
mutex_unlock(&charger->io_lock);
if (ret < 0)
{
pr_err("%s: i2c read error, reg: 0x%x, ret: %d (called by %ps)\n",
__func__, reg, ret, __builtin_return_address(0));
return -1;
}
*val = rbuf[0];
return ret;
}
static int mfc_reg_multi_read(struct i2c_client *client, u16 reg, u8 *val, int size)
{
struct mfc_charger_data *charger = i2c_get_clientdata(client);
int ret;
struct i2c_msg msg[2];
u8 wbuf[2];
msg[0].addr = client->addr;
msg[0].flags = client->flags & I2C_M_TEN;
msg[0].len = 2;
msg[0].buf = wbuf;
wbuf[0] = (reg & 0xFF00) >> 8;
wbuf[1] = (reg & 0xFF);
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = size;
msg[1].buf = val;
mutex_lock(&charger->io_lock);
ret = i2c_transfer(client->adapter, msg, 2);
mutex_unlock(&charger->io_lock);
if (ret < 0)
{
pr_err("%s: i2c transfer fail", __func__);
return -1;
}
return ret;
}
static int mfc_reg_write(struct i2c_client *client, u16 reg, u8 val)
{
struct mfc_charger_data *charger = i2c_get_clientdata(client);
int ret;
unsigned char data[3] = { reg >> 8, reg & 0xff, val };
mutex_lock(&charger->io_lock);
ret = i2c_master_send(client, data, 3);
mutex_unlock(&charger->io_lock);
if (ret < 3) {
pr_err("%s: i2c write error, reg: 0x%x, ret: %d (called by %ps)\n",
__func__, reg, ret, __builtin_return_address(0));
return ret < 0 ? ret : -EIO;
}
return 0;
}
static int mfc_reg_update(struct i2c_client *client, u16 reg, u8 val, u8 mask)
{
//val = 0b 0000 0001
//ms = 0b 1111 1110
struct mfc_charger_data *charger = i2c_get_clientdata(client);
unsigned char data[3] = { reg >> 8, reg & 0xff, val };
u8 data2;
int ret;
ret = mfc_reg_read(client, reg, &data2);
if (ret >= 0) {
u8 old_val = data2 & 0xff;
u8 new_val = (val & mask) | (old_val & (~mask));
data[2] = new_val;
mutex_lock(&charger->io_lock);
ret = i2c_master_send(client, data, 3);
mutex_unlock(&charger->io_lock);
if (ret < 3) {
pr_err("%s: i2c write error, reg: 0x%x, ret: %d\n",
__func__, reg, ret);
return ret < 0 ? ret : -EIO;
}
}
mfc_reg_read(client, reg, &data2);
return ret;
}
////
int mfc_get_firmware_version(struct mfc_charger_data *charger, int firm_mode)
{
int version = -1;
int ret;
u8 fw_major[2] = {0,};
u8 fw_minor[2] = {0,};
pr_info("%s: called by (%ps)\n", __func__, __builtin_return_address(0));
switch (firm_mode) {
case MFC_RX_FIRMWARE:
ret = mfc_reg_read(charger->client, MFC_FW_MAJOR_REV_L_REG, &fw_major[0]);
ret = mfc_reg_read(charger->client, MFC_FW_MAJOR_REV_H_REG, &fw_major[1]);
if (ret >= 0) {
version = fw_major[0] | (fw_major[1] << 8);
}
pr_info("%s rx major firmware version 0x%x \n", __func__, version);
ret = mfc_reg_read(charger->client, MFC_FW_MINOR_REV_L_REG, &fw_minor[0]);
ret = mfc_reg_read(charger->client, MFC_FW_MINOR_REV_H_REG, &fw_minor[1]);
if (ret >= 0) {
version = fw_minor[0] | (fw_minor[1] << 8);
}
pr_info("%s rx minor firmware version 0x%x \n", __func__, version);
break;
default:
pr_err("%s Wrong firmware mode \n", __func__);
version = -1;
break;
}
return version;
}
int mfc_get_chip_id(struct mfc_charger_data *charger)
{
u8 chip_id;
mfc_reg_read(charger->client, MFC_CHIP_ID_L_REG, &chip_id);
if (chip_id == 0x40) {
charger->chip_id = MFC_CHIP_LSI;
pr_info("%s: LSI CHIP\n", __func__);
} else { /* 0x20 */
charger->chip_id = MFC_CHIP_IDT;
pr_info("%s: IDT CHIP\n", __func__);
}
return charger->chip_id;
}
int mfc_get_ic_revision(struct mfc_charger_data *charger, int read_mode)
{
u8 temp;
int ret;
pr_info("%s: called by (%ps)\n", __func__, __builtin_return_address(0));
switch (read_mode) {
case MFC_IC_REVISION:
ret = mfc_reg_read(charger->client, MFC_CHIP_REVISION_REG, &temp);
if(ret >= 0) {
temp &= MFC_CHIP_REVISION_MASK;
pr_info("%s ic revision = %d \n", __func__, temp);
ret = temp;
}
else
ret = -1;
break;
case MFC_IC_FONT:
ret = mfc_reg_read(charger->client, MFC_CHIP_REVISION_REG, &temp);
if(ret >= 0) {
temp &= MFC_CHIP_FONT_MASK;
pr_info("%s ic font = %d \n", __func__, temp);
ret = temp;
}
else
ret = -1;
break;
default :
ret = -1;
break;
}
return ret;
}
int mfc_get_adc(struct mfc_charger_data *charger, int adc_type)
{
int ret = 0;
u8 data[2] = {0,};
switch (adc_type) {
case MFC_ADC_VOUT:
ret = mfc_reg_read(charger->client, MFC_ADC_VOUT_L_REG, &data[0]);
ret = mfc_reg_read(charger->client, MFC_ADC_VOUT_H_REG, &data[1]);
if(ret >= 0 ) {
ret = (data[0] | (data[1] << 8));
} else
ret = -1;
break;
case MFC_ADC_VRECT:
ret = mfc_reg_read(charger->client, MFC_ADC_VRECT_L_REG, &data[0]);
ret = mfc_reg_read(charger->client, MFC_ADC_VRECT_H_REG, &data[1]);
if(ret >= 0 ) {
ret = (data[0] | (data[1] << 8));
} else
ret = -1;
break;
case MFC_ADC_TX_ISENSE:
ret = mfc_reg_read(charger->client, MFC_ADC_TX_ISENSE_L_REG, &data[0]);
ret = mfc_reg_read(charger->client, MFC_ADC_TX_ISENSE_H_REG, &data[1]);
if(ret >= 0 ) {
ret = (data[0] | (data[1] << 8));
} else
ret = -1;
break;
case MFC_ADC_RX_IOUT:
ret = mfc_reg_read(charger->client, MFC_ADC_RX_IOUT_L_REG, &data[0]);
ret = mfc_reg_read(charger->client, MFC_ADC_RX_IOUT_H_REG, &data[1]);
if(ret >= 0 ) {
ret = (data[0] | (data[1] << 8));
} else
ret = -1;
break;
case MFC_ADC_DIE_TEMP:
/* only 4 MSB[3:0] field is used, Celsius */
ret = mfc_reg_read(charger->client, MFC_ADC_DIE_TEMP_L_REG, &data[0]);
ret = mfc_reg_read(charger->client, MFC_ADC_DIE_TEMP_H_REG, &data[1]);
if(ret >= 0 ) {
data[1] &= 0x0f;
ret = (data[0] | (data[1] << 8));
} else
ret = -1;
break;
case MFC_ADC_OP_FRQ: /* kHz */
ret = mfc_reg_read(charger->client, MFC_RX_OP_FREQ_L_REG, &data[0]);
ret = mfc_reg_read(charger->client, MFC_RX_OP_FREQ_H_REG, &data[1]);
if(ret >= 0 ) {
ret = (data[0] | (data[1] << 8));
} else
ret = -1;
break;
case MFC_ADC_PING_FRQ:
ret = mfc_reg_read(charger->client, MFC_RX_PING_FREQ_L_REG, &data[0]);
ret = mfc_reg_read(charger->client, MFC_RX_PING_FREQ_H_REG, &data[0]);
if(ret >= 0 ) {
ret = (data[0] | (data[1] << 8));
} else
ret = -1;
default:
break;
}
return ret;
}
void mfc_set_vout(struct mfc_charger_data *charger, int vout)
{
switch (vout) {
case MFC_VOUT_5V:
case MFC_VOUT_5_5V:
case MFC_VOUT_6V:
case MFC_VOUT_7V:
case MFC_VOUT_8V:
case MFC_VOUT_9V:
case MFC_VOUT_10V:
if (charger->chip_id == MFC_CHIP_IDT)
mfc_reg_write(charger->client, MFC_VOUT_SET_REG, mfc_idt_vout_val[vout]);
else /* LSI */
mfc_reg_write(charger->client, MFC_VOUT_SET_REG, mfc_lsi_vout_val[vout]);
msleep(100);
break;
default:
break;
}
pr_info("%s vout(%d) read = %d mV \n", __func__, vout, mfc_get_adc(charger, MFC_ADC_VOUT));
charger->pdata->vout_status = vout;
}
int mfc_get_vout(struct mfc_charger_data *charger)
{
u8 data;
int ret;
ret = mfc_reg_read(charger->client, MFC_VOUT_SET_REG, &data);
if (ret < 0) {
pr_err("%s: fail to read vout. (%d)\n", __func__, ret);
return ret;
} else
pr_info("%s: vout(0x%x)\n", __func__, data);
return data;
}
void mfc_rpp_set(struct mfc_charger_data *charger)
{
u8 data;
int ret;
if (charger->led_cover) {
pr_info("%s: LED cover exists. RPP 3/4 (0x%x)\n", __func__, charger->pdata->wc_cover_rpp);
mfc_reg_write(charger->client, MFC_RPP_SCALE_COEF_REG, charger->pdata->wc_cover_rpp);
} else {
pr_info("%s: LED cover not exists. RPP 1/2 (0x%x)\n", __func__, charger->pdata->wc_hv_rpp);
mfc_reg_write(charger->client, MFC_RPP_SCALE_COEF_REG, charger->pdata->wc_hv_rpp);
}
msleep(5);
ret = mfc_reg_read(charger->client, MFC_RPP_SCALE_COEF_REG, &data);
if (ret < 0) {
pr_err("%s: fail to read RPP scaling coefficient. (%d)\n", __func__, ret);
} else
pr_info("%s: RPP scaling coefficient(0x%x)\n", __func__, data);
}
void mfc_fod_set(struct mfc_charger_data *charger)
{
int i = 0;
pr_info("%s \n", __func__);
switch (charger->pdata->cable_type) {
case MFC_PAD_A4WP:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_A4WP_FOD_0A_REG+i, charger->pdata->fod_a4wp_data[i]);
}
break;
/* need to unify WPC and PMA */
case MFC_PAD_PMA:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_PMA_FOD_0A_REG+i, charger->pdata->fod_pma_data[i]);
}
break;
case MFC_PAD_WPC:
case MFC_PAD_WPC_AFC:
case MFC_PAD_WPC_PACK:
case MFC_PAD_WPC_PACK_TA:
case MFC_PAD_WPC_STAND:
case MFC_PAD_WPC_STAND_HV:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_WPC_FOD_0A_REG+i, charger->pdata->fod_wpc_data[i]);
}
break;
case MFC_PAD_NONE:
default:
break;
}
}
void mfc_fod_set_cv(struct mfc_charger_data *charger)
{
int i = 0;
pr_info("%s \n", __func__);
switch (charger->pdata->cable_type) {
case MFC_PAD_A4WP:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_A4WP_FOD_0A_REG+i, charger->pdata->fod_a4wp_data_cv[i]);
}
break;
/* need to unify WPC and PMA */
case MFC_PAD_PMA:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_PMA_FOD_0A_REG+i, charger->pdata->fod_pma_data_cv[i]);
}
break;
case MFC_PAD_WPC:
case MFC_PAD_WPC_AFC:
case MFC_PAD_WPC_PACK:
case MFC_PAD_WPC_PACK_TA:
case MFC_PAD_WPC_STAND:
case MFC_PAD_WPC_STAND_HV:
case MFC_PAD_NONE:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_WPC_FOD_0A_REG+i, charger->pdata->fod_wpc_data_cv[i]);
}
break;
default:
break;
}
}
void mfc_fod_set_cs100(struct mfc_charger_data *charger)
{
int i = 0;
pr_info("%s \n", __func__);
switch (charger->pdata->cable_type) {
case MFC_PAD_A4WP:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_A4WP_FOD_0A_REG+i, 0x7f);
}
break;
/* need to unify WPC and PMA */
case MFC_PAD_PMA:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_PMA_FOD_0A_REG+i, 0x7f);
}
break;
case MFC_PAD_WPC:
case MFC_PAD_WPC_AFC:
case MFC_PAD_WPC_PACK:
case MFC_PAD_WPC_PACK_TA:
case MFC_PAD_WPC_STAND:
case MFC_PAD_WPC_STAND_HV:
case MFC_PAD_NONE:
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_WPC_FOD_0A_REG+i, 0x7f);
}
break;
default:
break;
}
}
void mfc_fod_set_hero_5v(struct mfc_charger_data *charger)
{
int i = 0;
u8 fod[12] = {0, };
pr_info("%s \n", __func__);
if (charger->pdata->fod_hero_5v_data) {
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_write(charger->client, MFC_WPC_FOD_0A_REG+i, charger->pdata->fod_hero_5v_data[i]);
}
msleep(2);
for(i=0; i< MFC_NUM_FOD_REG; i++) {
mfc_reg_read(charger->client, MFC_WPC_FOD_0A_REG+i, &fod[i]);
}
pr_info("%s: HERO 5V FOD(%d %d %d %d %d %d %d %d %d %d %d %d)\n", __func__,
fod[0], fod[1], fod[2], fod[3], fod[4], fod[5], fod[6], fod[7], fod[8], fod[9], fod[10], fod[11]);
}
}
void mfc_set_cmd_l_reg(struct mfc_charger_data *charger, u8 val, u8 mask)
{
u8 temp = 0;
int ret = 0, i = 0;
do {
pr_info("%s \n", __func__);
ret = mfc_reg_update(charger->client, MFC_AP2MFC_CMD_L_REG, val, mask); // command
if(ret >= 0) {
ret = mfc_reg_read(charger->client, MFC_AP2MFC_CMD_L_REG, &temp); // check out set bit exists
if(ret < 0 || i > 3 )
break;
}
i++;
} while ((temp != 0) && (i < 3));
}
void mfc_set_cmd_h_reg(struct mfc_charger_data *charger, u8 val, u8 mask)
{
u8 temp = 0;
int ret = 0, i = 0;
do {
pr_info("%s \n", __func__);
ret = mfc_reg_update(charger->client, MFC_AP2MFC_CMD_H_REG, val, mask); // command
if(ret >= 0) {
msleep(250);
ret = mfc_reg_read(charger->client, MFC_AP2MFC_CMD_H_REG, &temp); // check out set bit exists
if(ret < 0 || i > 3 )
break;
}
i++;
} while ((temp != 0) && (i < 3));
}
void mfc_send_eop(struct mfc_charger_data *charger, int health_mode)
{
int i = 0;
int ret = 0;
pr_info("%s: health_mode (0x%x)\n", __func__, health_mode);
switch(health_mode) {
case POWER_SUPPLY_HEALTH_OVERHEAT:
case POWER_SUPPLY_HEALTH_OVERHEATLIMIT:
case POWER_SUPPLY_HEALTH_COLD:
if (charger->pdata->cable_type == MFC_PAD_PMA) {
pr_info("%s pma mode\n", __func__);
for (i = 0; i < CMD_CNT; i++) {
ret = mfc_reg_write(charger->client, MFC_EPT_REG, MFC_WPC_EPT_END_OF_CHG);
if (ret >= 0) {
mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_EOP_MASK, MFC_CMD_SEND_EOP_MASK);
msleep(250);
} else
break;
}
} else if (charger->pdata->cable_type == MFC_PAD_A4WP) {
pr_info("%s a4wp mode\n", __func__);
} else {
pr_info("%s wpc mode\n", __func__);
for (i = 0; i < CMD_CNT; i++) {
ret = mfc_reg_write(charger->client, MFC_EPT_REG, MFC_WPC_EPT_OVER_TEMP);
if (ret >= 0) {
mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_EOP_MASK, MFC_CMD_SEND_EOP_MASK);
msleep(250);
} else
break;
}
}
break;
case POWER_SUPPLY_HEALTH_UNDERVOLTAGE:
break;
default:
break;
}
}
void mfc_send_packet(struct mfc_charger_data *charger, u8 header, u8 rx_data_com, u8 *data_val, int data_size)
{
int i;
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
/* set AP2BT COM, and VALUE, and trigger INT_B */
mfc_reg_write(charger->client, MFC_AP2BT_DATA_COM_REG, rx_data_com);
for(i = 0; i< data_size; i++) {
mfc_reg_write(charger->client, MFC_AP2BT_DATA_VALUE0_REG+ i, data_val[i]);
}
mfc_set_cmd_l_reg(charger, MFC_CMD_AP2BT_DATA_MASK, MFC_CMD_AP2BT_DATA_MASK);
} else {
mfc_reg_write(charger->client, MFC_WPC_PCKT_HEADER_REG, header);
mfc_reg_write(charger->client, MFC_WPC_RX_DATA_COM_REG, rx_data_com);
for(i = 0; i< data_size; i++) {
mfc_reg_write(charger->client, MFC_WPC_RX_DATA_VALUE0_REG+ i, data_val[i]);
}
mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_RX_DATA_MASK, MFC_CMD_SEND_RX_DATA_MASK);
}
}
int mfc_send_cs100(struct mfc_charger_data *charger)
{
int i = 0;
int ret = 0;
u8 data_val[2];
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
data_val[0] = 0x80; /* charge complete */
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, AP2BT_COM_CHG_STATUS, data_val, 1);
}
for(i = 0; i < CMD_CNT; i++) {
ret = mfc_reg_write(charger->client, MFC_BATTERY_CHG_STATUS_REG, 100);
if(ret >= 0) {
mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_CHG_STS_MASK, MFC_CMD_SEND_CHG_STS_MASK);
msleep(250);
ret = 1;
} else {
ret = -1;
break;
}
}
return ret;
}
void mfc_send_command(struct mfc_charger_data *charger, int cmd_mode)
{
u8 data_val[4];
u8 cmd = 0;
switch (cmd_mode) {
case MFC_AFC_CONF_5V:
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
pr_info("%s: A4WP set 5V\n", __func__);
cmd = AP2BT_COM_AFC_MODE;
data_val[0] = 0x00; /* Value for A4WP AFC_SET 5V */
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
msleep(120);
charger->vout_mode = WIRELESS_VOUT_5V;
cancel_delayed_work(&charger->wpc_vout_mode_work);
wake_lock(&charger->wpc_vout_mode_lock);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, 0);
pr_info("%s vout read = %d\n", __func__, mfc_get_adc(charger, MFC_ADC_VOUT));
mfc_reg_read(charger->client, MFC_AP2BT_DATA_COM_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_AP2BT_DATA_VALUE0_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_AP2MFC_CMD_L_REG, &data_val[0]);
} else {
pr_info("%s: WPC/PMA set 5V\n", __func__);
cmd = WPC_COM_AFC_SET;
data_val[0] = 0x05; /* Value for WPC AFC_SET 5V */
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
msleep(120);
charger->vout_mode = WIRELESS_VOUT_5V;
cancel_delayed_work(&charger->wpc_vout_mode_work);
wake_lock(&charger->wpc_vout_mode_lock);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, 0);
pr_info("%s vout read = %d\n", __func__, mfc_get_adc(charger, MFC_ADC_VOUT));
mfc_reg_read(charger->client, MFC_WPC_RX_DATA_COM_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_WPC_RX_DATA_VALUE0_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_AP2MFC_CMD_L_REG, &data_val[0]);
}
break;
case MFC_AFC_CONF_10V:
if (charger->pdata->cable_type == MFC_PAD_A4WP) { /* PAD : A4WP */
pr_info("%s: A4WP set 10V\n", __func__);
cmd = AP2BT_COM_AFC_MODE;
data_val[0] = 0x01; /* Value for A4WP AFC_SET 10V */
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
msleep(120);
charger->vout_mode = WIRELESS_VOUT_10V;
cancel_delayed_work(&charger->wpc_vout_mode_work);
wake_lock(&charger->wpc_vout_mode_lock);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, 0);
pr_info("%s vout read = %d\n", __func__, mfc_get_adc(charger, MFC_ADC_VOUT));
mfc_reg_read(charger->client, MFC_AP2BT_DATA_COM_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_AP2BT_DATA_VALUE0_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_AP2MFC_CMD_L_REG, &data_val[0]);
} else { /* PAD : WPC, PMA */
pr_info("%s: WPC set 10V\n", __func__);
//trigger 10V vout work after 8sec
wake_lock(&charger->wpc_afc_vout_lock);
#if defined(CONFIG_SEC_FACTORY)
queue_delayed_work(charger->wqueue, &charger->wpc_afc_vout_work, msecs_to_jiffies(0));
#else
queue_delayed_work(charger->wqueue, &charger->wpc_afc_vout_work, msecs_to_jiffies(8000));
#endif
}
break;
case MFC_LED_CONTROL_ON:
pr_info("%s led on\n", __func__);
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
cmd = AP2BT_COM_LED_CONTROL;
data_val[0] = 0x00; /* Value for A4WP LED ON */
} else {
cmd = WPC_COM_LED_CONTROL;
data_val[0] = 0x00; /* Value for WPC LED ON */
}
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
break;
case MFC_LED_CONTROL_OFF:
pr_info("%s led off\n", __func__);
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
cmd = AP2BT_COM_LED_CONTROL;
data_val[0] = 0xff; /* Value for A4WP LED OFF */
} else {
cmd = WPC_COM_LED_CONTROL;
data_val[0] = 0xff; /* Value for WPC LED OFF */
}
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
break;
case MFC_FAN_CONTROL_ON:
pr_info("%s fan on\n", __func__);
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
cmd = AP2BT_COM_COOLING_CTRL;
data_val[0] = 0x00; /* Value for A4WP FAN ON */
} else {
cmd = WPC_COM_COOLING_CTRL;
data_val[0] = 0x00; /* Value for WPC FAN ON */
}
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
break;
case MFC_FAN_CONTROL_OFF:
pr_info("%s fan off\n", __func__);
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
cmd = AP2BT_COM_COOLING_CTRL;
data_val[0] = 0xff; /* Value for A4WP FAN OFF */
} else {
cmd = WPC_COM_COOLING_CTRL;
data_val[0] = 0xff; /* Value for WPC FAN OFF */
}
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
break;
case MFC_REQUEST_AFC_TX:
pr_info("%s request afc tx, cable(0x%x)\n", __func__, charger->pdata->cable_type);
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
cmd = AP2BT_COM_REQ_AFC_TX;
data_val[0] = 0x00; /* Value for A4WP Request AFC_TX */
} else {
cmd = WPC_COM_REQ_AFC_TX;
data_val[0] = 0x00; /* Value for WPC Request AFC_TX */
}
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
break;
case MFC_REQUEST_TX_ID:
pr_info("%s request TX ID\n", __func__);
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
cmd = AP2BT_COM_TX_ID;
data_val[0] = 0x00; /* Value for A4WP TX ID */
} else {
cmd = WPC_COM_TX_ID;
data_val[0] = 0x00; /* Value for WPC TX ID */
}
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
break;
default:
break;
}
}
void mfc_led_control(struct mfc_charger_data *charger, bool on)
{
int i = 0;
if(on) {
for(i=0; i< CMD_CNT; i++)
mfc_send_command(charger, MFC_LED_CONTROL_ON);
} else {
for(i=0; i< CMD_CNT; i++)
mfc_send_command(charger, MFC_LED_CONTROL_OFF);
}
}
void mfc_fan_control(struct mfc_charger_data *charger, bool on)
{
int i = 0;
if(on) {
for(i=0; i< CMD_CNT -1; i++)
mfc_send_command(charger, MFC_FAN_CONTROL_ON);
} else {
for(i=0; i< CMD_CNT -1; i++)
mfc_send_command(charger, MFC_FAN_CONTROL_OFF);
}
}
void mfc_set_vrect_adjust(struct mfc_charger_data *charger, int set)
{
int i = 0;
switch (set) {
case MFC_HEADROOM_0:
for(i=0; i<6; i++) {
mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x0);
msleep(50);
}
break;
case MFC_HEADROOM_1:
for(i=0; i<6; i++) {
mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x36);
msleep(50);
}
break;
case MFC_HEADROOM_2:
for(i=0; i<6; i++) {
mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x61);
msleep(50);
}
break;
case MFC_HEADROOM_3:
for(i=0; i<6; i++) {
mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x7f);
msleep(50);
}
break;
case MFC_HEADROOM_4:
for(i=0; i<6; i++) {
mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x06);
msleep(50);
}
break;
case MFC_HEADROOM_5:
for(i=0; i<6; i++) {
mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x10);
msleep(50);
}
break;
default:
pr_info("%s no headroom mode\n", __func__);
break;
}
}
void mfc_mis_align(struct mfc_charger_data *charger)
{
pr_info("%s: Reset M0\n",__func__);
if (charger->pdata->cable_type == MFC_PAD_WPC_AFC ||
charger->pdata->cable_type == MFC_PAD_PMA)
/* reset MCU of MFC IC */
mfc_set_cmd_l_reg(charger, MFC_CMD_MCU_RESET_MASK, MFC_CMD_MCU_RESET_MASK);
}
static int datacmp(const char *cs, const char *ct, int count)
{
unsigned char c1, c2;
while (count) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2) {
pr_err("%s, cnt %d\n", __func__, count);
return c1 < c2 ? -1 : 1;
}
count--;
}
return 0;
}
static int mfc_reg_multi_write_verify(struct i2c_client *client, u16 reg, const u8 * val, int size)
{
int ret = 0;
const int sendsz = 16;
int cnt = 0;
int retry_cnt = 0;
unsigned char data[sendsz+2];
u8 rdata[sendsz+2];
// dev_dbg(&client->dev, "%s: size: 0x%x\n", __func__, size);
while(size > sendsz) {
data[0] = (reg+cnt) >>8;
data[1] = (reg+cnt) & 0xff;
memcpy(data+2, val+cnt, sendsz);
// dev_dbg(&client->dev, "%s: addr: 0x%x, cnt: 0x%x\n", __func__, reg+cnt, cnt);
ret = i2c_master_send(client, data, sendsz+2);
if (ret < sendsz+2) {
pr_err("%s: i2c write error, reg: 0x%x\n", __func__, reg);
return ret < 0 ? ret : -EIO;
}
if (mfc_reg_multi_read(client, reg+cnt, rdata, sendsz) < 0) {
pr_err("%s, read failed(%d)\n", __func__, reg+cnt);
return -1;
}
if (datacmp(val+cnt, rdata, sendsz)) {
pr_err("%s, data is not matched. retry(%d)\n", __func__, retry_cnt);
retry_cnt++;
if(retry_cnt > 4) {
pr_err("%s, data is not matched. write failed\n", __func__);
retry_cnt = 0;
return -1;
}
continue;
}
// pr_debug("%s, data is matched!\n", __func__);
cnt += sendsz;
size -= sendsz;
retry_cnt = 0;
}
while (size > 0) {
data[0] = (reg+cnt) >>8;
data[1] = (reg+cnt) & 0xff;
memcpy(data+2, val+cnt, size);
// dev_dbg(&client->dev, "%s: addr: 0x%x, cnt: 0x%x, size: 0x%x\n", __func__, reg+cnt, cnt, size);
ret = i2c_master_send(client, data, size+2);
if (ret < size+2) {
pr_err("%s: i2c write error, reg: 0x%x\n", __func__, reg);
return ret < 0 ? ret : -EIO;
}
if (mfc_reg_multi_read(client, reg+cnt, rdata, size) < 0) {
pr_err("%s, read failed(%d)\n", __func__, reg+cnt);
return -1;
}
if (datacmp(val+cnt, rdata, size)) {
pr_err("%s, data is not matched. retry(%d)\n", __func__, retry_cnt);
retry_cnt++;
if(retry_cnt > 4) {
pr_err("%s, data is not matched. write failed\n", __func__);
retry_cnt = 0;
return -1;
}
continue;
}
size = 0;
pr_err("%s, data is matched!\n", __func__);
}
return ret;
}
static int mfc_reg_multi_write(struct i2c_client *client, u16 reg, const u8 * val, int size)
{
struct mfc_charger_data *charger = i2c_get_clientdata(client);
int ret;
const int sendsz = 16;
unsigned char data[sendsz+2];
int cnt = 0;
pr_err("%s: size: 0x%x\n",
__func__, size);
while(size > sendsz) {
data[0] = (reg+cnt) >>8;
data[1] = (reg+cnt) & 0xff;
memcpy(data+2, val+cnt, sendsz);
mutex_lock(&charger->io_lock);
ret = i2c_master_send(client, data, sendsz+2);
mutex_unlock(&charger->io_lock);
if (ret < sendsz+2) {
pr_err("%s: i2c write error, reg: 0x%x\n",
__func__, reg);
return ret < 0 ? ret : -EIO;
}
cnt = cnt + sendsz;
size = size - sendsz;
}
if (size > 0) {
data[0] = (reg+cnt) >>8;
data[1] = (reg+cnt) & 0xff;
memcpy(data+2, val+cnt, size);
mutex_lock(&charger->io_lock);
ret = i2c_master_send(client, data, size+2);
mutex_unlock(&charger->io_lock);
if (ret < size+2) {
dev_err(&client->dev, "%s: i2c write error, reg: 0x%x\n",
__func__, reg);
return ret < 0 ? ret : -EIO;
}
}
return ret;
}
static int LoadOTPLoaderInRAM(struct mfc_charger_data *charger, u16 addr)
{
int i, size;
u8 data[1024];
if (mfc_reg_multi_write_verify(charger->client, addr, MTPBootloader9320, sizeof(MTPBootloader9320)) < 0) {
pr_err("%s,fail", __func__);
}
size = sizeof(MTPBootloader9320);
i = 0;
while(size > 0) {
if (mfc_reg_multi_read(charger->client, addr+i, data+i, 16) < 0) {
pr_err("%s, read failed(%d)", __func__, addr+i);
return 0;
}
i += 16;
size -= 16;
}
size = sizeof(MTPBootloader9320);
if (datacmp(data, MTPBootloader9320, size)) {
pr_err("%s, data is not matched\n", __func__);
return 0;
}
return 1;
}
static int mfc_firmware_verify(struct mfc_charger_data *charger)
{
int ret = 0;
const u16 sendsz = 16;
size_t i = 0;
int block_len = 0;
int block_addr = 0;
u8 rdata[sendsz+2];
/* I2C WR to prepare boot-loader write */
if (mfc_reg_write(charger->client, 0x3000, 0x5a) < 0) {
pr_err("%s: key error\n", __func__);
return 0;
}
if (mfc_reg_write(charger->client, 0x3040, 0x11) < 0) {
pr_err("%s: halt M0, OTP_I2C_EN set error\n", __func__);
return 0;
}
dev_err(&charger->client->dev, "%s, request_firmware\n", __func__);
ret = request_firmware(&charger->firm_data_bin, MFC_FLASH_FW_HEX_PATH,
&charger->client->dev);
if ( ret < 0) {
dev_err(&charger->client->dev, "%s: failed to request firmware %s (%d) \n",
__func__, MFC_FLASH_FW_HEX_PATH, ret);
return 0;
}
ret = 1;
wake_lock(&charger->wpc_update_lock);
for (i = 0; i < charger->firm_data_bin->size; i += sendsz) {
block_len = (i + sendsz) > charger->firm_data_bin->size ? charger->firm_data_bin->size - i : sendsz;
block_addr = 0x8000 + i;
if (mfc_reg_multi_read(charger->client, block_addr, rdata, block_len) < 0) {
pr_err("%s, read failed\n", __func__);
ret = 0;
break;
}
if (datacmp(charger->firm_data_bin->data + i, rdata, block_len)) {
pr_err("%s, verify data is not matched. block_len(%d), block_addr(%d)\n",
__func__, block_len, block_addr);
ret = -1;
break;
}
}
release_firmware(charger->firm_data_bin);
wake_unlock(&charger->wpc_update_lock);
return ret;
}
bool WriteWordToMtp(struct mfc_charger_data *charger, u16 StartAddr, u32 data)
{
int j, cnt;
u8 sBuf[16] = {0,};
u16 CheckSum = StartAddr;
u16 CodeLength = 4;
//*(u32*)&sBuf[8] = data;
sBuf[8] = (u8)(data >> 0);
sBuf[9] = (u8)(data >> 8);
sBuf[10] = (u8)(data >> 16);
sBuf[11] = (u8)(data >> 24);
pr_info("%s: changed sBuf codes\n", __func__);
for (j = 3; j >= 0; j--)
CheckSum += sBuf[j + 8]; // add the non zero values
CheckSum += CodeLength; // finish calculation of the check sum
//*(u16*)&sBuf[2] = StartAddr;
//*(u16*)&sBuf[4] = CodeLength;
//*(u16*)&sBuf[6] = CheckSum;
sBuf[2] = (u8)(StartAddr >> 0);
sBuf[3] = (u8)(StartAddr >> 8);
sBuf[4] = (u8)(CodeLength >> 0);
sBuf[5] = (u8)(CodeLength >> 8);
sBuf[6] = (u8)(CheckSum >> 0);
sBuf[7] = (u8)(CheckSum >> 8);
if (mfc_reg_multi_write(charger->client, 0x400, sBuf, 4 + 8) < 0)
{
pr_err("ERROR: on writing to OTP buffer");
return false;
}
sBuf[0] = 0x01;
if (mfc_reg_write(charger->client, 0x400, sBuf[0]) < 0)
{
pr_err("ERROR: on OTP buffer validation");
return false;
}
cnt = 0;
do {
msleep(20);
if (mfc_reg_read(charger->client, 0x400, sBuf) < 0)
{
pr_err("ERROR: on readign OTP buffer status(%d)", cnt);
return false;
}
if (cnt > 1000) {
pr_err("ERROR: time out on buffer program to OTP");
break;
}
cnt++;
} while ((sBuf[0]&1) != 0);
if (sBuf[0] != 2) // not OK
{
pr_err("ERROR: buffer write to OTP returned status %d ",sBuf[0]);
return false;
}
return true;
}
static int PgmOTPwRAM_IDT(struct mfc_charger_data *charger, unsigned short OtpAddr,
const u8 * srcData, int srcOffs, int size)
{
int i, cnt;
u8 fw_major[2] = {0,};
u8 fw_minor[2] = {0,};
mfc_reg_read(charger->client, MFC_FW_MAJOR_REV_L_REG, &fw_major[0]);
mfc_reg_read(charger->client, MFC_FW_MAJOR_REV_H_REG, &fw_major[1]);
mfc_reg_read(charger->client, MFC_FW_MINOR_REV_L_REG, &fw_minor[0]);
mfc_reg_read(charger->client, MFC_FW_MINOR_REV_H_REG, &fw_minor[1]);
msleep(10);
if (mfc_reg_write(charger->client, 0x3000, 0x5a) < 0) {
pr_err("%s: write key error\n", __func__);
return false; // write key
}
msleep(10);
if (mfc_reg_write(charger->client, 0x3040, 0x10) < 0) {
pr_err("%s: halt M0 error\n", __func__);
return false; // halt M0
}
msleep(10);
if (!LoadOTPLoaderInRAM(charger, 0x1c00)){
pr_err("%s: LoadOTPLoaderInRAM error\n", __func__);
return false; // make sure load address and 1KB size are OK
}
msleep(10);
// Clear MTP program status byte
if (mfc_reg_write(charger->client, 0x0400, 0x00) < 0) {
pr_err("%s: clear MTP programming status byte error\n", __func__);
return false;
}
if (mfc_reg_write(charger->client, 0x3048, 0x80) < 0) {
pr_err("%s: map RAM to OTP error\n", __func__);
return false; // map RAM to OTP
}
// Check Key lock state
mfc_reg_write(charger->client, 0x3040, 0x80); //M0 RESET : P9320 will not acknowledge for this transaction !!
msleep(100);
pr_info("%s: start to write f/w bin to mtp\n", __func__);
for (i = 0; i < size; i += 128) // program pages of 128 bytes
{
u8 sBuf[144] = {0,}; // align size in 16 bytes boundary. may not be important for SS
u16 StartAddr = (u16)i;
u16 CheckSum = StartAddr;
u16 CodeLength = 128;
int j;
memcpy(sBuf + 8, srcData + i + srcOffs, 128);
if (i == 0x1480) { // the FW rev address for rev 58 is 0x14a8. 0x1280 is the half page base address.
//*(u32*)&sBuf[8 + 0x28] = 0;
sBuf[0x30] = 0;
sBuf[0x31] = 0;
sBuf[0x32] = 0;
sBuf[0x33] = 0;
}
j = size - i; // calculate how many bytes need to be programmed in the current run and round up to 16
if (j < 128)
{
j = ((j + 15) / 16) * 16;
CodeLength = (u16)j;
}
else
{
j = 128;
}
j -= 1; // compensate for index
for (; j >= 0; j--)
CheckSum += sBuf[j+8]; // add the non zero values
CheckSum += CodeLength; // finish calculation of the check sum
memcpy(sBuf+2, &StartAddr, 2);
memcpy(sBuf+4, &CodeLength, 2);
memcpy(sBuf+6, &CheckSum, 2);
// FOR REFERENCE HERE IS THE DATA STRUCTURE
//typedef struct {
// u16 Status;
// u16 StartAddr;
// u16 CodeLength;
// u16 DataChksum;
// u8 DataBuf[128];
//} P9320PgmStrType; // the structure is located at address 0x400
// TODO sBuf[0] = 0x00; // done during initialization.
if (mfc_reg_multi_write(charger->client, 0x400, sBuf, ((CodeLength+8+15)/16)*16) < 0)
{ // TODO the write size is aligned to 16 bytes. SS may not need to do this.
pr_err("ERROR: on writing to OTP buffer");
return false;
}
sBuf[0] = 0x01; // TODO write 0x11 if Vrect is powered from 5V
//write 0x31 if Vrect is powered from 8.2V
//write 0x01 if Vrect is 5V and there is a problem
if (mfc_reg_write(charger->client, 0x400, sBuf[0]) < 0)
{
pr_err("ERROR: on OTP buffer validation");
return false;
}
cnt = 0;
do
{
msleep(20);
if (mfc_reg_read(charger->client, 0x400, sBuf) < 0)
{
pr_err("ERROR: on readign OTP buffer status(%d)", cnt);
return false;
}
if (cnt > 1000) {
pr_err("ERROR: time out on buffer program to OTP");
break;
}
cnt++;
} while ((sBuf[0]&1) != 0);
if (sBuf[0] != 2) // not OK
{
pr_err("ERROR: buffer write to OTP returned status %d in sector 0x%x ",sBuf[0], i);
return false;
}
}
pr_info("%s: write current f/w rev (0x%x)\n", __func__, MFC_FW_BIN_FULL_VERSION);
if (!WriteWordToMtp(charger, MFC_FW_BIN_VERSION_ADDR, MFC_FW_BIN_FULL_VERSION)) {
// The address for fw rev 58 is 0x14a8
pr_err("ERROR: on writing FW rev to MTP\n");
return false;
}
msleep(10);
if (mfc_reg_write(charger->client, 0x3000, 0x5a) < 0) {
pr_err("%s: write key error..\n", __func__);
return false; // write key
}
msleep(10);
if (mfc_reg_write(charger->client, 0x3048, 0x00) < 0) {
pr_err("%s: remove code remapping error..\n", __func__);
return false; // remove code remapping
}
pr_err("OTP Programming finished in");
pr_info("%s------------------------------------------------- \n", __func__);
return true;
}
static int mfc_write_fw_flash_LSI(struct mfc_charger_data *charger, u8 addr_l, u8 addr_h, u8 *wdata)
{
if (mfc_reg_write(charger->client, 0x1F11, 0x04) < 0) {
pr_err("%s: failed to write 0x04 at 0x1F11\n", __func__);
return -1;
}
if (mfc_reg_write(charger->client, 0x1F12, addr_l) < 0) {
pr_err("%s: failed to write addr_l(0x%x) at 0x1F11\n", __func__, addr_l);
return -1;
}
if (mfc_reg_write(charger->client, 0x1F13, addr_h) < 0) {
pr_err("%s: failed to write addr_h(0x%x) at 0x1F11\n", __func__, addr_h);
return -1;
}
if (mfc_reg_write(charger->client, 0x1F14, wdata[0]) < 0) {
pr_err("%s: failed to write wdata[0]\n", __func__);
return -1;
}
if (mfc_reg_write(charger->client, 0x1F15, wdata[1]) < 0) {
pr_err("%s: failed to write wdata[1]\n", __func__);
return -1;
}
if (mfc_reg_write(charger->client, 0x1F16, wdata[2]) < 0) {
pr_err("%s: failed to write wdata[2]\n", __func__);
return -1;
}
if (mfc_reg_write(charger->client, 0x1F17, wdata[3]) < 0) {
pr_err("%s: failed to write wdata[3]\n", __func__);
return -1;
}
if (mfc_reg_write(charger->client, 0x1F10, 0x42) < 0) {
pr_err("%s: failed to write 0x42 at 0x1F10\n", __func__);
return -1;
}
return 0;
}
#define LSI_MFC_FW_FLASH_START_ADDR 0x1000
static int PgmOTPwRAM_LSI(struct mfc_charger_data *charger, unsigned short OtpAddr,
const u8 * srcData, int srcOffs, int size)
{
int addr;
u8 fw_major[2] = {0,};
u8 fw_minor[2] = {0,};
u8 wdata[4] = {0,};
// static int startAddr;
u16 temp;
u8 addr_l, addr_h;
mfc_reg_read(charger->client, MFC_FW_MAJOR_REV_L_REG, &fw_major[0]);
mfc_reg_read(charger->client, MFC_FW_MAJOR_REV_H_REG, &fw_major[1]);
mfc_reg_read(charger->client, MFC_FW_MINOR_REV_L_REG, &fw_minor[0]);
mfc_reg_read(charger->client, MFC_FW_MINOR_REV_H_REG, &fw_minor[1]);
pr_info("%s: Enter the flash mode (0x1F10)\n", __func__);
if (mfc_reg_write(charger->client, 0x1F10, 0x10) < 0) {
pr_err("%s: failed to enter the flash mode\n", __func__);
return false;
}
msleep(2);
pr_info("%s: Erase the flash memory\n", __func__);
if (mfc_reg_write(charger->client, 0x1F10, 0x44) < 0) {
pr_err("%s: failed to erase flash\n", __func__);
return false;
}
msleep(250); /* erasing flash needs 200ms delay at least */
pr_info("%s: write fwimg by 4 bytes \n", __func__);
size -= 0x1000;
for (addr = 0x00; addr < size; addr += 4) // program pages of 4bytes
{
temp = (0x1000 + addr) & 0xff;
addr_l = (u8)temp;
temp = (((0x1000 + addr) & 0xff00) >> 8);
addr_h = (u8)temp;
pr_info("%s: addr_h(0x%x), addr_l(0x%x)\n", __func__, addr_h, addr_l);
memcpy(wdata, srcData + LSI_MFC_FW_FLASH_START_ADDR + addr, 4);
mfc_write_fw_flash_LSI(charger, addr_l, addr_h, wdata);
}
pr_info("%s: write fw length --------------------\n", __func__);
wdata[0] = 0x8C; /* length */
wdata[1] = 0x3E;
wdata[2] = 0x00;
wdata[3] = 0x00;
mfc_write_fw_flash_LSI(charger, 0xf4, 0x6f, wdata);
pr_info("%s: write fw checksum --------------------\n", __func__);
wdata[0] = 0x90; /* checksum */
wdata[1] = 0x00;
wdata[2] = 0x00;
wdata[3] = 0x00;
mfc_write_fw_flash_LSI(charger, 0xf8, 0x6f, wdata);
pr_info("%s: write fw version --------------------\n", __func__);
wdata[0] = 0x00; /* fw major rev l */
wdata[1] = 0x00; /* fw major rev h */
wdata[2] = 0x05; /* fw minor rev l */
wdata[3] = 0x11; /* fw minor rev h */
mfc_write_fw_flash_LSI(charger, 0x00, 0x6f, wdata);
pr_info("%s: write fw date and timer code --------------------\n", __func__);
wdata[0] = 0x4A; /* J , date code start */
wdata[1] = 0x75; /* u */
wdata[2] = 0x6E; /* n */
wdata[3] = 0x20; /* space */
mfc_write_fw_flash_LSI(charger, 0x04, 0x6f, wdata);
wdata[0] = 0x31; /* 1 */
wdata[1] = 0x35; /* 5 */
wdata[2] = 0x20; /* space */
wdata[3] = 0x32; /* 2 */
mfc_write_fw_flash_LSI(charger, 0x08, 0x6f, wdata);
wdata[0] = 0x30; /* 0 */
wdata[1] = 0x31; /* 1 */
wdata[2] = 0x36; /* 6 */
wdata[3] = 0x31; /* 1 , timer code start */
mfc_write_fw_flash_LSI(charger, 0x0c, 0x6f, wdata);
wdata[0] = 0x33; /* 3 */
wdata[1] = 0x3A; /* : */
wdata[2] = 0x30; /* 0 */
wdata[3] = 0x30; /* 0 */
mfc_write_fw_flash_LSI(charger, 0x10, 0x6f, wdata);
wdata[0] = 0x3A; /* : */
wdata[1] = 0x30; /* 0 */
wdata[2] = 0x30; /* 0 */
wdata[3] = 0x00; /* reserved */
mfc_write_fw_flash_LSI(charger, 0x14, 0x6f, wdata);
pr_info("%s: write flash done flag --------------------*\n", __func__);
wdata[0] = 0x01;
wdata[1] = 0x00;
wdata[2] = 0x00;
wdata[3] = 0x00;
mfc_write_fw_flash_LSI(charger, 0xfc, 0x6f, wdata);
msleep(10);
pr_info("%s: Enter the normal mode\n", __func__);
if (mfc_reg_write(charger->client, 0x1F10, 0x20) < 0) {
pr_err("%s: failed to enter the normal mode\n", __func__);
return false;
}
msleep(10);
return true;
}
static void mfc_uno_on(struct mfc_charger_data *charger, bool onoff)
{
union power_supply_propval value = {0, };
if (onoff) { /* UNO ON */
psy_do_property("otg", get,
POWER_SUPPLY_PROP_ONLINE, value);
if (value.intval) {
charger->is_otg_on = true;
psy_do_property(charger->pdata->wired_charger_name, set,
POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW, value);
pr_info("%s CHGIN_OTG on, check OTG flag\n", __func__);
} else
charger->is_otg_on = false;
value.intval = 1;
psy_do_property(charger->pdata->wired_charger_name, set,
POWER_SUPPLY_PROP_CHARGE_UNO_CONTROL, value);
pr_info("%s: ENABLE\n", __func__);
} else { /* UNO OFF */
value.intval = 0;
psy_do_property(charger->pdata->wired_charger_name, set,
POWER_SUPPLY_PROP_CHARGE_UNO_CONTROL, value);
psy_do_property("battery", get,
POWER_SUPPLY_PROP_ONLINE, value);
if ((charger->is_otg_on) &&
value.intval == SEC_BATTERY_CABLE_OTG) { /* OTG status has to be recovered */
value.intval = 1;
psy_do_property(charger->pdata->wired_charger_name, set,
POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, value);
pr_info("%s CHGIN_OTG was ON, recover OTG status\n", __func__);
}
charger->is_otg_on = false;
pr_info("%s: DISABLE\n", __func__);
}
}
static void mfc_wpc_cm_fet_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_cm_fet_work.work);
u8 tmp = 0;
/* disable all CM FETs for MST operation */
mfc_reg_write(charger->client, MFC_RX_COMM_MOD_FET_REG, 0xf0);
mfc_reg_read(charger->client, MFC_RX_COMM_MOD_FET_REG, &tmp);
pr_info("%s: disable CM FET (0x%x)\n", __func__, tmp);
}
static void mfc_wpc_afc_vout_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_afc_vout_work.work);
u8 data_val[4];
u8 cmd = 0;
u8 i;
union power_supply_propval value = {0, };
pr_info("%s start\n", __func__);
/* change cable type */
if (charger->pdata->cable_type == MFC_PAD_WPC_STAND_HV)
value.intval = SEC_WIRELESS_PAD_WPC_STAND_HV;
else if (charger->pdata->cable_type == MFC_PAD_WPC_VEHICLE_HV)
value.intval = SEC_WIRELESS_PAD_VEHICLE_HV;
else {
charger->pdata->cable_type = MFC_PAD_WPC_AFC;
value.intval = SEC_WIRELESS_PAD_WPC_HV;
}
psy_do_property("wireless", set,
POWER_SUPPLY_PROP_ONLINE, value);
#if defined(CONFIG_BATTERY_SWELLING)
/* check swelling mode */
psy_do_property("battery", get,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, value);
pr_info("%s: check swelling mode(%d)\n", __func__, value.intval);
if (value.intval)
goto skip_set_afc_vout;
#endif
for(i = 0; i < CMD_CNT; i++) {
cmd = WPC_COM_AFC_SET;
data_val[0] = 0x2c; /* Value for WPC AFC_SET 10V */
pr_info("%s set 10V , cnt = %d \n", __func__, i);
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
mfc_reg_read(charger->client, MFC_WPC_RX_DATA_COM_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_WPC_RX_DATA_VALUE0_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_AP2MFC_CMD_L_REG, &data_val[0]);
msleep(100);
}
charger->is_afc_tx = true;
pr_info("%s: is_afc_tx = %d vout read = %d \n",
__func__, charger->is_afc_tx, mfc_get_adc(charger, MFC_ADC_VOUT));
/* use all CM FETs for 10V wireless charging */
mfc_reg_write(charger->client, MFC_RX_COMM_MOD_FET_REG, 0x00);
mfc_reg_read(charger->client, MFC_RX_COMM_MOD_FET_REG, &cmd);
pr_info("%s: CM FET setting(0x%x) \n", __func__, cmd);
psy_do_property("otg", get,
POWER_SUPPLY_PROP_ONLINE, value);
pr_info("%s: check state(%d, %d, %d)\n", __func__,
charger->vout_mode, charger->pdata->vout_status, value.intval);
if (!charger->is_full_status && !value.intval &&
charger->vout_mode != WIRELESS_VOUT_5V &&
charger->vout_mode != WIRELESS_VOUT_5V_STEP) {
charger->vout_mode = WIRELESS_VOUT_10V;
cancel_delayed_work(&charger->wpc_vout_mode_work);
wake_lock(&charger->wpc_vout_mode_lock);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, 0);
}
#if defined(CONFIG_BATTERY_SWELLING)
skip_set_afc_vout:
#endif
wake_unlock(&charger->wpc_afc_vout_lock);
}
static void mfc_wpc_fw_update_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_fw_update_work.work);
struct file *fp;
mm_segment_t old_fs;
long fsize, nread;
const u8 *fw_img;
int ret = 0;
int i = 0;
char fwtime[8] = {32, 32, 32, 32, 32, 32, 32, 32};
char fwdate[8] = {32, 32, 32, 32, 32, 32, 32, 32};
u8 data = 32; /* ascii space */
pr_info("%s firmware update mode is = %d \n", __func__, charger->fw_cmd);
switch(charger->fw_cmd) {
case SEC_WIRELESS_RX_SDCARD_MODE:
mfc_uno_on(charger, true);
charger->pdata->otp_firmware_result = MFC_FW_RESULT_DOWNLOADING;
msleep(200);
disable_irq(charger->pdata->irq_wpc_int);
disable_irq(charger->pdata->irq_wpc_det);
old_fs = get_fs();
set_fs(KERNEL_DS);
fp = filp_open(MFC_FW_SDCARD_BIN_PATH, O_RDONLY, S_IRUSR);
if (IS_ERR(fp)) {
pr_err("%s: failed to open %s, (%d)\n", __func__, MFC_FW_SDCARD_BIN_PATH, IS_ERR(fp));
set_fs(old_fs);
goto fw_err;
}
fsize = fp->f_path.dentry->d_inode->i_size;
pr_err("%s: start, file path %s, size %ld Bytes\n",
__func__, MFC_FW_SDCARD_BIN_PATH, fsize);
fw_img = kmalloc(fsize, GFP_KERNEL);
if (fw_img == NULL) {
pr_err("%s, kmalloc failed\n", __func__);
goto malloc_error;
}
nread = vfs_read(fp, (char __user *)fw_img,
fsize, &fp->f_pos);
pr_err("nread %ld Bytes\n", nread);
if (nread != fsize) {
pr_err("failed to read firmware file, nread %ld Bytes\n", nread);
goto read_err;
}
filp_close(fp, current->files);
set_fs(old_fs);
mfc_get_chip_id(charger);
pr_info("%s chip_id(%d) \n", __func__, charger->chip_id);
mfc_otp_update = 1;
if (charger->chip_id == MFC_CHIP_LSI) {
pr_info("%s: S.LSI MFC IC doesn't support sdcard f/w update\n", __func__);
goto read_err;
}
else /* MFC_CHIP_IDT */
ret = PgmOTPwRAM_IDT(charger, 0 ,fw_img, 0, fsize);
mfc_otp_update = 0;
charger->pdata->otp_firmware_ver = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE);
charger->pdata->wc_ic_grade = mfc_get_ic_revision(charger, MFC_IC_FONT);
charger->pdata->wc_ic_rev = mfc_get_ic_revision(charger, MFC_IC_REVISION);
if(ret)
charger->pdata->otp_firmware_result = MFC_FW_RESULT_PASS;
else
charger->pdata->otp_firmware_result = MFC_FW_RESULT_FAIL;
kfree(fw_img);
enable_irq(charger->pdata->irq_wpc_int);
enable_irq(charger->pdata->irq_wpc_det);
break;
case SEC_WIRELESS_RX_BUILT_IN_MODE:
mfc_uno_on(charger, true);
charger->pdata->otp_firmware_result = MFC_FW_RESULT_DOWNLOADING;
msleep(200);
disable_irq(charger->pdata->irq_wpc_int);
disable_irq(charger->pdata->irq_wpc_det);
dev_err(&charger->client->dev, "%s, request_firmware\n", __func__);
ret = request_firmware(&charger->firm_data_bin, MFC_FLASH_FW_HEX_PATH,
&charger->client->dev);
if ( ret < 0) {
dev_err(&charger->client->dev, "%s: failed to request firmware %s (%d) \n", __func__, MFC_FLASH_FW_HEX_PATH, ret);
charger->pdata->otp_firmware_result = MFC_FW_RESULT_FAIL;
goto fw_err;
}
wake_lock(&charger->wpc_update_lock);
mfc_get_chip_id(charger);
pr_info("%s data size = %ld, chip_id(%d) \n", __func__, charger->firm_data_bin->size, charger->chip_id);
mfc_otp_update = 1;
if (charger->chip_id == MFC_CHIP_LSI)
ret = PgmOTPwRAM_LSI(charger, 0 ,charger->firm_data_bin->data, 0, charger->firm_data_bin->size);
else /* MFC_CHIP_IDT */
ret = PgmOTPwRAM_IDT(charger, 0 ,charger->firm_data_bin->data, 0, charger->firm_data_bin->size);
mfc_otp_update = 0;
release_firmware(charger->firm_data_bin);
for(i = 0; i < 8; i++) {
if (mfc_reg_read(charger->client, MFC_FW_DATA_CODE_0+i, &data) > 0)
fwdate[i] = (char)data;
}
for(i = 0; i < 8; i++) {
if (mfc_reg_read(charger->client, MFC_FW_TIMER_CODE_0+i, &data) > 0)
fwtime[i] = (char)data;
}
pr_info("%s: %d%d%d%d%d%d%d%d, %d%d%d%d%d%d%d%d\n", __func__,
fwdate[0], fwdate[1], fwdate[2],fwdate[3], fwdate[4], fwdate[5], fwdate[6], fwdate[7],
fwtime[0], fwtime[1], fwtime[2],fwtime[3], fwtime[4], fwtime[5], fwtime[6], fwtime[7]);
charger->pdata->otp_firmware_ver = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE);
charger->pdata->wc_ic_grade = mfc_get_ic_revision(charger, MFC_IC_FONT);
charger->pdata->wc_ic_rev = mfc_get_ic_revision(charger, MFC_IC_REVISION);
if(ret)
charger->pdata->otp_firmware_result = MFC_FW_RESULT_PASS;
else
charger->pdata->otp_firmware_result = MFC_FW_RESULT_FAIL;
for(i = 0; i < 8; i++) {
if (mfc_reg_read(charger->client, MFC_FW_DATA_CODE_0+i, &data) > 0)
fwdate[i] = (char)data;
}
for(i = 0; i < 8; i++) {
if (mfc_reg_read(charger->client, MFC_FW_TIMER_CODE_0+i, &data) > 0)
fwtime[i] = (char)data;
}
pr_info("%s: %d%d%d%d%d%d%d%d, %d%d%d%d%d%d%d%d\n", __func__,
fwdate[0], fwdate[1], fwdate[2],fwdate[3], fwdate[4], fwdate[5], fwdate[6], fwdate[7],
fwtime[0], fwtime[1], fwtime[2],fwtime[3], fwtime[4], fwtime[5], fwtime[6], fwtime[7]);
enable_irq(charger->pdata->irq_wpc_int);
enable_irq(charger->pdata->irq_wpc_det);
wake_unlock(&charger->wpc_update_lock);
break;
case SEC_WIRELESS_TX_ON_MODE:
charger->pdata->cable_type = MFC_PAD_TX;
break;
case SEC_WIRELESS_TX_OFF_MODE:
/* need to check */
break;
case SEC_WIRELESS_RX_INIT:
/* need to check */
break;
default:
break;
}
msleep(200);
mfc_uno_on(charger, false);
pr_info("%s --------------------------------------------------------------- \n", __func__);
return;
read_err:
kfree(fw_img);
malloc_error:
filp_close(fp, current->files);
set_fs(old_fs);
fw_err:
mfc_uno_on(charger, false);
}
/*#if !defined(CONFIG_SEC_FACTORY)
static void mfc_wpc_fw_booting_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_fw_booting_work.work);
union power_supply_propval value = {0, };
int fw_version;
value.intval =
SEC_FUELGAUGE_CAPACITY_TYPE_SCALE;
psy_do_property(charger->pdata->fuelgauge_name, get,
POWER_SUPPLY_PROP_CAPACITY, value);
pr_info("%s: battery capacity (%d)\n", __func__, value.intval);
if (value.intval >= 10) {
mfc_uno_on(charger, true);
msleep(200);
fw_version = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE);
pr_info("%s: fw version (0x%x)\n", __func__, fw_version);
if (fw_version != MFC_FW_BIN_VERSION) {
charger->fw_cmd = SEC_WIRELESS_RX_BUILT_IN_MODE;
queue_delayed_work(charger->wqueue, &charger->wpc_fw_update_work, 0);
} else {
mfc_uno_on(charger, false);
}
}
}
#endif*/
static int mfc_chg_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct mfc_charger_data *charger = power_supply_get_drvdata(psy);
enum power_supply_ext_property ext_psp = psp;
// union power_supply_propval value;
u8 mst_mode;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
pr_info("%s charger->pdata->cs100_status %d \n",__func__,charger->pdata->cs100_status);
val->intval = charger->pdata->cs100_status;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = mfc_reg_read(charger->client, MFC_MST_MODE_SEL_REG, &mst_mode);
val->intval = mst_mode;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
case POWER_SUPPLY_PROP_HEALTH:
return -ENODATA;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
if(charger->pdata->ic_on_mode || charger->pdata->is_charging) {
val->intval = mfc_get_vout(charger);
} else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL:
case POWER_SUPPLY_PROP_CHARGE_POWERED_OTG_CONTROL:
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
return -ENODATA;
case POWER_SUPPLY_PROP_ONLINE:
pr_info("%s cable_type =%d \n ", __func__, charger->pdata->cable_type);
val->intval = charger->pdata->cable_type;
break;
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION:
val->intval = charger->pdata->vout_status;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
pr_info("%s: POWER_SUPPLY_PROP_MANUFACTURER, intval(0x%x), called by(%ps)\n", __func__, val->intval, __builtin_return_address(0));
if (val->intval == SEC_WIRELESS_OTP_FIRM_RESULT) {
pr_info("%s otp firmware result = %d,\n",__func__, charger->pdata->otp_firmware_result);
val->intval = charger->pdata->otp_firmware_result;
} else if(val->intval == SEC_WIRELESS_IC_REVISION) {
pr_info("%s: check f/w revision\n", __func__);
val->intval = mfc_get_ic_revision(charger, MFC_IC_REVISION);
} else if(val->intval == SEC_WIRELESS_IC_GRADE) {
pr_info("%s: check f/w revision\n", __func__);
val->intval = mfc_get_ic_revision(charger, MFC_IC_FONT);
} else if(val->intval == SEC_WIRELESS_OTP_FIRM_VER_BIN) {
val->intval = MFC_FW_BIN_VERSION; /* latest MFC F/W binary version */
} else if(val->intval == SEC_WIRELESS_OTP_FIRM_VER) {
val->intval = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE);
pr_info("%s: check f/w revision (0x%x)\n", __func__, val->intval);
} else if(val->intval == SEC_WIRELESS_TX_FIRM_RESULT) {
val->intval = charger->pdata->tx_firmware_result;
} else if (val->intval == SEC_WIRELESS_TX_FIRM_VER) {
val->intval = charger->pdata->tx_firmware_ver;
} else if(val->intval == SEC_TX_FIRMWARE) {
val->intval = charger->pdata->tx_status;
} else if(val->intval == SEC_WIRELESS_OTP_FIRM_VERIFY) {
mfc_get_chip_id(charger);
if (charger->chip_id == MFC_CHIP_LSI) {
pr_info("%s: LSI FIRM_VERIFY is not implemented\n", __func__);
val->intval = 1;
} else {
pr_info("%s: IDT FIRM_VERIFY\n", __func__);
msleep(10);
val->intval = mfc_firmware_verify(charger);
}
} else if (val->intval == SEC_WIRELESS_MST_SWITCH_VERIFY) {
if (gpio_is_valid(charger->pdata->mst_pwr_en)) {
gpio_direction_output(charger->pdata->mst_pwr_en, 1);
msleep(charger->pdata->mst_switch_delay);
val->intval = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE);
pr_info("%s: check f/w revision, mst power on (0x%x)\n", __func__, val->intval);
gpio_direction_output(charger->pdata->mst_pwr_en, 0);
} else {
pr_info("%s: MST_SWITCH_VERIFY, invalid gpio(mst_pwr_en)\n", __func__);
val->intval = -1;
}
} else {
val->intval = -ENODATA;
pr_err("%s wrong mode \n", __func__);
}
break;
case POWER_SUPPLY_PROP_ENERGY_NOW: /* vout */
if(charger->pdata->ic_on_mode || charger->pdata->is_charging) {
val->intval = mfc_get_adc(charger, MFC_ADC_VOUT);
pr_info("%s: wc vout (%d)\n", __func__, val->intval);
} else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_ENERGY_AVG: /* vrect */
if(charger->pdata->ic_on_mode || charger->pdata->is_charging) {
val->intval = mfc_get_adc(charger, MFC_ADC_VRECT);
} else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = mfc_get_adc(charger, val->intval);
break;
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
break;
case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX:
switch (ext_psp) {
case POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ:
val->intval = mfc_get_adc(charger, MFC_ADC_OP_FRQ);
pr_info("%s: Operating FQ %dkHz\n", __func__, val->intval);
break;
case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_CMD:
val->intval = charger->pdata->tx_data_cmd;
break;
case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VAL:
val->intval = charger->pdata->tx_data_val;
break;
default:
return -ENODATA;
}
break;
default:
return -ENODATA;
}
return 0;
}
static void mfc_wpc_vout_mode_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_vout_mode_work.work);
int vout_step = charger->pdata->vout_status;
int vout = MFC_VOUT_10V;
pr_info("%s: start - vout_mode(%d), vout_status(%d)\n",
__func__, charger->vout_mode, charger->pdata->vout_status);
switch (charger->vout_mode) {
case WIRELESS_VOUT_5V:
mfc_set_vout(charger, MFC_VOUT_5V);
break;
case WIRELESS_VOUT_9V:
mfc_set_vout(charger, MFC_VOUT_9V);
break;
case WIRELESS_VOUT_10V:
mfc_set_vout(charger, MFC_VOUT_10V);
break;
case WIRELESS_VOUT_5V_STEP:
vout_step--;
if (vout_step >= MFC_VOUT_5V) {
mfc_set_vout(charger, vout_step);
cancel_delayed_work(&charger->wpc_vout_mode_work);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, msecs_to_jiffies(250));
return;
}
break;
case WIRELESS_VOUT_9V_STEP:
vout = MFC_VOUT_9V;
case WIRELESS_VOUT_10V_STEP:
vout_step++;
if (vout_step <= vout) {
mfc_set_vout(charger, vout_step);
cancel_delayed_work(&charger->wpc_vout_mode_work);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, msecs_to_jiffies(250));
return;
}
break;
case WIRELESS_VOUT_CV_CALL:
case WIRELESS_VOUT_CC_CALL:
mfc_set_vrect_adjust(charger, MFC_HEADROOM_3);
msleep(500);
mfc_set_vout(charger, MFC_VOUT_5V);
msleep(500);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_0);
break;
case WIRELESS_VOUT_CC_CV_VOUT:
mfc_set_vout(charger, MFC_VOUT_5_5V);
break;
default:
break;
}
#if !defined(CONFIG_SEC_FACTORY)
if ((charger->pdata->cable_type == MFC_PAD_WPC_AFC ||
charger->pdata->cable_type == MFC_PAD_WPC_STAND_HV ||
charger->pdata->cable_type == MFC_PAD_WPC_VEHICLE_HV) &&
charger->pdata->vout_status <= MFC_VOUT_5V && charger->is_full_status) {
u8 data = 0x05;
/* send data for decreasing VRECT to 5V */
mfc_send_packet(charger, MFC_HEADER_AFC_CONF,
AP2BT_COM_AFC_MODE, &data, 1);
}
#endif
pr_info("%s: finish - vout_mode(%d), vout_status(%d)\n",
__func__, charger->vout_mode, charger->pdata->vout_status);
wake_unlock(&charger->wpc_vout_mode_lock);
}
#if defined(CONFIG_UPDATE_BATTERY_DATA)
static int mfc_chg_parse_dt(struct device *dev, mfc_charger_platform_data_t *pdata);
#endif
static int mfc_chg_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct mfc_charger_data *charger = power_supply_get_drvdata(psy);
int vout, vrect, iout, freq, i = 0;
u8 tmp = 0;
/* int ret; */
union power_supply_propval value;
u8 fod[12] = {0, };
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (val->intval == POWER_SUPPLY_STATUS_FULL) {
pr_info("%s set cs100\n", __func__);
if (charger->pdata->cable_type == SEC_WIRELESS_PAD_WPC) {
/* set fake FOD values before send cs100, need to tune */
mfc_fod_set_cs100(charger);
}
charger->pdata->cs100_status = mfc_send_cs100(charger);
#if !defined(CONFIG_SEC_FACTORY)
charger->is_full_status = 1;
if (charger->pdata->cable_type == MFC_PAD_WPC_AFC ||
charger->pdata->cable_type == MFC_PAD_WPC_STAND_HV ||
charger->pdata->cable_type == MFC_PAD_WPC_VEHICLE_HV) {
charger->vout_mode = WIRELESS_VOUT_5V_STEP;
cancel_delayed_work(&charger->wpc_vout_mode_work);
wake_lock(&charger->wpc_vout_mode_lock);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, msecs_to_jiffies(250));
}
#endif
} else if (val->intval == POWER_SUPPLY_STATUS_NOT_CHARGING) {
mfc_mis_align(charger);
} else if (val->intval == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE) {
mfc_fod_set_cv(charger);
}
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
value.intval = charger->pdata->cable_type;
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
break;
case POWER_SUPPLY_PROP_HEALTH:
if (val->intval == POWER_SUPPLY_HEALTH_OVERHEAT ||
val->intval == POWER_SUPPLY_HEALTH_OVERHEATLIMIT ||
val->intval == POWER_SUPPLY_HEALTH_COLD) {
pr_info("%s ept-ot\n", __func__);
mfc_send_eop(charger, val->intval);
}
break;
case POWER_SUPPLY_PROP_ONLINE:
if (is_wireless_type(val->intval))
charger->pdata->ic_on_mode = true;
else
charger->pdata->ic_on_mode = false;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
if (val->intval) {
charger->is_mst_on = MST_MODE_2;
pr_info("%s: set MST mode 2\n", __func__);
/* disable CM FETs to avoid MST/WPC crash situation */
queue_delayed_work(charger->wqueue,
&charger->wpc_cm_fet_work, msecs_to_jiffies(1000));
} else {
if (charger->chip_id == MFC_CHIP_LSI) {
/*
* Default Idle voltage of the INT_A is LOW.
* Prevent the un-wanted INT_A Falling handling.
* This is a work-around, and will be fixed by the revision.
*/
charger->mst_off_lock = 1;
if (!delayed_work_pending(&charger->mst_off_work))
queue_delayed_work(charger->wqueue, &charger->mst_off_work, 0);
}
pr_info("%s: set MST mode off\n", __func__);
charger->is_mst_on = MST_MODE_0;
}
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
charger->pdata->siop_level = val->intval;
if (charger->pdata->siop_level == 100) {
pr_info("%s vrect headroom set ROOM 2, siop = %d\n", __func__, charger->pdata->siop_level);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_2);
} else if (charger->pdata->siop_level < 100) {
pr_info("%s vrect headroom set ROOM 0, siop = %d\n", __func__, charger->pdata->siop_level);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_0);
}
break;
case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL:
if (val->intval)
charger->pdata->ic_on_mode = true;
else
charger->pdata->ic_on_mode = false;
break;
case POWER_SUPPLY_PROP_CHARGE_POWERED_OTG_CONTROL:
charger->fw_cmd = val->intval;
queue_delayed_work(charger->wqueue, &charger->wpc_fw_update_work, 0);
pr_info("%s: rx result = %d, tx result = %d\n", __func__,
charger->pdata->otp_firmware_result, charger->pdata->tx_firmware_result);
break;
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION:
if (val->intval == WIRELESS_VOUT_NORMAL_VOLTAGE) {
pr_info("%s: Wireless Vout forced set to 5V. + PAD CMD\n", __func__);
for (i = 0; i < CMD_CNT; i++) {
mfc_send_command(charger, MFC_AFC_CONF_5V);
msleep(250);
}
} else if (val->intval == WIRELESS_VOUT_HIGH_VOLTAGE) {
pr_info("%s: Wireless Vout forced set to 10V. + PAD CMD\n", __func__);
for (i = 0; i < CMD_CNT; i++) {
mfc_send_command(charger, MFC_AFC_CONF_10V);
msleep(250);
}
} else if (val->intval == WIRELESS_VOUT_CC_CV_VOUT ||
val->intval == WIRELESS_VOUT_CV_CALL ||
val->intval == WIRELESS_VOUT_CC_CALL) {
charger->vout_mode = val->intval;
cancel_delayed_work(&charger->wpc_vout_mode_work);
wake_lock(&charger->wpc_vout_mode_lock);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, 0);
} else if (val->intval == WIRELESS_VOUT_5V ||
val->intval == WIRELESS_VOUT_5V_STEP) {
int def_delay = 0;
charger->vout_mode = val->intval;
if ((charger->pdata->cable_type == MFC_PAD_WPC_AFC ||
charger->pdata->cable_type == MFC_PAD_WPC_STAND_HV ||
charger->pdata->cable_type == MFC_PAD_WPC_VEHICLE_HV)) {
def_delay = 250;
}
cancel_delayed_work(&charger->wpc_vout_mode_work);
wake_lock(&charger->wpc_vout_mode_lock);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, msecs_to_jiffies(def_delay));
} else if (val->intval == WIRELESS_VOUT_9V ||
val->intval == WIRELESS_VOUT_10V ||
val->intval == WIRELESS_VOUT_9V_STEP ||
val->intval == WIRELESS_VOUT_10V_STEP) {
if (!charger->is_full_status) {
if (!charger->is_afc_tx) {
u8 data_val[4], cmd = 0;
pr_info("%s: need to set afc tx before vout control\n", __func__);
for (i = 0; i < CMD_CNT; i++) {
cmd = WPC_COM_AFC_SET;
data_val[0] = 0x2c; /* Value for WPC AFC_SET 10V */
pr_info("%s set 10V , cnt = %d \n", __func__, i);
mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1);
mfc_reg_read(charger->client, MFC_WPC_RX_DATA_COM_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_WPC_RX_DATA_VALUE0_REG, &data_val[0]);
mfc_reg_read(charger->client, MFC_AP2MFC_CMD_L_REG, &data_val[0]);
msleep(100);
}
charger->is_afc_tx = true;
pr_info("%s: is_afc_tx = %d vout read = %d \n",
__func__, charger->is_afc_tx, mfc_get_adc(charger, MFC_ADC_VOUT));
/* use all CM FETs for 10V wireless charging */
mfc_reg_write(charger->client, MFC_RX_COMM_MOD_FET_REG, 0x00);
mfc_reg_read(charger->client, MFC_RX_COMM_MOD_FET_REG, &cmd);
pr_info("%s: CM FET setting(0x%x) \n", __func__, cmd);
}
charger->vout_mode = val->intval;
cancel_delayed_work(&charger->wpc_vout_mode_work);
wake_lock(&charger->wpc_vout_mode_lock);
queue_delayed_work(charger->wqueue,
&charger->wpc_vout_mode_work, msecs_to_jiffies(250));
} else {
pr_info("%s: block to set high vout level(vs=%d) because full status\n",
__func__, charger->pdata->vout_status);
}
} else if (val->intval == WIRELESS_PAD_FAN_OFF) {
pr_info("%s: fan off\n", __func__);
mfc_fan_control(charger, 0);
} else if (val->intval == WIRELESS_PAD_FAN_ON) {
pr_info("%s: fan on\n", __func__);
mfc_fan_control(charger, 1);
} else if (val->intval == WIRELESS_PAD_LED_OFF) {
pr_info("%s: led off\n", __func__);
mfc_led_control(charger, 0);
} else if (val->intval == WIRELESS_PAD_LED_ON) {
pr_info("%s: led on\n", __func__);
mfc_led_control(charger, 1);
} else if (val->intval == WIRELESS_VRECT_ADJ_ON) {
pr_info("%s: vrect adjust to have big headroom(default value)\n", __func__);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_1);
} else if (val->intval == WIRELESS_VRECT_ADJ_OFF) {
pr_info("%s: vrect adjust to have small headroom\n", __func__);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_0);
} else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_0) {
pr_info("%s: vrect adjust to have headroom 0(0mV)\n", __func__);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_0);
} else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_1) {
pr_info("%s: vrect adjust to have headroom 1(277mV)\n", __func__);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_1);
} else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_2) {
pr_info("%s: vrect adjust to have headroom 2(497mV)\n", __func__);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_2);
} else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_3) {
pr_info("%s: vrect adjust to have headroom 3(650mV)\n", __func__);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_3);
} else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_4) {
pr_info("%s: vrect adjust to have headroom 4(30mV)\n", __func__);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_4);
} else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_5) {
pr_info("%s: vrect adjust to have headroom 5(82mV)\n", __func__);
mfc_set_vrect_adjust(charger, MFC_HEADROOM_5);
} else if (val->intval == WIRELESS_CLAMP_ENABLE) {
pr_info("%s: enable clamp1, clamp2 for WPC modulation\n", __func__);
//default enabled state. no need to config.
//mfc_reg_update(charger->client, MFC_RX_COMM_MOD_FET_REG, 0x00, 0x00);
} else {
pr_info("%s: Unknown Command(%d)\n", __func__, val->intval);
}
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
charger->pdata->otp_firmware_result = val->intval;
pr_info("%s: otp_firmware result initialize (%d)\n", __func__,
charger->pdata->otp_firmware_result);
break;
#if defined(CONFIG_UPDATE_BATTERY_DATA)
case POWER_SUPPLY_PROP_POWER_DESIGN:
mfc_chg_parse_dt(charger->dev, charger->pdata);
break;
#endif
case POWER_SUPPLY_PROP_ENERGY_NOW:
/* send battery level to TX */
if (val->intval != charger->pdata->capacity) {
charger->pdata->capacity = val->intval;
pr_info("%s Send Capacity(%d) to TX\n", __func__, charger->pdata->capacity);
mfc_send_packet(charger, MFC_HEADER_AFC_CONF,
WPC_COM_CHG_LEVEL, &(charger->pdata->capacity), 1);
msleep(250);
mfc_send_packet(charger, MFC_HEADER_AFC_CONF,
WPC_COM_CHG_LEVEL, &(charger->pdata->capacity), 1);
}
vout = mfc_get_adc(charger, MFC_ADC_VOUT);
vrect = mfc_get_adc(charger, MFC_ADC_VRECT);
iout = mfc_get_adc(charger, MFC_ADC_RX_IOUT);
freq = mfc_get_adc(charger, MFC_ADC_OP_FRQ);
pr_info("%s RX_VOUT = %dmV, RX_VRECT = %dmV, RX_IOUT = %dmA, OP_FREQ = %dKHz, IC Rev = 0x%x, IC Font = 0x%x, cable=%d\n",
__func__, vout, vrect, iout, freq, mfc_get_ic_revision(charger, MFC_IC_REVISION),
mfc_get_ic_revision(charger, MFC_IC_FONT), charger->pdata->cable_type);
if ((vout < 6500) && (charger->pdata->capacity >= 85)) {
mfc_reg_read(charger->client, MFC_RX_COMM_MOD_FET_REG, &tmp);
if (tmp != 0x00) {
/* use all CM FETs for 10V wireless charging */
mfc_reg_write(charger->client, MFC_RX_COMM_MOD_FET_REG, 0x00);
mfc_reg_read(charger->client, MFC_RX_COMM_MOD_FET_REG, &tmp);
pr_info("%s: CM FET setting(0x%x)\n", __func__, tmp);
}
}
for (i = 0; i < MFC_NUM_FOD_REG; i++)
mfc_reg_read(charger->client, MFC_WPC_FOD_0A_REG+i, &fod[i]);
pr_info("%s: FOD(%d %d %d %d %d %d %d %d %d %d %d %d)\n", __func__,
fod[0], fod[1], fod[2], fod[3], fod[4], fod[5],
fod[6], fod[7], fod[8], fod[9], fod[10], fod[11]);
break;
case POWER_SUPPLY_PROP_FILTER_CFG:
charger->led_cover = val->intval;
pr_info("%s: LED_COVER(%d)\n", __func__, charger->led_cover);
break;
case POWER_SUPPLY_PROP_CHARGE_EMPTY:
mfc_reg_read(charger->client, MFC_STATUS_L_REG, &tmp);
tmp = tmp >> 7;
pr_info("%s: MFC LDO (%d), vout (%d)\n", __func__, val->intval, tmp);
if (val->intval && !tmp) { /* LDO ON */
mfc_set_cmd_l_reg(charger, MFC_CMD_TOGGLE_LDO_MASK, MFC_CMD_TOGGLE_LDO_MASK);
pr_info("%s: MFC LDO toggle ------------ cable_work\n", __func__);
msleep(3);
mfc_reg_read(charger->client, MFC_STATUS_L_REG, &tmp);
tmp = tmp >> 7;
pr_info("%s: MFC LDO STAT(%d)\n", __func__, tmp);
} else if (!val->intval && tmp) { /* LDO OFF */
pr_info("%s: MFC LDO toggle ------------ cable_work\n", __func__);
mfc_set_cmd_l_reg(charger, MFC_CMD_TOGGLE_LDO_MASK, MFC_CMD_TOGGLE_LDO_MASK);
msleep(3);
mfc_reg_read(charger->client, MFC_STATUS_L_REG, &tmp);
tmp = tmp >> 7;
pr_info("%s: MFC LDO STAT(%d)\n", __func__, tmp);
}
break;
case POWER_SUPPLY_PROP_SCOPE:
return -ENODATA;
default:
return -ENODATA;
}
return 0;
}
#define FREQ_OFFSET 384000 /* 64*6000 */
static void mfc_wpc_opfq_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_opfq_work.work);
u16 op_fq;
u8 pad_mode;
union power_supply_propval value;
mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, &pad_mode);
if ((pad_mode == PAD_MODE_WPC_BASIC) ||\
(pad_mode == PAD_MODE_WPC_ADV)) {
op_fq = mfc_get_adc(charger, MFC_ADC_OP_FRQ);
pr_info("%s: Operating FQ %dkHz(0x%x)\n", __func__, op_fq, op_fq);
if (op_fq > 230) { /* wpc threshold 230kHz */
pr_info("%s: Reset M0\n",__func__);
mfc_reg_write(charger->client, 0x3040, 0x80); /*restart M0 */
charger->pdata->opfq_cnt++;
if (charger->pdata->opfq_cnt <= CMD_CNT) {
queue_delayed_work(charger->wqueue, &charger->wpc_opfq_work, msecs_to_jiffies(10000));
return;
}
}
} else if ((pad_mode == PAD_MODE_PMA_SR1) ||\
(pad_mode == PAD_MODE_PMA_SR1E)) {
charger->pdata->cable_type = MFC_PAD_PMA;
value.intval = SEC_WIRELESS_PAD_PMA;
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
}
charger->pdata->opfq_cnt = 0;
wake_unlock(&charger->wpc_opfq_lock);
}
static void mfc_wpc_det_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_det_work.work);
int wc_w_state;
union power_supply_propval value;
u8 pad_mode;
u8 vrect;
mfc_get_chip_id(charger);
pr_info("%s : first chip_id read(%d)\n", __func__, charger->chip_id);
if (charger->chip_id == MFC_CHIP_LSI) {
/*
* We don't have to handle the wpc detect handling,
* when it's the MST mode.
*/
if(charger->is_mst_on == MST_MODE_2) {
pr_info("%s MST RETURN!\n",__func__);
return;
}
if(charger->mst_off_lock == 1) {
pr_info("%s MST Off Lock!\n",__func__);
return;
}
}
if (charger->is_mst_on == MST_MODE_2) {
pr_info("%s: skip wpc_det_work for MST operation\n", __func__);
return;
}
wake_lock(&charger->wpc_wake_lock);
pr_info("%s\n",__func__);
wc_w_state = gpio_get_value(charger->pdata->wpc_det);
if ((charger->wc_w_state == 0) && (wc_w_state == 1)) {
charger->pdata->vout_status = MFC_VOUT_5V;
#if 0 /* To prepare for the future issue */
/* read firmware version */
if(mfc_get_firmware_version(charger, MFC_RX_FIRMWARE) == MFC_OTP_FIRM_VERSION && adc_cal > 0)
mfc_runtime_sram_change(charger);/* change sram */
#endif
mfc_get_chip_id(charger);
/* enable Mode Change INT */
mfc_reg_update(charger->client, MFC_INT_A_ENABLE_L_REG,
MFC_STAT_L_OP_MODE_MASK, MFC_STAT_L_OP_MODE_MASK);
/* read vrect adjust */
mfc_reg_read(charger->client, MFC_VRECT_ADJ_REG, &vrect);
pr_info("%s: wireless charger activated, set V_INT as PN\n",__func__);
/* read pad mode */
mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, &pad_mode);
pad_mode = pad_mode >> 5;
pr_info("%s: Pad type (0x%x)\n", __func__, pad_mode);
if ((pad_mode == PAD_MODE_PMA_SR1) ||
(pad_mode == PAD_MODE_PMA_SR1E)) {
charger->pdata->cable_type = MFC_PAD_PMA;
value.intval = SEC_WIRELESS_PAD_PMA;
psy_do_property("wireless", set,
POWER_SUPPLY_PROP_ONLINE, value);
} else if ((pad_mode == PAD_MODE_WPC_BASIC) ||
(pad_mode == PAD_MODE_WPC_ADV)) {
charger->pdata->cable_type = MFC_PAD_WPC;
value.intval = SEC_WIRELESS_PAD_WPC;
psy_do_property("wireless", set,
POWER_SUPPLY_PROP_ONLINE, value);
wake_lock(&charger->wpc_opfq_lock);
queue_delayed_work(charger->wqueue, &charger->wpc_opfq_work, msecs_to_jiffies(10000));
} else if ((pad_mode == PAD_MODE_A4WP) ||
(pad_mode == PAD_MODE_A4WP_LPM)) {
/* Enable BT2AP INT src */
mfc_reg_write(charger->client, MFC_INT_A_ENABLE_L_REG, 0x03); // ENABLE BT2AP INTR
mfc_reg_write(charger->client, MFC_INT_A_ENABLE_H_REG, 0x80); // ENABLE BT2AP INTR
/* Enable AP2BT INT src */
mfc_reg_write(charger->client, MFC_INT_B_ENABLE_REG, 0x83); // ENABLE AP2BT INTR
charger->pdata->cable_type = MFC_PAD_A4WP;
value.intval = SEC_WIRELESS_PAD_WPC;
psy_do_property("wireless", set,
POWER_SUPPLY_PROP_ONLINE, value);
}
/* set fod value */
if(charger->pdata->fod_data_check)
mfc_fod_set(charger);
/* set request afc_tx */
mfc_send_command(charger, MFC_REQUEST_AFC_TX);
/* set rpp scaling factor for LED cover */
mfc_rpp_set(charger);
#if 0
/* set request TX_ID */
mfc_send_command(charger, MFC_REQUEST_TX_ID);
#endif
charger->pdata->is_charging = 1;
} else if ((charger->wc_w_state == 1) && (wc_w_state == 0)) {
charger->pdata->cable_type = MFC_PAD_NONE;
charger->pdata->is_charging = 0;
charger->pdata->vout_status = MFC_VOUT_5V;
charger->pdata->opfq_cnt = 0;
charger->pdata->tx_data_cmd = 0;
charger->pdata->tx_data_val = 0;
charger->vout_mode = 0;
charger->is_full_status = 0;
charger->pdata->capacity = 101;
charger->is_afc_tx = false;
value.intval = SEC_WIRELESS_PAD_NONE;
psy_do_property("wireless", set,
POWER_SUPPLY_PROP_ONLINE, value);
pr_info("%s: wpc deactivated, set V_INT as PD\n",__func__);
msleep(1000);
/* if vrect >= 3000mV and vout <= 2000mV, restart M0 */
if (mfc_get_adc(charger, MFC_ADC_VRECT) >= 3000 &&
mfc_get_adc(charger, MFC_ADC_VOUT) <= 2000) {
pr_err("%s Restart M0\n", __func__);
/* reset MCU of MFC IC */
mfc_set_cmd_l_reg(charger, MFC_CMD_MCU_RESET_MASK, MFC_CMD_MCU_RESET_MASK);
}
if (delayed_work_pending(&charger->wpc_opfq_work)) {
wake_unlock(&charger->wpc_opfq_lock);
cancel_delayed_work(&charger->wpc_opfq_work);
}
if (delayed_work_pending(&charger->wpc_afc_vout_work)) {
wake_unlock(&charger->wpc_afc_vout_lock);
cancel_delayed_work(&charger->wpc_afc_vout_work);
}
if (delayed_work_pending(&charger->wpc_vout_mode_work)) {
wake_unlock(&charger->wpc_vout_mode_lock);
cancel_delayed_work(&charger->wpc_vout_mode_work);
}
cancel_delayed_work(&charger->wpc_isr_work);
cancel_delayed_work(&charger->wpc_opfq_work);
cancel_delayed_work(&charger->wpc_tx_id_work);
}
pr_info("%s: w(%d to %d)\n", __func__,
charger->wc_w_state, wc_w_state);
charger->wc_w_state = wc_w_state;
wake_unlock(&charger->wpc_wake_lock);
}
/* INT_A (BT2AP interrupt) */
static void mfc_wpc_isr_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_isr_work.work);
u8 cmd_data, val_data;
int i;
union power_supply_propval value;
if (!charger->wc_w_state) {
pr_info("%s: charger->wc_w_state is 0. exit wpc_isr_work.\n",__func__);
return;
}
pr_info("%s: cable_type (0x%x)\n", __func__, charger->pdata->cable_type);
wake_lock(&charger->wpc_wake_lock);
pr_info("%s\n",__func__);
if (charger->pdata->cable_type == MFC_PAD_A4WP) {
mfc_reg_read(charger->client, MFC_BT2AP_DATA_COM_REG, &cmd_data);
mfc_reg_read(charger->client, MFC_BT2AP_DATA_VALUE0_REG, &val_data);
charger->pdata->tx_data_cmd = cmd_data;
charger->pdata->tx_data_val = val_data;
pr_info("%s: A4WP Interrupt Occured, CMD : 0x%x, DATA : 0x%x\n",
__func__, cmd_data, val_data);
switch (cmd_data)
{
case BT2AP_COM_TX_ID:
switch (val_data) {
case TX_ID_UNKNOWN:
case TX_ID_BATT_PACK_TA:
case TX_ID_BATT_PACK:
case TX_ID_STAND_TYPE_START:
default:
break;
} //cmd_data : BT2AP_COM_TX_ID switch end
break;
case BT2AP_COM_REQ_AFC_TX:
break;
case BT2AP_COM_AFC_MODE:
switch (val_data) {
case TX_AFC_SET_5V:
charger->pad_vout = PAD_VOUT_5V;
break;
case TX_AFC_SET_10V:
pr_info("%s data = 0x%x, might be 10V irq \n", __func__, val_data);
if (!gpio_get_value(charger->pdata->wpc_det)) {
wake_unlock(&charger->wpc_wake_lock);
return;
}
mfc_send_command(charger, MFC_AFC_CONF_10V);
msleep(500);
charger->pdata->cable_type = MFC_PAD_A4WP;
/* If A4WP_HV is supported, then SEC_WIRELESS_PAD_A4WP_HV type should be used.
and sec_battery and charger file also have to change wireless cable type.*/
value.intval = SEC_WIRELESS_PAD_WPC_HV;
psy_do_property("wireless", set,
POWER_SUPPLY_PROP_ONLINE, value);
for(i = 0; i < CMD_CNT - 1; i++) {
if (!gpio_get_value(charger->pdata->wpc_det)) {
wake_unlock(&charger->wpc_wake_lock);
return;
}
if (mfc_get_adc(charger, MFC_ADC_VOUT) > 7500) {
pr_info("%s 10V set is done \n", __func__);
break;
} else {
pr_info("%s send AFC_CONF_10V again \n", __func__);
mfc_send_command(charger, MFC_AFC_CONF_10V);
msleep(500);
}
}
if(sleep_mode) {
pr_info("%s sleep mode, turn on fan \n", __func__);
mfc_fan_control(charger, true);
msleep(250);
pr_info("%s sleep mode, turn off fan \n", __func__);
mfc_fan_control(charger, false);
msleep(250);
}
charger->pad_vout = PAD_VOUT_10V;
break;
case TX_AFC_SET_12V:
case TX_AFC_SET_18V:
case TX_AFC_SET_19V:
case TX_AFC_SET_20V:
case TX_AFC_SET_24V:
default:
pr_info("%s: unsupport : 0x%x", __func__, val_data);
}
break;
case BT2AP_COM_CHG_STATUS:
case BT2AP_COM_UNKNOWN:
case BT2AP_COM_PWR_STATUS:
case BT2AP_COM_SID_TAG:
case BT2AP_COM_SID_TOKEN:
case BT2AP_COM_TX_STANDBY:
case BT2AP_COM_COOLING_CTRL:
default:
break;
}
} else { /* WPC, PMA */
mfc_reg_read(charger->client, MFC_WPC_TX_DATA_COM_REG, &cmd_data);
mfc_reg_read(charger->client, MFC_WPC_TX_DATA_VALUE0_REG, &val_data);
charger->pdata->tx_data_cmd = cmd_data;
charger->pdata->tx_data_val = val_data;
pr_info("%s: WPC Interrupt Occured, CMD : 0x%x, DATA : 0x%x\n",
__func__, cmd_data, val_data);
if (cmd_data == WPC_TX_COM_AFC_SET) {
switch (val_data) {
case TX_AFC_SET_5V:
charger->pad_vout = PAD_VOUT_5V;
break;
case TX_AFC_SET_10V:
pr_info("%s data = 0x%x, might be 10V irq \n", __func__, val_data);
if (!gpio_get_value(charger->pdata->wpc_det)) {
pr_err("%s Wireless charging is paused during set high voltage. \n", __func__);
wake_unlock(&charger->wpc_wake_lock);
return;
}
if (charger->pdata->cable_type == MFC_PAD_WPC_AFC ||
charger->pdata->cable_type == MFC_PAD_PREPARE_HV ||
charger->pdata->cable_type == MFC_PAD_WPC_STAND_HV ||
charger->pdata->cable_type == MFC_PAD_WPC_VEHICLE_HV) {
pr_err("%s: Is is already HV wireless cable. No need to set again \n", __func__);
wake_unlock(&charger->wpc_wake_lock);
return;
}
/* send AFC_SET */
mfc_send_command(charger, MFC_AFC_CONF_10V);
msleep(500);
/* change cable type */
charger->pdata->cable_type = MFC_PAD_PREPARE_HV;
value.intval = SEC_WIRELESS_PAD_PREPARE_HV;
psy_do_property("wireless", set,
POWER_SUPPLY_PROP_ONLINE, value);
if(sleep_mode) {
pr_info("%s sleep mode, turn on fan \n", __func__);
mfc_fan_control(charger, true);
msleep(250);
pr_info("%s sleep mode, turn off fan \n", __func__);
mfc_fan_control(charger, false);
msleep(250);
}
charger->pad_vout = PAD_VOUT_10V;
break;
case TX_AFC_SET_12V:
break;
case TX_AFC_SET_18V:
case TX_AFC_SET_19V:
case TX_AFC_SET_20V:
case TX_AFC_SET_24V:
break;
case TX_ID_VEHICLE_PAD:
pr_info("%s: VEHICLE PAD\n", __func__);
charger->pdata->cable_type = MFC_PAD_WPC_VEHICLE;
value.intval = SEC_WIRELESS_PAD_VEHICLE;
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
break;
case TX_ID_BATT_PACK:
pr_info("%s: WIRELESS BATTERY PACK\n", __func__);
charger->pdata->cable_type = MFC_PAD_WPC_PACK;
value.intval = SEC_WIRELESS_PAD_WPC_PACK;
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
break;
case TX_ID_BATT_PACK_TA:
pr_info("%s: WIRELESS BATTERY PACK with TA\n", __func__);
charger->pdata->cable_type = MFC_PAD_WPC_PACK_TA;
value.intval = SEC_WIRELESS_PAD_WPC_PACK_TA;
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
break;
default:
pr_info("%s: unsupport : 0x%x", __func__, val_data);
}
queue_delayed_work(charger->wqueue, &charger->wpc_tx_id_work, msecs_to_jiffies(1000));
} else if (cmd_data == WPC_TX_COM_TX_ID) {
switch (val_data) {
case TX_ID_UNKNOWN:
break;
case TX_ID_VEHICLE_PAD:
if (charger->pad_vout == PAD_VOUT_10V) {
if (charger->pdata->cable_type == MFC_PAD_PREPARE_HV) {
charger->pdata->cable_type = MFC_PAD_WPC_VEHICLE_HV;
value.intval = SEC_WIRELESS_PAD_PREPARE_HV;
} else {
charger->pdata->cable_type = MFC_PAD_WPC_VEHICLE_HV;
value.intval = SEC_WIRELESS_PAD_VEHICLE_HV;
}
} else {
charger->pdata->cable_type = MFC_PAD_WPC_VEHICLE;
value.intval = SEC_WIRELESS_PAD_VEHICLE;
}
pr_info("%s: VEHICLE Wireless Charge PAD %s\n", __func__,
charger->pad_vout == PAD_VOUT_10V ? "HV" : "");
break;
case TX_ID_STAND_TYPE_START:
if (charger->pad_vout == PAD_VOUT_10V) {
if (charger->pdata->cable_type == MFC_PAD_PREPARE_HV) {
charger->pdata->cable_type = MFC_PAD_WPC_STAND_HV;
value.intval = SEC_WIRELESS_PAD_PREPARE_HV;
} else {
charger->pdata->cable_type = MFC_PAD_WPC_STAND_HV;
value.intval = SEC_WIRELESS_PAD_WPC_STAND_HV;
}
} else {
charger->pdata->cable_type = MFC_PAD_WPC_STAND;
value.intval = SEC_WIRELESS_PAD_WPC_STAND;
mfc_fod_set_hero_5v(charger);
}
pr_info("%s: STAND Wireless Charge PAD %s\n", __func__,
charger->pad_vout == PAD_VOUT_10V ? "HV" : "");
pr_info("%s: cable_type(%d)\n", __func__, charger->pdata->cable_type);
break;
case TX_ID_BATT_PACK:
charger->pdata->cable_type = MFC_PAD_WPC_PACK;
value.intval = SEC_WIRELESS_PAD_WPC_PACK;
pr_info("%s: WIRELESS BATTERY PACK\n", __func__);
break;
case TX_ID_BATT_PACK_TA:
charger->pdata->cable_type = MFC_PAD_WPC_PACK_TA;
value.intval = SEC_WIRELESS_PAD_WPC_PACK_TA;
pr_info("%s: WIRELESS BATTERY PACK with TA\n", __func__);
break;
default:
value.intval = charger->pdata->cable_type;
pr_info("%s: UNDEFINED PAD : 0x%x\n", __func__, val_data);
break;
}
if (value.intval != MFC_PAD_PREPARE_HV)
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
pr_info("%s: TX_ID : 0x%x\n", __func__, val_data);
value.intval = val_data;
psy_do_property("wireless", set, POWER_SUPPLY_PROP_AUTHENTIC, value);
}
}
wake_unlock(&charger->wpc_wake_lock);
}
static void mfc_wpc_tx_id_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_tx_id_work.work);
pr_info("%s\n",__func__);
mfc_send_command(charger, MFC_REQUEST_TX_ID);
}
/*
* Prevent the un-wanted INT_A Falling handling.
* This is a work-around, and will be fixed by the revision.
*/
static void mfc_mst_off_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, mst_off_work.work);
pr_info("%s\n",__func__);
charger->mst_off_lock = 1;
msleep(25);
charger->mst_off_lock = 0;
}
static irqreturn_t mfc_wpc_det_irq_thread(int irq, void *irq_data)
{
struct mfc_charger_data *charger = irq_data;
pr_info("%s !\n",__func__);
if (charger->is_probed)
queue_delayed_work(charger->wqueue, &charger->wpc_det_work, 0);
else
pr_info("%s: prevent work thread before device is probed.\n", __func__);
return IRQ_HANDLED;
}
/* mfc_mst_routine : MST dedicated codes */
void mfc_mst_routine(struct mfc_charger_data *charger, u8 *irq_src)
{
if(charger->is_mst_on == MST_MODE_2) {
/* clear intterupt */
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src[0]); // clear int
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src[1]); // clear int
mfc_set_cmd_l_reg(charger, 0x20, MFC_CMD_CLEAR_INT_MASK); // command
mfc_reg_write(charger->client, MFC_MST_MODE_SEL_REG, 0x02); /* set MST mode2 */
pr_info("%s 2AC Missing ! : MST on REV : %d\n", __func__, charger->pdata->wc_ic_rev);
/* clear intterupt */
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src[0]); // clear int
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src[1]); // clear int
mfc_set_cmd_l_reg(charger, 0x20, MFC_CMD_CLEAR_INT_MASK); // command
msleep(10);
}
}
static irqreturn_t mfc_wpc_irq_thread(int irq, void *irq_data)
{
struct mfc_charger_data *charger = irq_data;
int wc_w_state_irq;
int ret;
u8 irq_src[2];
u8 reg_data;
// u8 cnt = 0;
if ((charger->chip_id == MFC_CHIP_LSI) && (charger->mst_off_lock == 1)) {
pr_info("%s MST Off Lock!\n",__func__);
return IRQ_NONE;
}
pr_info("%s !\n",__func__);
wake_lock(&charger->wpc_wake_lock);
ret = mfc_reg_read(charger->client, MFC_INT_A_L_REG, &irq_src[0]);
ret = mfc_reg_read(charger->client, MFC_INT_A_H_REG, &irq_src[1]);
wc_w_state_irq = gpio_get_value(charger->pdata->wpc_int);
pr_info("%s wc_w_state_irq = %d\n", __func__, wc_w_state_irq);
if (ret < 0) {
pr_err("%s: Failed to read interrupt source: %d\n",
__func__, ret);
wake_unlock(&charger->wpc_wake_lock);
//return IRQ_NONE;
goto INT_ERROR;
}
if(irq_src[1] & MFC_STAT_H_AC_MISSING_DET_MASK) {
pr_info("%s 1AC Missing ! : MST on REV : %d\n", __func__, charger->pdata->wc_ic_rev);
mfc_mst_routine(charger, irq_src);
}
pr_info("%s: interrupt source(0x%x)\n", __func__, irq_src[1] << 8 | irq_src[0]);
mfc_get_firmware_version(charger, MFC_RX_FIRMWARE);
if(irq_src[0] & MFC_STAT_L_OP_MODE_MASK) {
ret = mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, &reg_data);
reg_data &= 0x0C; /* use only [3:2]bit of sys_op_mode register for MST */
pr_info("%s MODE CHANGE IRQ ! (0x%x)\n", __func__, reg_data);
}
if ((irq_src[0] & MFC_STAT_L_OVER_VOL_MASK) ||
(irq_src[0] & MFC_STAT_L_OVER_CURR_MASK) ||
(irq_src[0] & MFC_STAT_L_OVER_TEMP_MASK)) {
pr_info("%s ABNORMAL STAT IRQ ! \n", __func__);
//ret = mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, &reg_data);
}
if(irq_src[0] & MFC_STAT_L_INT_LPM_MASK) {
pr_info("%s INT LPM IRQ ! \n", __func__);
}
if(irq_src[0] & MFC_STAT_L_BT2AP_DATA_MASK) {
pr_info("%s BT2AP DATA IRQ ! \n", __func__);
if(!delayed_work_pending(&charger->wpc_isr_work))
queue_delayed_work(charger->wqueue, &charger->wpc_isr_work, msecs_to_jiffies(1000));
}
if(irq_src[1] & MFC_STAT_H_TX_DATA_RECEIVED_MASK) {
pr_info("%s TX RECEIVED IRQ ! \n", __func__);
if(charger->pdata->cable_type == MFC_PAD_WPC_STAND||
charger->pdata->cable_type == MFC_PAD_WPC_STAND_HV)
pr_info("%s Don't run ISR_WORK for NO ACK ! \n", __func__);
else if(!delayed_work_pending(&charger->wpc_isr_work))
queue_delayed_work(charger->wqueue, &charger->wpc_isr_work, msecs_to_jiffies(1000));
}
if(irq_src[1] & MFC_STAT_H_TX_OVER_CURR_MASK) {
pr_info("%s TX OVER CURRENT IRQ ! \n", __func__);
}
if(irq_src[1] & MFC_STAT_H_TX_OVER_TEMP_MASK) {
pr_info("%s TX OVER TEMP IRQ ! \n", __func__);
}
if(irq_src[1] & MFC_STAT_H_TX_CON_DISCON_MASK) {
pr_info("%s TX CONNECT IRQ ! \n", __func__);
charger->pdata->tx_status = SEC_TX_POWER_TRANSFER;
}
/* clear intterupt */
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src[0]); // clear int
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src[1]); // clear int
mfc_set_cmd_l_reg(charger, 0x20, MFC_CMD_CLEAR_INT_MASK); // command
/* debug */
ret = mfc_reg_read(charger->client, MFC_INT_A_L_REG, &irq_src[0]);
ret = mfc_reg_read(charger->client, MFC_INT_A_H_REG, &irq_src[1]);
wc_w_state_irq = gpio_get_value(charger->pdata->wpc_int);
pr_info("%s wc_w_state_irq = %d\n", __func__, wc_w_state_irq);
wake_unlock(&charger->wpc_wake_lock);
return IRQ_HANDLED;
INT_ERROR:
/* clear intterupt */
pr_info("%s interrup error!\n", __func__);
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src[0]); // clear int
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src[1]); // clear int
mfc_set_cmd_l_reg(charger, 0x20, MFC_CMD_CLEAR_INT_MASK); // command
wake_unlock(&charger->wpc_wake_lock);
return IRQ_NONE;
}
static int mfc_chg_parse_dt(struct device *dev,
mfc_charger_platform_data_t *pdata)
{
int ret = 0;
struct device_node *np = dev->of_node;
enum of_gpio_flags irq_gpio_flags;
int len,i;
const u32 *p;
if (!np) {
pr_err("%s np NULL\n", __func__);
return 1;
} else {
p = of_get_property(np, "battery,fod_wpc_data", &len);
if (p) {
len = len / sizeof(u32);
pdata->fod_wpc_data = kzalloc(sizeof(*pdata->fod_wpc_data) * len, GFP_KERNEL);
ret = of_property_read_u32_array(np, "battery,fod_wpc_data",
pdata->fod_wpc_data, len);
pdata->fod_data_check = 1;
for(i = 0; i <len; i++)
pr_info("%s fod WPC data = %d ",__func__,pdata->fod_wpc_data[i]);
} else {
pdata->fod_data_check = 0;
pr_err("%s there is not fod_wpc_data\n", __func__);
}
p = of_get_property(np, "battery,fod_pma_data", &len);
if (p) {
len = len / sizeof(u32);
pdata->fod_pma_data = kzalloc(sizeof(*pdata->fod_pma_data) * len, GFP_KERNEL);
ret = of_property_read_u32_array(np, "battery,fod_pma_data",
pdata->fod_pma_data, len);
pdata->fod_data_check = 1;
for(i = 0; i <len; i++)
pr_info("%s fod PMA data = %d ",__func__,pdata->fod_pma_data[i]);
} else {
pdata->fod_data_check = 0;
pr_err("%s there is not fod_pma_data\n", __func__);
}
p = of_get_property(np, "battery,fod_a4wp_data", &len);
if (p) {
len = len / sizeof(u32);
pdata->fod_a4wp_data = kzalloc(sizeof(*pdata->fod_a4wp_data) * len, GFP_KERNEL);
ret = of_property_read_u32_array(np, "battery,fod_a4wp_data",
pdata->fod_a4wp_data, len);
pdata->fod_data_check = 1;
for(i = 0; i <len; i++)
pr_info("%s fod A4WP data = %d ",__func__,pdata->fod_a4wp_data[i]);
} else {
pdata->fod_data_check = 0;
pr_err("%s there is not fod_a4wp_data\n", __func__);
}
p = of_get_property(np, "battery,fod_wpc_data_cv", &len);
if (p) {
len = len / sizeof(u32);
pdata->fod_wpc_data_cv = kzalloc(sizeof(*pdata->fod_wpc_data_cv) * len, GFP_KERNEL);
ret = of_property_read_u32_array(np, "battery,fod_wpc_data_cv",
pdata->fod_wpc_data_cv, len);
pdata->fod_data_check = 1;
for(i = 0; i <len; i++)
pr_info("%s fod WPC data_cv = %d ",__func__,pdata->fod_wpc_data_cv[i]);
} else {
pdata->fod_data_check = 0;
pr_err("%s there is not fod_wpc_data_cv\n", __func__);
}
p = of_get_property(np, "battery,fod_pma_data_cv", &len);
if (p) {
len = len / sizeof(u32);
pdata->fod_pma_data_cv = kzalloc(sizeof(*pdata->fod_pma_data_cv) * len, GFP_KERNEL);
ret = of_property_read_u32_array(np, "battery,fod_pma_data_cv",
pdata->fod_pma_data_cv, len);
pdata->fod_data_check = 1;
for(i = 0; i <len; i++)
pr_info("%s fod PMA data_cv = %d ",__func__,pdata->fod_pma_data_cv[i]);
} else {
pdata->fod_data_check = 0;
pr_err("%s there is not fod_pma_data_cv\n", __func__);
}
p = of_get_property(np, "battery,fod_a4wp_data_cv", &len);
if (p) {
len = len / sizeof(u32);
pdata->fod_a4wp_data_cv = kzalloc(sizeof(*pdata->fod_a4wp_data_cv) * len, GFP_KERNEL);
ret = of_property_read_u32_array(np, "battery,fod_a4wp_data_cv",
pdata->fod_a4wp_data_cv, len);
pdata->fod_data_check = 1;
for(i = 0; i <len; i++)
pr_info("%s fod A4WP data_cv = %d ",__func__,pdata->fod_a4wp_data_cv[i]);
} else {
pdata->fod_data_check = 0;
pr_err("%s there is not fod_a4wp_data_cv\n", __func__);
}
p = of_get_property(np, "battery,fod_hero_5v_data", &len);
if (p) {
len = len / sizeof(u32);
pdata->fod_hero_5v_data = kzalloc(sizeof(*pdata->fod_hero_5v_data) * len, GFP_KERNEL);
ret = of_property_read_u32_array(np, "battery,fod_hero_5v_data",
pdata->fod_hero_5v_data, len);
for(i = 0; i <len; i++)
pr_info("%s fod Hero 5V data = 0x%x ",__func__,pdata->fod_hero_5v_data[i]);
} else {
pr_err("%s there is not fod_hero_5v_data\n", __func__);
}
ret = of_property_read_string(np,
"battery,wireless_charger_name", (char const **)&pdata->wireless_charger_name);
if (ret < 0)
pr_info("%s: Wireless Charger name is Empty\n", __func__);
ret = of_property_read_string(np,
"battery,charger_name", (char const **)&pdata->wired_charger_name);
if (ret < 0)
pr_info("%s: Charger name is Empty\n", __func__);
ret = of_property_read_string(np,
"battery,fuelgauge_name", (char const **)&pdata->fuelgauge_name);
if (ret < 0)
pr_info("%s: Fuelgauge name is Empty\n", __func__);
ret = of_property_read_u32(np, "battery,wpc_cc_cv_vout",
&pdata->wpc_cc_cv_vout);
if (ret < 0)
pr_info("%s: wpc_cv_call_vout is Empty \n", __func__);
ret = of_property_read_u32(np, "battery,wpc_cv_call_vout",
&pdata->wpc_cv_call_vout);
if (ret < 0)
pr_info("%s: wpc_cv_call_vout is Empty \n", __func__);
ret = of_property_read_u32(np, "battery,wpc_cc_call_vout",
&pdata->wpc_cc_call_vout);
if (ret < 0)
pr_info("%s: wpc_cc_call_vout is Empty \n", __func__);
ret = of_property_read_u32(np, "battery,hv_vout_wa",
&pdata->hv_vout_wa);
if (ret < 0) {
pr_info("%s: no need hv_vout_wa. \n", __func__);
pdata->hv_vout_wa = 0;
}
ret = of_property_read_u32(np, "battery,mst_switch_delay",
&pdata->mst_switch_delay);
if (ret < 0) {
pr_info("%s: mst_switch_delay is Empty \n", __func__);
pdata->mst_switch_delay = 1000; /* set default value (dream) */
}
ret = of_property_read_u32(np, "battery,wc_cover_rpp",
&pdata->wc_cover_rpp);
if (ret < 0) {
pr_info("%s: fail to read wc_cover_rpp. \n", __func__);
pdata->wc_cover_rpp = 0x55;
}
ret = of_property_read_u32(np, "battery,wc_hv_rpp",
&pdata->wc_hv_rpp);
if (ret < 0) {
pr_info("%s: fail to read wc_hv_rpp. \n", __func__);
pdata->wc_hv_rpp = 0x40;
}
/* wpc_det */
ret = pdata->wpc_det = of_get_named_gpio_flags(np, "battery,wpc_det",
0, &irq_gpio_flags);
if (ret < 0) {
dev_err(dev, "%s : can't get wpc_det\r\n", __FUNCTION__);
} else {
pdata->irq_wpc_det = gpio_to_irq(pdata->wpc_det);
pr_info("%s wpc_det = 0x%x, irq_wpc_det = 0x%x \n",__func__, pdata->wpc_det, pdata->irq_wpc_det);
}
/* wpc_int (This GPIO means MFC_AP_INT) */
ret = pdata->wpc_int = of_get_named_gpio_flags(np, "battery,wpc_int",
0, &irq_gpio_flags);
if (ret < 0) {
dev_err(dev, "%s : can't wpc_int\r\n", __FUNCTION__);
} else {
pdata->irq_wpc_int = gpio_to_irq(pdata->wpc_int);
pr_info("%s wpc_int = 0x%x, irq_wpc_int = 0x%x \n",__func__, pdata->wpc_int, pdata->irq_wpc_int);
}
/* mst_pwr_en (MST PWR EN) */
ret = pdata->mst_pwr_en = of_get_named_gpio_flags(np, "battery,mst_pwr_en",
0, &irq_gpio_flags);
if (ret < 0) {
dev_err(dev, "%s : can't mst_pwr_en\r\n", __FUNCTION__);
}
/* wpc_en (MFC EN) */
ret = pdata->wpc_en = of_get_named_gpio_flags(np, "battery,wpc_en",
0, &irq_gpio_flags);
if (ret < 0) {
dev_err(dev, "%s : can't wpc_en\r\n", __FUNCTION__);
}
return 0;
}
}
static ssize_t mfc_store_addr(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct mfc_charger_data *charger = power_supply_get_drvdata(psy);
int x;
if (sscanf(buf, "0x%10x\n", &x) == 1) {
charger->addr = x;
}
return count;
}
static ssize_t mfc_show_addr(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct mfc_charger_data *charger = power_supply_get_drvdata(psy);
return sprintf(buf, "0x%x\n", charger->addr);
}
static ssize_t mfc_store_size(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct mfc_charger_data *charger = power_supply_get_drvdata(psy);
int x;
if (sscanf(buf, "%10d\n", &x) == 1) {
charger->size = x;
}
return count;
}
static ssize_t mfc_show_size(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct mfc_charger_data *charger = power_supply_get_drvdata(psy);
return sprintf(buf, "0x%x\n", charger->size);
}
static ssize_t mfc_store_data(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct mfc_charger_data *charger = power_supply_get_drvdata(psy);
int x;
if (sscanf(buf, "0x%10x", &x) == 1) {
u8 data = x;
if (mfc_reg_write(charger->client, charger->addr, data) < 0)
{
dev_info(charger->dev,
"%s: addr: 0x%x write fail\n", __func__, charger->addr);
}
}
return count;
}
static ssize_t mfc_show_data(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct mfc_charger_data *charger = power_supply_get_drvdata(psy);
u8 data;
int i, count = 0;;
if (charger->size == 0)
charger->size = 1;
for (i = 0; i < charger->size; i++) {
if (mfc_reg_read(charger->client, charger->addr+i, &data) < 0) {
dev_info(charger->dev,
"%s: read fail\n", __func__);
count += sprintf(buf+count, "addr: 0x%x read fail\n", charger->addr+i);
continue;
}
count += sprintf(buf+count, "addr: 0x%x, data: 0x%x\n", charger->addr+i,data);
}
return count;
}
static DEVICE_ATTR(addr, 0644, mfc_show_addr, mfc_store_addr);
static DEVICE_ATTR(size, 0644, mfc_show_size, mfc_store_size);
static DEVICE_ATTR(data, 0644, mfc_show_data, mfc_store_data);
static struct attribute *mfc_attributes[] = {
&dev_attr_addr.attr,
&dev_attr_size.attr,
&dev_attr_data.attr,
NULL
};
static const struct attribute_group mfc_attr_group = {
.attrs = mfc_attributes,
};
static const struct power_supply_desc mfc_charger_power_supply_desc = {
.name = "mfc-charger",
.type = POWER_SUPPLY_TYPE_UNKNOWN,
.properties = mfc_charger_props,
.num_properties = ARRAY_SIZE(mfc_charger_props),
.get_property = mfc_chg_get_property,
.set_property = mfc_chg_set_property,
};
static void mfc_wpc_int_req_work(struct work_struct *work)
{
struct mfc_charger_data *charger =
container_of(work, struct mfc_charger_data, wpc_int_req_work.work);
int ret = 0;
pr_info("%s\n", __func__);
/* wpc_irq */
if (charger->pdata->irq_wpc_int) {
msleep(100);
ret = request_threaded_irq(charger->pdata->irq_wpc_int,
NULL, mfc_wpc_irq_thread,
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
"wpc-irq", charger);
if (ret) {
pr_err("%s: Failed to Reqeust IRQ\n", __func__);
}
}
if (ret < 0)
free_irq(charger->pdata->irq_wpc_det, NULL);
}
static int mfc_charger_probe(
struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device_node *of_node = client->dev.of_node;
struct mfc_charger_data *charger;
mfc_charger_platform_data_t *pdata = client->dev.platform_data;
struct power_supply_config mfc_cfg = {};
int ret = 0;
int wc_w_state_irq;
dev_info(&client->dev,
"%s: MFC Charger Driver Loading\n", __func__);
if (of_node) {
pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
dev_err(&client->dev, "Failed to allocate memory\n");
return -ENOMEM;
}
ret = mfc_chg_parse_dt(&client->dev, pdata);
if (ret < 0)
goto err_parse_dt;
} else {
pdata = client->dev.platform_data;
}
charger = kzalloc(sizeof(*charger), GFP_KERNEL);
if (charger == NULL) {
dev_err(&client->dev, "Memory is not enough.\n");
ret = -ENOMEM;
goto err_wpc_nomem;
}
charger->dev = &client->dev;
ret = i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK);
if (!ret) {
ret = i2c_get_functionality(client->adapter);
dev_err(charger->dev, "I2C functionality is not supported.\n");
ret = -ENOSYS;
goto err_i2cfunc_not_support;
}
charger->client = client;
charger->pdata = pdata;
pr_info("%s: %s\n", __func__, charger->pdata->wireless_charger_name );
i2c_set_clientdata(client, charger);
charger->pdata->cable_type = MFC_PAD_NONE;
charger->pdata->is_charging = 0;
charger->pdata->tx_status = 0;
charger->pdata->cs100_status = 0;
charger->pdata->capacity = 101;
charger->pdata->vout_status = MFC_VOUT_5V;
charger->pdata->opfq_cnt = 0;
charger->is_mst_on = MST_MODE_0;
charger->chip_id = MFC_CHIP_IDT;
charger->is_otg_on = false;
charger->led_cover = 0;
charger->vout_mode = MFC_VOUT_5V;
charger->is_full_status = 0;
charger->is_afc_tx = false;
mutex_init(&charger->io_lock);
/* wpc_det */
if (charger->pdata->irq_wpc_det) {
INIT_DELAYED_WORK(&charger->wpc_det_work, mfc_wpc_det_work);
INIT_DELAYED_WORK(&charger->wpc_opfq_work, mfc_wpc_opfq_work);
}
/* wpc_irq (INT_A) */
if (charger->pdata->irq_wpc_int) {
INIT_DELAYED_WORK(&charger->wpc_isr_work, mfc_wpc_isr_work);
INIT_DELAYED_WORK(&charger->wpc_tx_id_work, mfc_wpc_tx_id_work);
INIT_DELAYED_WORK(&charger->wpc_int_req_work, mfc_wpc_int_req_work);
}
INIT_DELAYED_WORK(&charger->wpc_vout_mode_work, mfc_wpc_vout_mode_work);
INIT_DELAYED_WORK(&charger->wpc_afc_vout_work, mfc_wpc_afc_vout_work);
INIT_DELAYED_WORK(&charger->wpc_fw_update_work, mfc_wpc_fw_update_work);
INIT_DELAYED_WORK(&charger->wpc_cm_fet_work, mfc_wpc_cm_fet_work);
/*#if !defined(CONFIG_SEC_FACTORY)
INIT_DELAYED_WORK(&charger->wpc_fw_booting_work, mfc_wpc_fw_booting_work);
#endif*/
/*
* Default Idle voltage of the INT_A is LOW.
* Prevent the un-wanted INT_A Falling handling.
* This is a work-around, and will be fixed by the revision.
*/
INIT_DELAYED_WORK(&charger->mst_off_work, mfc_mst_off_work);
mfc_cfg.drv_data = charger;
charger->psy_chg = power_supply_register(charger->dev, &mfc_charger_power_supply_desc, &mfc_cfg);
if ((void *)charger->psy_chg < 0) {
pr_err("%s: Failed to Register psy_chg\n", __func__);
goto err_supply_unreg;
}
charger->wqueue = create_singlethread_workqueue("mfc_workqueue");
if (!charger->wqueue) {
pr_err("%s: Fail to Create Workqueue\n", __func__);
goto err_pdata_free;
}
wake_lock_init(&charger->wpc_wake_lock, WAKE_LOCK_SUSPEND,
"wpc_wakelock");
wake_lock_init(&charger->wpc_update_lock, WAKE_LOCK_SUSPEND,
"wpc_update_lock");
wake_lock_init(&charger->wpc_opfq_lock, WAKE_LOCK_SUSPEND,
"wpc_opfq_lock");
wake_lock_init(&charger->wpc_afc_vout_lock, WAKE_LOCK_SUSPEND,
"wpc_afc_vout_lock");
wake_lock_init(&charger->wpc_vout_mode_lock, WAKE_LOCK_SUSPEND,
"wpc_vout_mode_lock");
/* Enable interrupts after battery driver load */
/* wpc_det */
if (charger->pdata->irq_wpc_det) {
ret = request_threaded_irq(charger->pdata->irq_wpc_det,
NULL, mfc_wpc_det_irq_thread,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
IRQF_ONESHOT,
"wpd-det-irq", charger);
if (ret) {
pr_err("%s: Failed to Reqeust IRQ\n", __func__);
goto err_irq_wpc_det;
}
}
/* wpc_irq */
queue_delayed_work(charger->wqueue, &charger->wpc_int_req_work, msecs_to_jiffies(100));
wc_w_state_irq = gpio_get_value(charger->pdata->wpc_int);
pr_info("%s wc_w_state_irq = %d\n", __func__, wc_w_state_irq);
if (gpio_get_value(charger->pdata->wpc_det)) {
u8 irq_src[2];
pr_info("%s: Charger interrupt occured during lpm \n", __func__);
mfc_reg_read(charger->client, MFC_INT_A_L_REG, &irq_src[0]);
mfc_reg_read(charger->client, MFC_INT_A_H_REG, &irq_src[1]);
/* clear intterupt */
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src[0]); // clear int
mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src[1]); // clear int
mfc_set_cmd_l_reg(charger, 0x20, MFC_CMD_CLEAR_INT_MASK); // command
queue_delayed_work(charger->wqueue, &charger->wpc_det_work, 0);
if(!wc_w_state_irq && !delayed_work_pending(&charger->wpc_isr_work))
queue_delayed_work(charger->wqueue, &charger->wpc_isr_work, msecs_to_jiffies(2000));
}
/*#if !defined(CONFIG_SEC_FACTORY)
else if (!lpcharge) {
pr_info("%s: call wpc_fw_booting_work for firmware update\n", __func__);
queue_delayed_work(charger->wqueue, &charger->wpc_fw_booting_work, 0);
}
#endif*/
ret = sysfs_create_group(&charger->psy_chg->dev.kobj, &mfc_attr_group);
if (ret) {
dev_info(&client->dev,
"%s: sysfs_create_group failed\n", __func__);
}
charger->is_probed = true;
dev_info(&client->dev,
"%s: MFC Charger Driver Loaded\n", __func__);
device_init_wakeup(charger->dev, 1);
return 0;
err_irq_wpc_det:
err_pdata_free:
power_supply_unregister(charger->psy_chg);
err_supply_unreg:
mutex_destroy(&charger->io_lock);
err_i2cfunc_not_support:
kfree(charger);
err_wpc_nomem:
err_parse_dt:
devm_kfree(&client->dev, pdata);
return ret;
}
static int mfc_charger_remove(struct i2c_client *client)
{
return 0;
}
#if defined(CONFIG_PM)
static int mfc_charger_suspend(struct device *dev)
{
struct mfc_charger_data *charger = dev_get_drvdata(dev);
if (device_may_wakeup(charger->dev)){
enable_irq_wake(charger->pdata->irq_wpc_int);
enable_irq_wake(charger->pdata->irq_wpc_det);
}
disable_irq(charger->pdata->irq_wpc_int);
disable_irq(charger->pdata->irq_wpc_det);
return 0;
}
static int mfc_charger_resume(struct device *dev)
{
struct mfc_charger_data *charger = dev_get_drvdata(dev);
pr_info("%s \n", __func__);
if (device_may_wakeup(charger->dev)) {
disable_irq_wake(charger->pdata->irq_wpc_int);
disable_irq_wake(charger->pdata->irq_wpc_det);
}
enable_irq(charger->pdata->irq_wpc_int);
enable_irq(charger->pdata->irq_wpc_det);
return 0;
}
#else
#define mfc_charger_suspend NULL
#define mfc_charger_resume NULL
#endif
static void mfc_charger_shutdown(struct i2c_client *client)
{
struct mfc_charger_data *charger = i2c_get_clientdata(client);
pr_info("%s \n", __func__);
if(charger->pdata->is_charging)
mfc_set_vrect_adjust(charger, MFC_HEADROOM_1);
}
static const struct i2c_device_id mfc_charger_id_table[] = {
{ "mfc-charger", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, mfc_id_table);
#ifdef CONFIG_OF
static struct of_device_id mfc_charger_match_table[] = {
{ .compatible = "idt,mfc-charger",},
{},
};
#else
#define mfc_charger_match_table NULL
#endif
const struct dev_pm_ops mfc_pm = {
.suspend = mfc_charger_suspend,
.resume = mfc_charger_resume,
};
static struct i2c_driver mfc_charger_driver = {
.driver = {
.name = "mfc-charger",
.owner = THIS_MODULE,
#if defined(CONFIG_PM)
.pm = &mfc_pm,
#endif /* CONFIG_PM */
.of_match_table = mfc_charger_match_table,
},
.shutdown = mfc_charger_shutdown,
.probe = mfc_charger_probe,
.remove = mfc_charger_remove,
.id_table = mfc_charger_id_table,
};
static int __init mfc_charger_init(void)
{
pr_info("%s \n",__func__);
return i2c_add_driver(&mfc_charger_driver);
}
static void __exit mfc_charger_exit(void)
{
pr_info("%s \n",__func__);
i2c_del_driver(&mfc_charger_driver);
}
module_init(mfc_charger_init);
module_exit(mfc_charger_exit);
MODULE_DESCRIPTION("Samsung MFC Charger Driver");
MODULE_AUTHOR("Samsung Electronics");
MODULE_LICENSE("GPL");