2478 lines
74 KiB
C
2478 lines
74 KiB
C
/*
|
|
* p9220_charger.c
|
|
* Samsung p9220 Charger Driver
|
|
*
|
|
* Copyright (C) 2015 Samsung Electronics
|
|
* Yeongmi Ha <yeongmi86.ha@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/p9220_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
|
|
|
|
#define P9220S_FW_SDCARD_BIN_PATH "sdcard/p9220_otp.bin"
|
|
#define P9220S_OTP_FW_HEX_PATH "idt/p9220_otp.bin"
|
|
#define P9220S_SRAM_FW_HEX_PATH "idt/p9220_sram.bin"
|
|
|
|
extern bool sleep_mode;
|
|
|
|
static enum power_supply_property sec_charger_props[] = {
|
|
/*
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_MANUFACTURER,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
|
|
POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL,
|
|
POWER_SUPPLY_PROP_CHARGE_POWERED_OTG_CONTROL,
|
|
POWER_SUPPLY_PROP_ENERGY_NOW,
|
|
POWER_SUPPLY_PROP_ENERGY_AVG,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION,
|
|
POWER_SUPPLY_PROP_SCOPE,
|
|
*/
|
|
};
|
|
|
|
int p9220_otp_update = 0;
|
|
u8 adc_cal = 0;
|
|
|
|
extern unsigned int lpcharge;
|
|
int p9220_get_firmware_version(struct p9220_charger_data *charger, int firm_mode);
|
|
static irqreturn_t p9220_wpc_det_irq_thread(int irq, void *irq_data);
|
|
static irqreturn_t p9220_wpc_irq_thread(int irq, void *irq_data);
|
|
|
|
static int p9220_reg_read(struct i2c_client *client, u16 reg, u8 *val)
|
|
{
|
|
struct p9220_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)
|
|
{
|
|
dev_err(&client->dev, "%s: i2c read error, reg: 0x%x, ret: %d\n",
|
|
__func__, reg, ret);
|
|
return -1;
|
|
}
|
|
*val = rbuf[0];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int p9220_reg_multi_read(struct i2c_client *client, u16 reg, u8 *val, int size)
|
|
{
|
|
struct p9220_charger_data *charger = i2c_get_clientdata(client);
|
|
int ret;
|
|
struct i2c_msg msg[2];
|
|
u8 wbuf[2];
|
|
|
|
// pr_debug("%s: reg = 0x%x, size = 0x%x\n", __func__, reg, size);
|
|
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 p9220_reg_write(struct i2c_client *client, u16 reg, u8 val)
|
|
{
|
|
struct p9220_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) {
|
|
dev_err(&client->dev, "%s: i2c write error, reg: 0x%x, ret: %d\n",
|
|
__func__, reg, ret);
|
|
return ret < 0 ? ret : -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int p9220_reg_update(struct i2c_client *client, u16 reg, u8 val, u8 mask)
|
|
{
|
|
struct p9220_charger_data *charger = i2c_get_clientdata(client);
|
|
unsigned char data[3] = { reg >> 8, reg & 0xff, val };
|
|
u8 data2;
|
|
int ret;
|
|
|
|
ret = p9220_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) {
|
|
dev_err(&client->dev, "%s: i2c write error, reg: 0x%x, ret: %d\n",
|
|
__func__, reg, ret);
|
|
return ret < 0 ? ret : -EIO;
|
|
}
|
|
}
|
|
p9220_reg_read(client, reg, &data2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int p9220_reg_multi_write(struct i2c_client *client, u16 reg, const u8 * val, int size)
|
|
{
|
|
struct p9220_charger_data *charger = i2c_get_clientdata(client);
|
|
int ret;
|
|
const int sendsz = 16;
|
|
unsigned char data[sendsz+2];
|
|
int cnt = 0;
|
|
|
|
dev_err(&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);
|
|
mutex_lock(&charger->io_lock);
|
|
ret = i2c_master_send(client, data, sendsz+2);
|
|
mutex_unlock(&charger->io_lock);
|
|
if (ret < sendsz+2) {
|
|
dev_err(&client->dev, "%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 p9220_clear_sram(struct i2c_client *client, u16 reg, const u8 * val, int size)
|
|
{
|
|
int ret;
|
|
const int sendsz = 64;
|
|
unsigned char data[sendsz+2];
|
|
int cnt = 0;
|
|
|
|
while(size > sendsz) {
|
|
data[0] = (reg+cnt) >>8;
|
|
data[1] = (reg+cnt) & 0xff;
|
|
memcpy(data+2, val, 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) {
|
|
dev_err(&client->dev, "%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, 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) {
|
|
dev_err(&client->dev, "%s: i2c write error, reg: 0x%x\n",
|
|
__func__, reg);
|
|
return ret < 0 ? ret : -EIO;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int p9220_get_adc(struct p9220_charger_data *charger, int adc_type)
|
|
{
|
|
int ret = 0;
|
|
u8 data[2] = {0,};
|
|
|
|
switch (adc_type) {
|
|
case P9220_ADC_VOUT:
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_VOUT_L_REG, &data[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_VOUT_H_REG, &data[1]);
|
|
if(ret >= 0 ) {
|
|
data[1] &= 0x0f;
|
|
ret = (data[0] | (data[1] << 8))*12600/4095;
|
|
} else
|
|
ret = 0;
|
|
break;
|
|
case P9220_ADC_VRECT:
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_VRECT_L_REG, &data[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_VRECT_H_REG, &data[1]);
|
|
if(ret >= 0 ) {
|
|
data[1] &= 0x0f;
|
|
ret = (data[0] | (data[1] << 8))*21000/4095;
|
|
} else
|
|
ret = 0;
|
|
break;
|
|
case P9220_ADC_TX_ISENSE:
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_TX_ISENSE_L_REG, &data[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_TX_ISENSE_H_REG, &data[1]);
|
|
if(ret >= 0 ) {
|
|
data[1] &= 0x0f;
|
|
ret = (data[0] | (data[1] << 8)); // need to check
|
|
} else
|
|
ret = 0;
|
|
break;
|
|
case P9220_ADC_RX_IOUT:
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_RX_IOUT_L_REG, &data[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_RX_IOUT_H_REG, &data[1]);
|
|
if(ret >= 0 ) {
|
|
data[1] &= 0x0f;
|
|
ret = (data[0] | (data[1] << 8)); // need to check
|
|
} else
|
|
ret = 0;
|
|
break;
|
|
case P9220_ADC_DIE_TEMP:
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_DIE_TEMP_L_REG, &data[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_DIE_TEMP_H_REG, &data[1]);
|
|
if(ret >= 0 ) {
|
|
data[1] &= 0x0f;
|
|
ret = (data[0] | (data[1] << 8)); // need to check
|
|
} else
|
|
ret = 0;
|
|
break;
|
|
|
|
case P9220_ADC_ALLIGN_X:
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_ALLIGN_X_REG, &data[0]);
|
|
if(ret >= 0 ) {
|
|
ret = data[0]; // need to check
|
|
} else
|
|
ret = 0;
|
|
break;
|
|
|
|
case P9220_ADC_ALLIGN_Y:
|
|
ret = p9220_reg_read(charger->client, P9220_ADC_ALLIGN_Y_REG, &data[0]);
|
|
if(ret >= 0 ) {
|
|
ret = data[0]; // need to check
|
|
} else
|
|
ret = 0;
|
|
break;
|
|
case P9220_ADC_OP_FRQ:
|
|
ret = p9220_reg_read(charger->client, P9220_OP_FREQ_L_REG, &data[0]);
|
|
if(ret < 0 ) {
|
|
ret = 0;
|
|
return ret;
|
|
}
|
|
ret = p9220_reg_read(charger->client, P9220_OP_FREQ_H_REG, &data[1]);
|
|
if(ret >= 0 )
|
|
ret = FREQ_OFFSET / (data[0] | (data[1] << 8));
|
|
else
|
|
ret = -1;
|
|
break;
|
|
case P9220_ADC_TX_PING:
|
|
ret = p9220_reg_read(charger->client, P9220_TX_PING_FREQ_REG, &data[0]);
|
|
if(ret >= 0 )
|
|
ret = data[0];
|
|
else
|
|
ret = -1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void p9220_set_vout(struct p9220_charger_data *charger, int vout)
|
|
{
|
|
union power_supply_propval value;
|
|
switch (vout) {
|
|
case P9220_VOUT_5V:
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG, P9220_VOUT_5V_VAL);
|
|
msleep(100);
|
|
pr_info("%s vout read = %d mV \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
break;
|
|
case P9220_VOUT_6V:
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG, P9220_VOUT_6V_VAL);
|
|
msleep(100);
|
|
pr_info("%s vout read = %d mV \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
break;
|
|
case P9220_VOUT_9V:
|
|
psy_do_property(charger->pdata->wired_charger_name, get, POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, value);
|
|
if (value.intval) {
|
|
pr_info("%s CHGIN_OTG now ON, do not set VOUT 9V \n", __func__);
|
|
break;
|
|
}
|
|
/* We set VOUT to 10V actually for HERO for RE/CE standard authentication */
|
|
if (charger->pdata->hv_vout_wa)
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG, charger->pdata->hv_vout_wa);
|
|
else
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG, P9220_VOUT_9V_VAL);
|
|
msleep(100);
|
|
pr_info("%s vout read = %d mV \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
break;
|
|
case P9220_VOUT_CC_CV:
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG,
|
|
(charger->pdata->wpc_cc_cv_vout - 3500) / 100);
|
|
msleep(100);
|
|
pr_info("%s vout read = %d mV \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
break;
|
|
case P9220_VOUT_CV_CALL:
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG,
|
|
(charger->pdata->wpc_cv_call_vout - 3500) / 100);
|
|
msleep(100);
|
|
pr_info("%s vout read = %d mV \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
break;
|
|
case P9220_VOUT_CC_CALL:
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG,
|
|
(charger->pdata->wpc_cc_call_vout - 3500) / 100);
|
|
msleep(100);
|
|
pr_info("%s vout read = %d mV \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
break;
|
|
case P9220_VOUT_9V_STEP:
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG, P9220_VOUT_6V_VAL);
|
|
msleep(1000);
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG, P9220_VOUT_7V_VAL);
|
|
msleep(1000);
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG, P9220_VOUT_8V_VAL);
|
|
msleep(1000);
|
|
p9220_reg_write(charger->client, P9220_VOUT_SET_REG, P9220_VOUT_9V_VAL);
|
|
msleep(1000);
|
|
pr_info("%s vout read = %d mV \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
charger->pdata->vout_status = vout;
|
|
}
|
|
|
|
int p9220_get_vout(struct p9220_charger_data *charger)
|
|
{
|
|
u8 data;
|
|
int ret;
|
|
ret = p9220_reg_read(charger->client, P9220_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 p9220_fod_set(struct p9220_charger_data *charger)
|
|
{
|
|
int i = 0;
|
|
|
|
pr_info("%s \n", __func__);
|
|
if(charger->pdata->fod_data_check) {
|
|
for(i=0; i< P9220_NUM_FOD_REG; i++)
|
|
p9220_reg_write(charger->client, P9220_WPC_FOD_0A_REG+i, charger->pdata->fod_data[i]);
|
|
}
|
|
}
|
|
|
|
void p9220_fod_set_cv(struct p9220_charger_data *charger)
|
|
{
|
|
int i = 0;
|
|
|
|
pr_info("%s \n", __func__);
|
|
if(charger->pdata->fod_data_check) {
|
|
for(i=0; i< P9220_NUM_FOD_REG; i++)
|
|
p9220_reg_write(charger->client, P9220_WPC_FOD_0A_REG+i, charger->pdata->fod_data_cv[i]);
|
|
}
|
|
}
|
|
|
|
void p9220_fod_set_cs100(struct p9220_charger_data *charger)
|
|
{
|
|
int i = 0;
|
|
|
|
pr_info("%s \n", __func__);
|
|
if(charger->pdata->fod_data_check) {
|
|
for(i=1; i< P9220_NUM_FOD_REG; i+=2)
|
|
p9220_reg_write(charger->client, P9220_WPC_FOD_0A_REG+i, 0x7f);
|
|
}
|
|
}
|
|
|
|
void p9220_set_cmd_reg(struct p9220_charger_data *charger, u8 val, u8 mask)
|
|
{
|
|
u8 temp = 0;
|
|
int ret = 0, i = 0;
|
|
|
|
do {
|
|
pr_info("%s \n", __func__);
|
|
ret = p9220_reg_update(charger->client, P9220_COMMAND_REG, val, mask); // command
|
|
if(ret >= 0) {
|
|
msleep(250);
|
|
ret = p9220_reg_read(charger->client, P9220_COMMAND_REG, &temp); // check out set bit exists
|
|
if(ret < 0 || i > 3 )
|
|
break;
|
|
}
|
|
i++;
|
|
}while(temp != 0);
|
|
}
|
|
|
|
void p9220_send_eop(struct p9220_charger_data *charger, int health_mode)
|
|
{
|
|
int i = 0;
|
|
int ret = 0;
|
|
|
|
switch(health_mode) {
|
|
case POWER_SUPPLY_HEALTH_OVERHEAT:
|
|
case POWER_SUPPLY_HEALTH_OVERHEATLIMIT:
|
|
case POWER_SUPPLY_HEALTH_COLD:
|
|
if(charger->pdata->cable_type == SEC_WIRELESS_PAD_PMA) {
|
|
pr_info("%s pma mode \n", __func__);
|
|
for(i = 0; i < CMD_CNT; i++) {
|
|
ret = p9220_reg_write(charger->client, P9220_END_POWER_TRANSFER_REG, P9220_EPT_END_OF_CHG);
|
|
if(ret >= 0) {
|
|
p9220_set_cmd_reg(charger, P9220_CMD_SEND_EOP_MASK, P9220_CMD_SEND_EOP_MASK);
|
|
msleep(250);
|
|
} else
|
|
break;
|
|
}
|
|
} else {
|
|
pr_info("%s wpc mode \n", __func__);
|
|
for(i = 0; i < CMD_CNT; i++) {
|
|
ret = p9220_reg_write(charger->client, P9220_END_POWER_TRANSFER_REG, P9220_EPT_OVER_TEMP);
|
|
if(ret >= 0) {
|
|
p9220_set_cmd_reg(charger, P9220_CMD_SEND_EOP_MASK, P9220_CMD_SEND_EOP_MASK);
|
|
msleep(250);
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_HEALTH_UNDERVOLTAGE:
|
|
#if 0
|
|
pr_info("%s ept-reconfigure \n", __func__);
|
|
ret = p9220_reg_write(charger->client, P9220_END_POWER_TRANSFER_REG, P9220_EPT_RECONFIG);
|
|
if(ret >= 0) {
|
|
p9220_set_cmd_reg(charger, P9220_CMD_SEND_EOP_MASK, P9220_CMD_SEND_EOP_MASK);
|
|
msleep(250);
|
|
}
|
|
#endif
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int p9220_send_cs100(struct p9220_charger_data *charger)
|
|
{
|
|
int i = 0;
|
|
int ret = 0;
|
|
|
|
for(i = 0; i < CMD_CNT; i++) {
|
|
ret = p9220_reg_write(charger->client, P9220_CHG_STATUS_REG, 100);
|
|
if(ret >= 0) {
|
|
p9220_set_cmd_reg(charger, P9220_CMD_SEND_CHG_STS_MASK, P9220_CMD_SEND_CHG_STS_MASK);
|
|
msleep(250);
|
|
ret = 1;
|
|
} else {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void p9220_send_packet(struct p9220_charger_data *charger, u8 header, u8 rx_data_com, u8 *data_val, int data_size)
|
|
{
|
|
int ret;
|
|
int i;
|
|
ret = p9220_reg_write(charger->client, P9220_PACKET_HEADER, header);
|
|
ret = p9220_reg_write(charger->client, P9220_RX_DATA_COMMAND, rx_data_com);
|
|
|
|
for(i = 0; i< data_size; i++) {
|
|
ret = p9220_reg_write(charger->client, P9220_RX_DATA_VALUE0 + i, data_val[i]);
|
|
}
|
|
p9220_set_cmd_reg(charger, P9220_CMD_SEND_RX_DATA_MASK, P9220_CMD_SEND_RX_DATA_MASK);
|
|
}
|
|
|
|
void p9220_send_command(struct p9220_charger_data *charger, int cmd_mode)
|
|
{
|
|
u8 data_val[4];
|
|
|
|
switch (cmd_mode) {
|
|
case P9220_AFC_CONF_5V:
|
|
pr_info("%s set 5V \n", __func__);
|
|
|
|
data_val[0] = 0x05;
|
|
p9220_send_packet(charger, P9220_HEADER_AFC_CONF, P9220_RX_DATA_COM_AFC_SET, data_val, 1);
|
|
msleep(120);
|
|
|
|
p9220_set_vout(charger, P9220_VOUT_5V);
|
|
pr_info("%s vout read = %d \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
p9220_reg_read(charger->client, P9220_RX_DATA_COMMAND, &data_val[0]);
|
|
p9220_reg_read(charger->client, P9220_RX_DATA_VALUE0, &data_val[0]);
|
|
p9220_reg_read(charger->client, P9220_COMMAND_REG, &data_val[0]);
|
|
break;
|
|
case P9220_AFC_CONF_9V:
|
|
pr_info("%s set 9V \n", __func__);
|
|
|
|
data_val[0] = 0x2c;
|
|
p9220_send_packet(charger, P9220_HEADER_AFC_CONF, P9220_RX_DATA_COM_AFC_SET, data_val, 1);
|
|
msleep(120);
|
|
|
|
/* Enable Clamp1, Clamp2 for WPC 9W */
|
|
p9220_reg_update(charger->client, P9220_MOD_DEPTH_REG, 0x30, 0x30);
|
|
p9220_set_vout(charger, P9220_VOUT_9V);
|
|
pr_info("%s vout read = %d \n", __func__, p9220_get_adc(charger, P9220_ADC_VOUT));
|
|
|
|
p9220_reg_read(charger->client, P9220_RX_DATA_COMMAND, &data_val[0]);
|
|
p9220_reg_read(charger->client, P9220_RX_DATA_VALUE0, &data_val[0]);
|
|
p9220_reg_read(charger->client, P9220_COMMAND_REG, &data_val[0]);
|
|
break;
|
|
case P9220_LED_CONTROL_ON:
|
|
pr_info("%s led on \n", __func__);
|
|
data_val[0] = 0x0;
|
|
p9220_send_packet(charger, P9220_HEADER_AFC_CONF, P9220_RX_DATA_COM_LED_CTRL, data_val, 1);
|
|
break;
|
|
case P9220_LED_CONTROL_OFF:
|
|
pr_info("%s led off \n", __func__);
|
|
data_val[0] = 0xff;
|
|
p9220_send_packet(charger, P9220_HEADER_AFC_CONF, P9220_RX_DATA_COM_LED_CTRL, data_val, 1);
|
|
break;
|
|
case P9220_FAN_CONTROL_ON:
|
|
pr_info("%s fan on \n", __func__);
|
|
data_val[0] = 0x0;
|
|
p9220_send_packet(charger, P9220_HEADER_AFC_CONF, P9220_RX_DATA_COM_FAN_CTRL, data_val, 1);
|
|
break;
|
|
case P9220_FAN_CONTROL_OFF:
|
|
pr_info("%s fan off \n", __func__);
|
|
data_val[0] = 0xff;
|
|
p9220_send_packet(charger, P9220_HEADER_AFC_CONF, P9220_RX_DATA_COM_FAN_CTRL, data_val, 1);
|
|
break;
|
|
case P9220_REQUEST_AFC_TX:
|
|
pr_info("%s request afc tx \n", __func__);
|
|
data_val[0] = 0x0;
|
|
p9220_send_packet(charger, P9220_HEADER_AFC_CONF, P9220_RX_DATA_COM_REQ_AFC, data_val, 1);
|
|
break;
|
|
case P9220_REQUEST_TX_ID:
|
|
pr_info("%s request TX ID \n", __func__);
|
|
data_val[0] = 0x0;
|
|
p9220_send_packet(charger, P9220_HEADER_AFC_CONF, P9220_RX_DATA_COM_TX_ID, data_val, 1);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void p9220_led_control(struct p9220_charger_data *charger, bool on)
|
|
{
|
|
int i = 0;
|
|
|
|
if(on) {
|
|
for(i=0; i< CMD_CNT; i++)
|
|
p9220_send_command(charger, P9220_LED_CONTROL_ON);
|
|
} else {
|
|
for(i=0; i< CMD_CNT; i++)
|
|
p9220_send_command(charger, P9220_LED_CONTROL_OFF);
|
|
}
|
|
}
|
|
|
|
void p9220_fan_control(struct p9220_charger_data *charger, bool on)
|
|
{
|
|
int i = 0;
|
|
|
|
if(on) {
|
|
for(i=0; i< CMD_CNT -1; i++)
|
|
p9220_send_command(charger, P9220_FAN_CONTROL_ON);
|
|
} else {
|
|
for(i=0; i< CMD_CNT -1; i++)
|
|
p9220_send_command(charger, P9220_FAN_CONTROL_OFF);
|
|
}
|
|
}
|
|
|
|
void p9220_set_vrect_adjust(struct p9220_charger_data *charger, int set)
|
|
{
|
|
int i = 0;
|
|
|
|
switch (set) {
|
|
case P9220_HEADROOM_0:
|
|
for(i=0; i<6; i++) {
|
|
p9220_reg_write(charger->client, P9220_VRECT_SET_REG, 0x0);
|
|
msleep(50);
|
|
}
|
|
break;
|
|
case P9220_HEADROOM_1:
|
|
for(i=0; i<6; i++) {
|
|
p9220_reg_write(charger->client, P9220_VRECT_SET_REG, 0x36);
|
|
msleep(50);
|
|
}
|
|
break;
|
|
case P9220_HEADROOM_2:
|
|
for(i=0; i<6; i++) {
|
|
p9220_reg_write(charger->client, P9220_VRECT_SET_REG, 0x61);
|
|
msleep(50);
|
|
}
|
|
break;
|
|
case P9220_HEADROOM_3:
|
|
for(i=0; i<6; i++) {
|
|
p9220_reg_write(charger->client, P9220_VRECT_SET_REG, 0x7f);
|
|
msleep(50);
|
|
}
|
|
break;
|
|
case P9220_HEADROOM_4:
|
|
for(i=0; i<6; i++) {
|
|
p9220_reg_write(charger->client, P9220_VRECT_SET_REG, 0x06);
|
|
msleep(50);
|
|
}
|
|
break;
|
|
case P9220_HEADROOM_5:
|
|
for(i=0; i<6; i++) {
|
|
p9220_reg_write(charger->client, P9220_VRECT_SET_REG, 0x10);
|
|
msleep(50);
|
|
}
|
|
break;
|
|
default:
|
|
pr_info("%s no headroom mode\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void p9220_mis_align(struct p9220_charger_data *charger)
|
|
{
|
|
pr_info("%s: Reset M0\n",__func__);
|
|
if (charger->pdata->cable_type == P9220_PAD_MODE_WPC_AFC ||
|
|
charger->pdata->cable_type == P9220_PAD_MODE_PMA)
|
|
p9220_reg_write(charger->client, 0x3040, 0x80); /*restart M0 */
|
|
}
|
|
|
|
int p9220_get_firmware_version(struct p9220_charger_data *charger, int firm_mode)
|
|
{
|
|
int version = -1;
|
|
int ret;
|
|
u8 otp_fw_major[2] = {0,};
|
|
u8 otp_fw_minor[2] = {0,};
|
|
u8 tx_fw_major[2] = {0,};
|
|
u8 tx_fw_minor[2] = {0,};
|
|
|
|
switch (firm_mode) {
|
|
case P9220_RX_FIRMWARE:
|
|
ret = p9220_reg_read(charger->client, P9220_OTP_FW_MAJOR_REV_L_REG, &otp_fw_major[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_OTP_FW_MAJOR_REV_H_REG, &otp_fw_major[1]);
|
|
if (ret >= 0) {
|
|
version = otp_fw_major[0] | (otp_fw_major[1] << 8);
|
|
}
|
|
pr_info("%s rx major firmware version 0x%x \n", __func__, version);
|
|
|
|
ret = p9220_reg_read(charger->client, P9220_OTP_FW_MINOR_REV_L_REG, &otp_fw_minor[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_OTP_FW_MINOR_REV_H_REG, &otp_fw_minor[1]);
|
|
if (ret >= 0) {
|
|
version = otp_fw_minor[0] | (otp_fw_minor[1] << 8);
|
|
}
|
|
pr_info("%s rx minor firmware version 0x%x \n", __func__, version);
|
|
break;
|
|
case P9220_TX_FIRMWARE:
|
|
ret = p9220_reg_read(charger->client, P9220_SRAM_FW_MAJOR_REV_L_REG, &tx_fw_major[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_SRAM_FW_MAJOR_REV_H_REG, &tx_fw_major[1]);
|
|
if (ret >= 0) {
|
|
version = tx_fw_major[0] | (tx_fw_major[1] << 8);
|
|
}
|
|
pr_info("%s tx major firmware version 0x%x \n", __func__, version);
|
|
|
|
ret = p9220_reg_read(charger->client, P9220_SRAM_FW_MINOR_REV_L_REG, &tx_fw_minor[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_SRAM_FW_MINOR_REV_H_REG, &tx_fw_minor[1]);
|
|
if (ret >= 0) {
|
|
version = tx_fw_minor[0] | (tx_fw_minor[1] << 8);
|
|
}
|
|
pr_info("%s tx minor firmware version 0x%x \n", __func__, version);
|
|
break;
|
|
default:
|
|
pr_err("%s Wrong firmware mode \n", __func__);
|
|
version = -1;
|
|
break;
|
|
}
|
|
|
|
return version;
|
|
}
|
|
|
|
int p9220_get_ic_grade(struct p9220_charger_data *charger, int read_mode)
|
|
{
|
|
u8 grade;
|
|
int ret;
|
|
|
|
switch (read_mode) {
|
|
case P9220_IC_GRADE:
|
|
ret = p9220_reg_read(charger->client, P9220_CHIP_REVISION_REG, &grade);
|
|
|
|
if(ret >= 0) {
|
|
grade &= P9220_CHIP_GRADE_MASK;
|
|
pr_info("%s ic grade = %d \n", __func__, grade);
|
|
ret = grade;
|
|
}
|
|
else
|
|
ret = -1;
|
|
break;
|
|
case P9220_IC_VERSION:
|
|
ret = p9220_reg_read(charger->client, P9220_CHIP_REVISION_REG, &grade);
|
|
|
|
if(ret >= 0) {
|
|
grade &= P9220_CHIP_REVISION_MASK;
|
|
pr_info("%s ic version = %d \n", __func__, grade);
|
|
ret = grade;
|
|
}
|
|
else
|
|
ret = -1;
|
|
break;
|
|
case P9220_IC_VENDOR:
|
|
ret = -1;
|
|
break;
|
|
default :
|
|
ret = -1;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void p9220_wireless_chg_init(struct p9220_charger_data *charger)
|
|
{
|
|
pr_info("%s \n", __func__);
|
|
|
|
p9220_set_vout(charger, P9220_VOUT_5V);
|
|
}
|
|
|
|
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 LoadOTPLoaderInRAM(struct p9220_charger_data *charger, u16 addr)
|
|
{
|
|
int i, size;
|
|
u8 data[1024];
|
|
if (p9220_reg_multi_write(charger->client, addr, OTPBootloader, sizeof(OTPBootloader)) < 0) {
|
|
pr_err("%s,fail", __func__);
|
|
}
|
|
size = sizeof(OTPBootloader);
|
|
i = 0;
|
|
while(size > 0) {
|
|
if (p9220_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(OTPBootloader);
|
|
if (datacmp(data, OTPBootloader, size)) {
|
|
pr_err("%s, data is not matched\n", __func__);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int p9220_firmware_verify(struct p9220_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 (p9220_reg_write(charger->client, 0x3C00, 0x80) < 0) {
|
|
pr_err("%s: reset FDEM error\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3000, 0x5a) < 0) {
|
|
pr_err("%s: key error\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3040, 0x11) < 0) {
|
|
pr_err("%s: halt M0, OTP_I2C_EN set error\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3C04, 0x04) < 0) {
|
|
pr_err("%s: OTP_VRR 2.98V error\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x5C00, 0x11) < 0) {
|
|
pr_err("%s: OTP_CTRL VPP_EN set error\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
dev_err(&charger->client->dev, "%s, request_firmware\n", __func__);
|
|
ret = request_firmware(&charger->firm_data_bin, P9220S_OTP_FW_HEX_PATH,
|
|
&charger->client->dev);
|
|
if ( ret < 0) {
|
|
dev_err(&charger->client->dev, "%s: failed to request firmware %s (%d) \n",
|
|
__func__, P9220S_OTP_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 (p9220_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.\n", __func__);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
release_firmware(charger->firm_data_bin);
|
|
|
|
wake_unlock(&charger->wpc_update_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int p9220_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) {
|
|
dev_err(&client->dev, "%s: i2c write error, reg: 0x%x\n", __func__, reg);
|
|
return ret < 0 ? ret : -EIO;
|
|
}
|
|
if (p9220_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) {
|
|
dev_err(&client->dev, "%s: i2c write error, reg: 0x%x\n", __func__, reg);
|
|
return ret < 0 ? ret : -EIO;
|
|
}
|
|
if (p9220_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 p9220_TxFW_SRAM(struct p9220_charger_data *charger, const u8 * srcData, int fw_size)
|
|
{
|
|
const u8 clear_data[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
u16 addr;
|
|
u8 temp;
|
|
int ret;
|
|
|
|
pr_info("%s TX FW update Start \n",__func__);
|
|
|
|
if (p9220_reg_write(charger->client, 0x3000, 0x5a) < 0) {
|
|
pr_err("%s: write key error\n", __func__);
|
|
return false;
|
|
}
|
|
if (p9220_reg_write(charger->client, 0x3040, 0x10) < 0) {
|
|
pr_err("%s: halt M0 error\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
/* clear 0x0000 to 0x1FFF */
|
|
if (p9220_clear_sram(charger->client, 0x0000, clear_data, 0x2000) < 0) {
|
|
pr_err("%s: clear memory error\n", __func__);
|
|
return false;
|
|
}
|
|
addr = 0x0800;
|
|
if (p9220_reg_multi_write_verify(charger->client, addr, srcData, fw_size) < 0) {
|
|
pr_err("%s: write fail", __func__);
|
|
return false;
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3000, 0x5a) < 0) {
|
|
pr_err("%s: write key error..\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3048, 0xD0) < 0) {
|
|
pr_err("%s: remapping error..\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
p9220_reg_write(charger->client, 0x3040, 0x80);
|
|
|
|
pr_err("TX firmware update finished.\n");
|
|
ret = p9220_reg_read(charger->client, P9220_SYS_OP_MODE_REG, &temp);
|
|
pr_info("%s: SYS_OP_MODE 0x%x, ret: %d\n", __func__, temp, ret);
|
|
return true;
|
|
}
|
|
|
|
static int PgmOTPwRAM(struct p9220_charger_data *charger, unsigned short OtpAddr,
|
|
const u8 * srcData, int srcOffs, int size)
|
|
{
|
|
int i, j, cnt;
|
|
int block_len = 0;
|
|
u8 fw_major[2] = {0,};
|
|
u8 fw_minor[2] = {0,};
|
|
|
|
p9220_reg_read(charger->client, P9220_OTP_FW_MAJOR_REV_L_REG, &fw_major[0]);
|
|
p9220_reg_read(charger->client, P9220_OTP_FW_MAJOR_REV_H_REG, &fw_major[1]);
|
|
p9220_reg_read(charger->client, P9220_OTP_FW_MINOR_REV_L_REG, &fw_minor[0]);
|
|
p9220_reg_read(charger->client, P9220_OTP_FW_MINOR_REV_H_REG, &fw_minor[1]);
|
|
|
|
if (p9220_reg_write(charger->client, 0x3000, 0x5a) < 0) {
|
|
pr_err("%s: write key error\n", __func__);
|
|
return false; // write key
|
|
}
|
|
if (p9220_reg_write(charger->client, 0x3040, 0x10) < 0) {
|
|
pr_err("%s: halt M0 error\n", __func__);
|
|
return false; // halt M0
|
|
}
|
|
if (!LoadOTPLoaderInRAM(charger, 0x1c00)){
|
|
pr_err("%s: LoadOTPLoaderInRAM error\n", __func__);
|
|
return false; // make sure load address and 1KB size are OK
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3048, 0x80) < 0) {
|
|
pr_err("%s: map RAM to OTP error\n", __func__);
|
|
return false; // map RAM to OTP
|
|
}
|
|
p9220_reg_write(charger->client, 0x3040, 0x80);
|
|
msleep(100);
|
|
|
|
for (i = 0; i < size; i += 128) // program pages of 128 bytes
|
|
{
|
|
u8 sBuf[136] = {0,};
|
|
u16 StartAddr = (u16)i;
|
|
u16 CheckSum = StartAddr;
|
|
u16 CodeLength = 128;
|
|
|
|
block_len = (i + 128) > size ? size - i : 128;
|
|
if (block_len == 128) {
|
|
memcpy(sBuf + 8, srcData + i + srcOffs, 128);
|
|
} else {
|
|
memset(sBuf, 0, 136);
|
|
memcpy(sBuf + 8, srcData + i + srcOffs, block_len);
|
|
}
|
|
|
|
for (j = 127; j >= 0; j--)
|
|
{
|
|
if (sBuf[j + 8] != 0)
|
|
break;
|
|
else
|
|
CodeLength--;
|
|
}
|
|
if (CodeLength == 0)
|
|
continue; // skip programming if nothing to program
|
|
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);
|
|
|
|
if (p9220_reg_multi_write_verify(charger->client, 0x400, sBuf, CodeLength + 8) < 0)
|
|
{
|
|
pr_err("ERROR: on writing to OTP buffer");
|
|
return false;
|
|
}
|
|
sBuf[0] = 1;
|
|
if (p9220_reg_write(charger->client, 0x400, sBuf[0]) < 0)
|
|
{
|
|
pr_err("ERROR: on OTP buffer validation");
|
|
return false;
|
|
}
|
|
|
|
cnt = 0;
|
|
do
|
|
{
|
|
msleep(20);
|
|
if (p9220_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);
|
|
|
|
if (sBuf[0] != 2) // not OK
|
|
{
|
|
pr_err("ERROR: buffer write to OTP returned status %d ",sBuf[0]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3000, 0x5a) < 0) {
|
|
pr_err("%s: write key error..\n", __func__);
|
|
return false; // write key
|
|
}
|
|
if (p9220_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 p9220_runtime_sram_change(struct p9220_charger_data *charger)
|
|
{
|
|
int ret, i = 0;
|
|
u8 reg;
|
|
|
|
pr_info("%s \n", __func__);
|
|
|
|
do {
|
|
ret = p9220_reg_write(charger->client, 0x5834, adc_cal);
|
|
ret = p9220_reg_read(charger->client, 0x5834, ®);
|
|
pr_info("%s [%d] otp : 0x%x, sram : 0x%x \n", __func__, i, adc_cal, reg);
|
|
if(i > 10 || ret < 0)
|
|
return false;
|
|
msleep(10);
|
|
i++;
|
|
} while(reg != adc_cal);
|
|
|
|
return true;
|
|
}
|
|
|
|
int p9220_runtime_sram_preprocess(struct p9220_charger_data *charger)
|
|
{
|
|
u8 reg;
|
|
u8 pad_mode;
|
|
|
|
pr_info("%s \n", __func__);
|
|
|
|
if(gpio_get_value(charger->pdata->wpc_det)) {
|
|
pr_info("%s it is wireless lpm \n", __func__);
|
|
p9220_reg_read(charger->client, P9220_SYS_OP_MODE_REG, &pad_mode);
|
|
pr_info("%s pad_mode = %d \n", __func__, pad_mode);
|
|
|
|
if(pad_mode & P9220_SYS_MODE_PMA)
|
|
return true;
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3000, 0x5a) < 0) {
|
|
pr_err("%s: failed unlock register\n", __func__);
|
|
}
|
|
|
|
if (p9220_reg_write(charger->client, 0x3040, 0x11) < 0) {
|
|
pr_err("%s: failed stop process\n", __func__);
|
|
}
|
|
|
|
//write 1 at bit0 of 0xbfbe
|
|
if(p9220_reg_read(charger->client, 0xbfbe, ®) < 0)
|
|
adc_cal = 0;
|
|
else {
|
|
adc_cal = reg = reg | 0x01;
|
|
pr_info("%s 0xbfbe = 0x%x \n", __func__, reg);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int p9220_firmware_update(struct p9220_charger_data *charger, int cmd)
|
|
{
|
|
struct file *fp;
|
|
mm_segment_t old_fs;
|
|
long fsize, nread;
|
|
int ret = 0;
|
|
const u8 *fw_img;
|
|
|
|
pr_info("%s firmware update mode is = %d \n", __func__, cmd);
|
|
|
|
switch(cmd) {
|
|
case SEC_WIRELESS_RX_SDCARD_MODE:
|
|
charger->pdata->otp_firmware_result = P9220_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(P9220S_FW_SDCARD_BIN_PATH, O_RDONLY, S_IRUSR);
|
|
|
|
if (IS_ERR(fp)) {
|
|
pr_err("%s: failed to open %s\n", __func__, P9220S_FW_SDCARD_BIN_PATH);
|
|
ret = -ENOENT;
|
|
set_fs(old_fs);
|
|
return ret;
|
|
}
|
|
|
|
fsize = fp->f_path.dentry->d_inode->i_size;
|
|
pr_err("%s: start, file path %s, size %ld Bytes\n",
|
|
__func__, P9220S_FW_SDCARD_BIN_PATH, fsize);
|
|
|
|
fw_img = kmalloc(fsize, GFP_KERNEL);
|
|
|
|
if (fw_img == NULL) {
|
|
pr_err("%s, kmalloc failed\n", __func__);
|
|
ret = -EFAULT;
|
|
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);
|
|
ret = -EIO;
|
|
goto read_err;
|
|
}
|
|
|
|
filp_close(fp, current->files);
|
|
set_fs(old_fs);
|
|
p9220_otp_update = 1;
|
|
PgmOTPwRAM(charger, 0 ,fw_img, 0, fsize);
|
|
p9220_otp_update = 0;
|
|
|
|
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:
|
|
dev_err(&charger->client->dev, "%s, Do not write OTP firmware \n", __func__);
|
|
return 0;
|
|
#if 0
|
|
charger->pdata->otp_firmware_result = P9220_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, P9220S_OTP_FW_HEX_PATH,
|
|
&charger->client->dev);
|
|
if ( ret < 0) {
|
|
dev_err(&charger->client->dev, "%s: failed to request firmware %s (%d) \n", __func__, P9220S_OTP_FW_HEX_PATH, ret);
|
|
charger->pdata->otp_firmware_result = P9220_FW_RESULT_FAIL;
|
|
return -EINVAL;
|
|
}
|
|
wake_lock(&charger->wpc_update_lock);
|
|
pr_info("%s data size = %ld \n", __func__, charger->firm_data_bin->size);
|
|
p9220_otp_update = 1;
|
|
ret = PgmOTPwRAM(charger, 0 ,charger->firm_data_bin->data, 0, charger->firm_data_bin->size);
|
|
p9220_otp_update = 0;
|
|
release_firmware(charger->firm_data_bin);
|
|
|
|
charger->pdata->otp_firmware_ver = p9220_get_firmware_version(charger, P9220_RX_FIRMWARE);
|
|
charger->pdata->wc_ic_grade = p9220_get_ic_grade(charger, P9220_IC_GRADE);
|
|
charger->pdata->wc_ic_rev = p9220_get_ic_grade(charger, P9220_IC_VERSION);
|
|
|
|
if(ret)
|
|
charger->pdata->otp_firmware_result = P9220_FW_RESULT_PASS;
|
|
else
|
|
charger->pdata->otp_firmware_result = P9220_FW_RESULT_FAIL;
|
|
enable_irq(charger->pdata->irq_wpc_int);
|
|
enable_irq(charger->pdata->irq_wpc_det);
|
|
wake_unlock(&charger->wpc_update_lock);
|
|
#endif
|
|
break;
|
|
case SEC_WIRELESS_TX_ON_MODE:
|
|
charger->pdata->tx_firmware_result = P9220_FW_RESULT_DOWNLOADING;
|
|
msleep(200);
|
|
dev_err(&charger->client->dev, "%s, built in sram mode on \n", __func__);
|
|
ret = request_firmware(&charger->firm_data_bin, P9220S_SRAM_FW_HEX_PATH, &charger->client->dev);
|
|
|
|
if ( ret < 0) {
|
|
dev_err(&charger->client->dev, "%s: failed to request firmware %s (%d) \n", __func__,
|
|
P9220S_SRAM_FW_HEX_PATH, ret);
|
|
charger->pdata->tx_firmware_result = P9220_FW_RESULT_FAIL;
|
|
return -EINVAL;
|
|
}
|
|
|
|
wake_lock(&charger->wpc_update_lock);
|
|
pr_info("%s data size = %ld \n", __func__, charger->firm_data_bin->size);
|
|
ret = p9220_TxFW_SRAM(charger, charger->firm_data_bin->data, charger->firm_data_bin->size);
|
|
release_firmware(charger->firm_data_bin);
|
|
|
|
charger->pdata->tx_firmware_ver = p9220_get_firmware_version(charger, P9220_TX_FIRMWARE);
|
|
|
|
if(ret) {
|
|
charger->pdata->tx_firmware_result = P9220_FW_RESULT_PASS;
|
|
charger->pdata->tx_status = SEC_TX_STANDBY;
|
|
} else {
|
|
charger->pdata->tx_firmware_result = P9220_FW_RESULT_FAIL;
|
|
charger->pdata->tx_status = SEC_TX_ERROR;
|
|
}
|
|
|
|
charger->pdata->cable_type = P9220_PAD_MODE_TX;
|
|
wake_unlock(&charger->wpc_update_lock);
|
|
break;
|
|
case SEC_WIRELESS_TX_OFF_MODE:
|
|
charger->pdata->tx_firmware_result = P9220_FW_RESULT_DOWNLOADING;
|
|
charger->pdata->cable_type = P9220_PAD_MODE_NONE;
|
|
dev_err(&charger->client->dev, "%s, built in sram mode off \n", __func__);
|
|
charger->pdata->tx_status = SEC_TX_OFF;
|
|
break;
|
|
case SEC_WIRELESS_RX_INIT:
|
|
p9220_runtime_sram_preprocess(charger); /* get 0xBFBE value */
|
|
break;
|
|
default:
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
pr_info("%s --------------------------------------------------------------- \n", __func__);
|
|
|
|
return 0;
|
|
|
|
read_err:
|
|
kfree(fw_img);
|
|
malloc_error:
|
|
filp_close(fp, current->files);
|
|
set_fs(old_fs);
|
|
return ret;
|
|
}
|
|
|
|
static int p9220_chg_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct p9220_charger_data *charger =
|
|
container_of(psy, struct p9220_charger_data, psy_chg);
|
|
enum power_supply_ext_property ext_psp = psp;
|
|
|
|
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_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 = p9220_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:
|
|
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_GRADE) {
|
|
val->intval = p9220_get_ic_grade(charger, P9220_IC_GRADE);
|
|
} else if(val->intval == SEC_WIRELESS_IC_REVISION) {
|
|
val->intval = p9220_get_ic_grade(charger, P9220_IC_VERSION);
|
|
} else if(val->intval == SEC_WIRELESS_OTP_FIRM_VER_BIN) {
|
|
val->intval = P9220_OTP_FIRM_VERSION;
|
|
} else if(val->intval == SEC_WIRELESS_OTP_FIRM_VER) {
|
|
val->intval = p9220_get_firmware_version(charger, P9220_RX_FIRMWARE);
|
|
} 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) {
|
|
val->intval = p9220_firmware_verify(charger);
|
|
} else{
|
|
val->intval = -1;
|
|
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 = p9220_get_adc(charger, P9220_ADC_VOUT);
|
|
} else
|
|
val->intval = 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ENERGY_AVG: /* vrect */
|
|
if(charger->pdata->ic_on_mode || charger->pdata->is_charging) {
|
|
val->intval = p9220_get_adc(charger, P9220_ADC_VRECT);
|
|
} else
|
|
val->intval = 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_SCOPE:
|
|
val->intval = p9220_get_adc(charger, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX:
|
|
switch (ext_psp) {
|
|
case POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ:
|
|
val->intval = p9220_get_adc(charger, P9220_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_cmd;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_UPDATE_BATTERY_DATA)
|
|
static int p9220_chg_parse_dt(struct device *dev, p9220_charger_platform_data_t *pdata);
|
|
#endif
|
|
static int p9220_chg_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct p9220_charger_data *charger =
|
|
container_of(psy, struct p9220_charger_data, psy_chg);
|
|
int vout, vrect, iout, freq, i = 0;
|
|
union power_supply_propval value;
|
|
|
|
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 */
|
|
p9220_fod_set_cs100(charger);
|
|
}
|
|
charger->pdata->cs100_status = p9220_send_cs100(charger);
|
|
} else if(val->intval == POWER_SUPPLY_STATUS_NOT_CHARGING) {
|
|
p9220_mis_align(charger);
|
|
} else if(val->intval == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE) {
|
|
p9220_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__);
|
|
p9220_send_eop(charger, val->intval);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
if(val->intval == SEC_BATTERY_CABLE_WIRELESS ||
|
|
val->intval == SEC_BATTERY_CABLE_HV_WIRELESS ||
|
|
val->intval == SEC_BATTERY_CABLE_PMA_WIRELESS ) {
|
|
charger->pdata->ic_on_mode = true;
|
|
} else {
|
|
charger->pdata->ic_on_mode = false;
|
|
}
|
|
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);
|
|
p9220_set_vrect_adjust(charger, P9220_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);
|
|
p9220_set_vrect_adjust(charger, P9220_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:
|
|
p9220_firmware_update(charger, val->intval);
|
|
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++) {
|
|
p9220_send_command(charger, P9220_AFC_CONF_5V);
|
|
msleep(250);
|
|
}
|
|
} else if (val->intval == WIRELESS_VOUT_HIGH_VOLTAGE) {
|
|
pr_info("%s: Wireless Vout forced set to 9V. + PAD CMD\n",__func__);
|
|
for(i = 0; i < CMD_CNT; i++) {
|
|
p9220_send_command(charger, P9220_AFC_CONF_9V);
|
|
msleep(250);
|
|
}
|
|
} else if (val->intval == WIRELESS_VOUT_CC_CV_VOUT) {
|
|
p9220_set_vout(charger, P9220_VOUT_CC_CV);
|
|
pr_info("%s: Wireless Vout forced set to %dmV\n",
|
|
__func__, charger->pdata->wpc_cc_cv_vout);
|
|
} else if (val->intval == WIRELESS_VOUT_CV_CALL) {
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_3);
|
|
msleep(500);
|
|
p9220_set_vout(charger, P9220_VOUT_CV_CALL);
|
|
msleep(500);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_0);
|
|
pr_info("%s: Wireless Vout forced set to %dmV\n",
|
|
__func__, charger->pdata->wpc_cv_call_vout);
|
|
} else if (val->intval == WIRELESS_VOUT_CC_CALL) {
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_3);
|
|
msleep(500);
|
|
p9220_set_vout(charger, P9220_VOUT_CC_CALL);
|
|
msleep(500);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_0);
|
|
pr_info("%s: Wireless Vout forced set to %dmV\n",
|
|
__func__, charger->pdata->wpc_cc_call_vout);
|
|
} else if (val->intval == WIRELESS_VOUT_5V) {
|
|
p9220_set_vout(charger, P9220_VOUT_5V);
|
|
pr_info("%s: Wireless Vout forced set to 5V\n", __func__);
|
|
} else if (val->intval == WIRELESS_VOUT_9V) {
|
|
p9220_set_vout(charger, P9220_VOUT_9V);
|
|
pr_info("%s: Wireless Vout forced set to 9V\n", __func__);
|
|
} else if (val->intval == WIRELESS_VOUT_9V_OTG) {
|
|
p9220_set_vout(charger, P9220_VOUT_9V);
|
|
pr_info("%s: Wireless Vout forced set to 9V OTG\n", __func__);
|
|
} else if (val->intval == WIRELESS_PAD_FAN_OFF) {
|
|
pr_info("%s: fan off \n",__func__);
|
|
p9220_fan_control(charger, 0);
|
|
} else if (val->intval == WIRELESS_PAD_FAN_ON) {
|
|
pr_info("%s: fan on \n",__func__);
|
|
p9220_fan_control(charger, 1);
|
|
} else if (val->intval == WIRELESS_PAD_LED_OFF) {
|
|
pr_info("%s: led off \n",__func__);
|
|
p9220_led_control(charger, 0);
|
|
} else if (val->intval == WIRELESS_PAD_LED_ON) {
|
|
pr_info("%s: led on \n",__func__);
|
|
p9220_led_control(charger, 1);
|
|
} else if(val->intval == WIRELESS_VRECT_ADJ_ON) {
|
|
pr_info("%s: vrect adjust to have big headroom(defualt value) \n",__func__);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_1);
|
|
} else if(val->intval == WIRELESS_VRECT_ADJ_OFF) {
|
|
pr_info("%s: vrect adjust to have small headroom \n",__func__);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_0);
|
|
} else if(val->intval == WIRELESS_VRECT_ADJ_ROOM_0) {
|
|
pr_info("%s: vrect adjust to have headroom 0(0mV) \n",__func__);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_0);
|
|
} else if(val->intval == WIRELESS_VRECT_ADJ_ROOM_1) {
|
|
pr_info("%s: vrect adjust to have headroom 1(277mV) \n",__func__);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_1);
|
|
} else if(val->intval == WIRELESS_VRECT_ADJ_ROOM_2) {
|
|
pr_info("%s: vrect adjust to have headroom 2(497mV) \n",__func__);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_2);
|
|
} else if(val->intval == WIRELESS_VRECT_ADJ_ROOM_3) {
|
|
pr_info("%s: vrect adjust to have headroom 3(650mV) \n",__func__);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_3);
|
|
} else if(val->intval == WIRELESS_VRECT_ADJ_ROOM_4) {
|
|
pr_info("%s: vrect adjust to have headroom 4(30mV) \n",__func__);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_4);
|
|
} else if(val->intval == WIRELESS_VRECT_ADJ_ROOM_5) {
|
|
pr_info("%s: vrect adjust to have headroom 5(82mV) \n",__func__);
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_5);
|
|
} else if (val->intval == WIRELESS_CLAMP_ENABLE) {
|
|
pr_info("%s: enable clamp1, clamp2 for WPC modulation \n",__func__);
|
|
p9220_reg_update(charger->client, P9220_MOD_DEPTH_REG, 0x30, 0x30);
|
|
} 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:
|
|
p9220_chg_parse_dt(charger->dev, charger->pdata);
|
|
break;
|
|
#endif
|
|
case POWER_SUPPLY_PROP_ENERGY_NOW:
|
|
vout = p9220_get_adc(charger, P9220_ADC_VOUT);
|
|
vrect = p9220_get_adc(charger, P9220_ADC_VRECT);
|
|
iout = p9220_get_adc(charger, P9220_ADC_RX_IOUT);
|
|
freq = p9220_get_adc(charger, P9220_ADC_OP_FRQ);
|
|
pr_info("%s RX_VOUT = %dmV, RX_VRECT = %dmV, RX_IOUT = %dmA, OP_FREQ = %dKHz\n", __func__, vout, vrect, iout, freq);
|
|
break;
|
|
case POWER_SUPPLY_PROP_SCOPE:
|
|
return -EINVAL;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void p9220_wpc_opfq_work(struct work_struct *work)
|
|
{
|
|
struct p9220_charger_data *charger =
|
|
container_of(work, struct p9220_charger_data, wpc_opfq_work.work);
|
|
|
|
u16 op_fq;
|
|
u8 pad_mode;
|
|
union power_supply_propval value;
|
|
|
|
p9220_reg_read(charger->client, P9220_SYS_OP_MODE_REG, &pad_mode);
|
|
if (pad_mode & P9220_PAD_MODE_WPC) {
|
|
op_fq = p9220_get_adc(charger, P9220_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__);
|
|
p9220_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 & P9220_PAD_MODE_PMA) {
|
|
charger->pdata->cable_type = P9220_PAD_MODE_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 p9220_wpc_det_work(struct work_struct *work)
|
|
{
|
|
struct p9220_charger_data *charger =
|
|
container_of(work, struct p9220_charger_data, wpc_det_work.work);
|
|
int wc_w_state;
|
|
union power_supply_propval value;
|
|
u8 pad_mode;
|
|
u8 vrect;
|
|
|
|
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 = P9220_VOUT_5V;
|
|
|
|
/* read firmware version */
|
|
if(p9220_get_firmware_version(charger, P9220_RX_FIRMWARE) == P9220_OTP_FIRM_VERSION && adc_cal > 0)
|
|
p9220_runtime_sram_change(charger);/* change sram */
|
|
|
|
/* set fod value */
|
|
if(charger->pdata->fod_data_check)
|
|
p9220_fod_set(charger);
|
|
|
|
/* enable Mode Change INT */
|
|
p9220_reg_update(charger->client, P9220_INT_ENABLE_L_REG,
|
|
P9220_STAT_MODE_CHANGE_MASK, P9220_STAT_MODE_CHANGE_MASK);
|
|
|
|
/* read vrect adjust */
|
|
p9220_reg_read(charger->client, P9220_VRECT_SET_REG, &vrect);
|
|
|
|
pr_info("%s: wpc activated, set V_INT as PN\n",__func__);
|
|
|
|
/* read pad mode */
|
|
p9220_reg_read(charger->client, P9220_SYS_OP_MODE_REG, &pad_mode);
|
|
if(pad_mode & P9220_SYS_MODE_PMA) {
|
|
charger->pdata->cable_type = P9220_PAD_MODE_PMA;
|
|
value.intval = SEC_WIRELESS_PAD_PMA;
|
|
psy_do_property("wireless", set,
|
|
POWER_SUPPLY_PROP_ONLINE, value);
|
|
} else {
|
|
charger->pdata->cable_type = P9220_PAD_MODE_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));
|
|
}
|
|
|
|
/* set request afc_tx */
|
|
p9220_send_command(charger, P9220_REQUEST_AFC_TX);
|
|
#if 0
|
|
/* set request TX_ID */
|
|
p9220_send_command(charger, P9220_REQUEST_TX_ID);
|
|
#endif
|
|
|
|
charger->pdata->is_charging = 1;
|
|
} else if ((charger->wc_w_state == 1) && (wc_w_state == 0)) {
|
|
|
|
charger->pdata->cable_type = P9220_PAD_MODE_NONE;
|
|
charger->pdata->is_charging = 0;
|
|
charger->pdata->vout_status = P9220_VOUT_0V;
|
|
charger->pdata->opfq_cnt = 0;
|
|
charger->pdata->tx_data_cmd = 0;
|
|
charger->pdata->tx_data_val = 0;
|
|
|
|
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 (p9220_get_adc(charger, P9220_ADC_VRECT) >= 3000 &&
|
|
p9220_get_adc(charger, P9220_ADC_VOUT) <= 2000) {
|
|
pr_err("%s Restart M0\n", __func__);
|
|
p9220_reg_write(charger->client, 0x3040, 0x80); /*restart M0 */
|
|
}
|
|
|
|
if(delayed_work_pending(&charger->wpc_opfq_work)) {
|
|
wake_unlock(&charger->wpc_opfq_lock);
|
|
cancel_delayed_work(&charger->wpc_opfq_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);
|
|
}
|
|
|
|
static void p9220_wpc_isr_work(struct work_struct *work)
|
|
{
|
|
struct p9220_charger_data *charger =
|
|
container_of(work, struct p9220_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;
|
|
}
|
|
|
|
wake_lock(&charger->wpc_wake_lock);
|
|
pr_info("%s\n",__func__);
|
|
|
|
p9220_reg_read(charger->client, P9220_TX_DATA_COMMAND, &cmd_data);
|
|
p9220_reg_read(charger->client, P9220_TX_DATA_VALUE0, &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 == P9220_TX_DATA_COM_AFC_TX) {
|
|
switch (val_data) {
|
|
case 0x00:
|
|
charger->pad_vout = PAD_VOUT_5V;
|
|
break;
|
|
case 0x01:
|
|
pr_info("%s: AFC wireless charger\n", __func__);
|
|
if (!gpio_get_value(charger->pdata->wpc_det)) {
|
|
wake_unlock(&charger->wpc_wake_lock);
|
|
return;
|
|
}
|
|
/* send AFC_SET */
|
|
p9220_send_command(charger, P9220_AFC_CONF_9V);
|
|
msleep(500);
|
|
|
|
/* change cable type */
|
|
charger->pdata->cable_type = P9220_PAD_MODE_WPC_AFC;
|
|
value.intval = SEC_WIRELESS_PAD_WPC_HV;
|
|
psy_do_property("wireless", set,
|
|
POWER_SUPPLY_PROP_ONLINE, value);
|
|
|
|
/* retry to send AFC_SET for bad communication condition */
|
|
for(i = 0; i < CMD_CNT - 1; i++) {
|
|
int vout = 0;
|
|
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;
|
|
}
|
|
vout = p9220_get_adc(charger, P9220_ADC_VOUT);
|
|
if ( vout > 8500) {
|
|
pr_info("%s 9V set is done as vout is %dmV \n", __func__, vout);
|
|
break;
|
|
} else {
|
|
pr_info("%s 9V failed vout is %dmV \n", __func__, vout);
|
|
/* to init vout/vrect as 5V for stable CEP */
|
|
p9220_set_vout(charger, P9220_VOUT_5V);
|
|
msleep(500);
|
|
vout = p9220_get_adc(charger, P9220_ADC_VOUT);
|
|
pr_info("%s read vout should be 5V -> (%dmV) \n", __func__, vout);
|
|
|
|
/* send AFC_SET again */
|
|
pr_info("%s send AFC_CONF_9V again \n", __func__);
|
|
p9220_send_command(charger, P9220_AFC_CONF_9V);
|
|
msleep(500);
|
|
vout = p9220_get_adc(charger, P9220_ADC_VOUT);
|
|
pr_info("%s read vout should be 9V -> (%dmV) \n", __func__, vout);
|
|
}
|
|
}
|
|
|
|
if(sleep_mode) {
|
|
pr_info("%s sleep mode, turn on fan \n", __func__);
|
|
p9220_fan_control(charger, true);
|
|
msleep(250);
|
|
|
|
pr_info("%s sleep mode, turn off fan \n", __func__);
|
|
p9220_fan_control(charger, false);
|
|
msleep(250);
|
|
}
|
|
charger->pad_vout = PAD_VOUT_10V;
|
|
break;
|
|
case 0x02:
|
|
case 0x03:
|
|
case 0x04:
|
|
case 0x05:
|
|
case 0x06:
|
|
break;
|
|
case 0x11:
|
|
pr_info("%s: VEHICLE PAD\n", __func__);
|
|
charger->pdata->cable_type = P9220_PAD_MODE_WPC_VEHICLE;
|
|
value.intval = SEC_WIRELESS_PAD_VEHICLE;
|
|
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
|
|
case 0x40:
|
|
pr_info("%s: WIRELESS BATTERY PACK\n", __func__);
|
|
charger->pdata->cable_type = P9220_PAD_MODE_WPC_PACK;
|
|
value.intval = SEC_WIRELESS_PAD_WPC_PACK;
|
|
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
|
|
break;
|
|
case 0x41:
|
|
pr_info("%s: WIRELESS BATTERY PACK with TA\n", __func__);
|
|
charger->pdata->cable_type = P9220_PAD_MODE_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 == P9220_TX_DATA_COM_TX_ID) {
|
|
switch (val_data) {
|
|
case 0x00:
|
|
break;
|
|
case 0x30:
|
|
if (charger->pad_vout == PAD_VOUT_10V) {
|
|
charger->pdata->cable_type = P9220_PAD_MODE_WPC_STAND_HV;
|
|
value.intval = SEC_WIRELESS_PAD_WPC_STAND_HV;
|
|
} else {
|
|
charger->pdata->cable_type = P9220_PAD_MODE_WPC_STAND;
|
|
value.intval = SEC_WIRELESS_PAD_WPC_STAND;
|
|
}
|
|
pr_info("%s: STAND Wireless Charge PAD %s\n", __func__,
|
|
charger->pad_vout == PAD_VOUT_10V ? "HV" : "");
|
|
break;
|
|
case 0x40:
|
|
charger->pdata->cable_type = P9220_PAD_MODE_WPC_PACK;
|
|
value.intval = SEC_WIRELESS_PAD_WPC_PACK;
|
|
pr_info("%s: WIRELESS BATTERY PACK\n", __func__);
|
|
break;
|
|
case 0x41:
|
|
charger->pdata->cable_type = P9220_PAD_MODE_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\n", __func__);
|
|
}
|
|
|
|
psy_do_property("wireless", set, POWER_SUPPLY_PROP_ONLINE, value);
|
|
}
|
|
|
|
wake_unlock(&charger->wpc_wake_lock);
|
|
}
|
|
|
|
static void p9220_wpc_tx_id_work(struct work_struct *work)
|
|
{
|
|
struct p9220_charger_data *charger =
|
|
container_of(work, struct p9220_charger_data, wpc_tx_id_work.work);
|
|
|
|
pr_info("%s\n",__func__);
|
|
|
|
p9220_send_command(charger, P9220_REQUEST_TX_ID);
|
|
}
|
|
|
|
static irqreturn_t p9220_wpc_det_irq_thread(int irq, void *irq_data)
|
|
{
|
|
struct p9220_charger_data *charger = irq_data;
|
|
|
|
pr_info("%s !\n",__func__);
|
|
|
|
queue_delayed_work(charger->wqueue, &charger->wpc_det_work, 0);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t p9220_wpc_irq_thread(int irq, void *irq_data)
|
|
{
|
|
struct p9220_charger_data *charger = irq_data;
|
|
int wc_w_state_irq;
|
|
int ret;
|
|
u8 irq_src[2];
|
|
u8 reg_data;
|
|
|
|
pr_info("%s !\n",__func__);
|
|
wake_lock(&charger->wpc_wake_lock);
|
|
|
|
ret = p9220_reg_read(charger->client, P9220_INT_L_REG, &irq_src[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_INT_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;
|
|
}
|
|
|
|
pr_info("%s: interrupt source(0x%x)\n", __func__, irq_src[1] << 8 | irq_src[0]);
|
|
p9220_get_firmware_version(charger, P9220_RX_FIRMWARE);
|
|
|
|
if(irq_src[0] & P9220_STAT_MODE_CHANGE_MASK) {
|
|
pr_info("%s MODE CHANGE IRQ ! \n", __func__);
|
|
ret = p9220_reg_read(charger->client, P9220_SYS_OP_MODE_REG, ®_data);
|
|
}
|
|
|
|
if(irq_src[0] & P9220_STAT_TX_DATA_RECEIVED_MASK) {
|
|
pr_info("%s TX RECEIVED IRQ ! \n", __func__);
|
|
if(charger->pdata->cable_type == P9220_PAD_MODE_WPC_STAND ||
|
|
charger->pdata->cable_type == P9220_PAD_MODE_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] & P9220_STAT_OVER_CURR_MASK) {
|
|
pr_info("%s OVER CURRENT IRQ ! \n", __func__);
|
|
}
|
|
|
|
if(irq_src[1] & P9220_STAT_OVER_TEMP_MASK) {
|
|
pr_info("%s OVER TEMP IRQ ! \n", __func__);
|
|
}
|
|
|
|
if(irq_src[1] & P9220_STAT_TX_CONNECT_MASK) {
|
|
pr_info("%s TX CONNECT IRQ ! \n", __func__);
|
|
charger->pdata->tx_status = SEC_TX_POWER_TRANSFER;
|
|
}
|
|
|
|
msleep(5);
|
|
|
|
/* clear intterupt */
|
|
p9220_reg_write(charger->client, P9220_INT_CLEAR_L_REG, irq_src[0]); // clear int
|
|
p9220_reg_write(charger->client, P9220_INT_CLEAR_H_REG, irq_src[1]); // clear int
|
|
p9220_set_cmd_reg(charger, 0x20, P9220_CMD_CLEAR_INT_MASK); // command
|
|
|
|
/* debug */
|
|
ret = p9220_reg_read(charger->client, P9220_INT_L_REG, &irq_src[0]);
|
|
ret = p9220_reg_read(charger->client, P9220_INT_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;
|
|
}
|
|
|
|
static int p9220_chg_parse_dt(struct device *dev,
|
|
p9220_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;
|
|
|
|
np = of_find_node_by_name(NULL, "battery");
|
|
if (!np) {
|
|
pr_err("%s np NULL\n", __func__);
|
|
return 1;
|
|
} else {
|
|
p = of_get_property(np, "battery,fod_data", &len);
|
|
if (p) {
|
|
len = len / sizeof(u32);
|
|
pdata->fod_data = kzalloc(sizeof(*pdata->fod_data) * len, GFP_KERNEL);
|
|
ret = of_property_read_u32_array(np, "battery,fod_data",
|
|
pdata->fod_data, len);
|
|
pdata->fod_data_check = 1;
|
|
|
|
for(i = 0; i <len; i++)
|
|
pr_info("%s fod data = %d ",__func__,pdata->fod_data[i]);
|
|
} else {
|
|
pdata->fod_data_check = 0;
|
|
pr_err("%s there is not fod_data\n", __func__);
|
|
}
|
|
p = of_get_property(np, "battery,fod_data_cv", &len);
|
|
if (p) {
|
|
len = len / sizeof(u32);
|
|
pdata->fod_data_cv = kzalloc(sizeof(*pdata->fod_data_cv) * len, GFP_KERNEL);
|
|
ret = of_property_read_u32_array(np, "battery,fod_data_cv",
|
|
pdata->fod_data_cv, len);
|
|
pdata->fod_data_check = 1;
|
|
|
|
for(i = 0; i <len; i++)
|
|
pr_info("%s fod data_cv = %d ",__func__,pdata->fod_data_cv[i]);
|
|
} else {
|
|
pdata->fod_data_check = 0;
|
|
pr_err("%s there is not fod_data_cv\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_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;
|
|
}
|
|
|
|
/* 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 */
|
|
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);
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static ssize_t p9220_store_addr(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct p9220_charger_data *charger = container_of(psy, struct p9220_charger_data, psy_chg);
|
|
int x;
|
|
if (sscanf(buf, "0x%x\n", &x) == 1) {
|
|
charger->addr = x;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t p9220_show_addr(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct p9220_charger_data *charger = container_of(psy, struct p9220_charger_data, psy_chg);
|
|
return sprintf(buf, "0x%x\n", charger->addr);
|
|
}
|
|
|
|
static ssize_t p9220_store_size(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct p9220_charger_data *charger = container_of(psy, struct p9220_charger_data, psy_chg);
|
|
int x;
|
|
if (sscanf(buf, "%d\n", &x) == 1) {
|
|
charger->size = x;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t p9220_show_size(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct p9220_charger_data *charger = container_of(psy, struct p9220_charger_data, psy_chg);
|
|
return sprintf(buf, "0x%x\n", charger->size);
|
|
}
|
|
static ssize_t p9220_store_data(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct p9220_charger_data *charger = container_of(psy, struct p9220_charger_data, psy_chg);
|
|
int x;
|
|
|
|
if (sscanf(buf, "0x%x", &x) == 1) {
|
|
u8 data = x;
|
|
if (p9220_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 p9220_show_data(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct p9220_charger_data *charger = container_of(psy, struct p9220_charger_data, psy_chg);
|
|
u8 data;
|
|
int i, count = 0;;
|
|
if (charger->size == 0)
|
|
charger->size = 1;
|
|
|
|
for (i = 0; i < charger->size; i++) {
|
|
if (p9220_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, p9220_show_addr, p9220_store_addr);
|
|
static DEVICE_ATTR(size, 0644, p9220_show_size, p9220_store_size);
|
|
static DEVICE_ATTR(data, 0644, p9220_show_data, p9220_store_data);
|
|
|
|
static struct attribute *p9220_attributes[] = {
|
|
&dev_attr_addr.attr,
|
|
&dev_attr_size.attr,
|
|
&dev_attr_data.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group p9220_attr_group = {
|
|
.attrs = p9220_attributes,
|
|
};
|
|
|
|
|
|
static int p9220_charger_probe(
|
|
struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct device_node *of_node = client->dev.of_node;
|
|
struct p9220_charger_data *charger;
|
|
p9220_charger_platform_data_t *pdata = client->dev.platform_data;
|
|
int ret = 0;
|
|
int wc_w_state_irq;
|
|
|
|
dev_info(&client->dev,
|
|
"%s: p9220 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 = p9220_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->ic_on_mode = false;
|
|
charger->pdata->cable_type = P9220_PAD_MODE_NONE;
|
|
charger->pdata->is_charging = 0;
|
|
|
|
charger->pdata->otp_firmware_result = P9220_FW_RESULT_DOWNLOADING;
|
|
charger->pdata->tx_firmware_result = P9220_FW_RESULT_DOWNLOADING;
|
|
charger->pdata->tx_status = 0;
|
|
charger->pdata->cs100_status = 0;
|
|
charger->pdata->vout_status = P9220_VOUT_0V;
|
|
charger->pdata->opfq_cnt = 0;
|
|
charger->pdata->tx_data_cmd = 0;
|
|
charger->pdata->tx_data_val = 0;
|
|
|
|
charger->psy_chg.name = pdata->wireless_charger_name;
|
|
charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN;
|
|
charger->psy_chg.get_property = p9220_chg_get_property;
|
|
charger->psy_chg.set_property = p9220_chg_set_property;
|
|
charger->psy_chg.properties = sec_charger_props;
|
|
charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props);
|
|
|
|
mutex_init(&charger->io_lock);
|
|
|
|
/* wpc_det */
|
|
if (charger->pdata->irq_wpc_det) {
|
|
INIT_DELAYED_WORK(&charger->wpc_det_work, p9220_wpc_det_work);
|
|
INIT_DELAYED_WORK(&charger->wpc_opfq_work, p9220_wpc_opfq_work);
|
|
}
|
|
|
|
/* wpc_irq */
|
|
if (charger->pdata->irq_wpc_int) {
|
|
INIT_DELAYED_WORK(&charger->wpc_isr_work, p9220_wpc_isr_work);
|
|
INIT_DELAYED_WORK(&charger->wpc_tx_id_work, p9220_wpc_tx_id_work);
|
|
}
|
|
|
|
ret = power_supply_register(&client->dev, &charger->psy_chg);
|
|
if (ret) {
|
|
dev_err(&client->dev,
|
|
"%s: Failed to Register psy_chg\n", __func__);
|
|
goto err_supply_unreg;
|
|
}
|
|
|
|
charger->wqueue = create_singlethread_workqueue("p9220_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");
|
|
|
|
/* Enable interrupts after battery driver load */
|
|
/* wpc_det */
|
|
if (charger->pdata->irq_wpc_det) {
|
|
ret = request_threaded_irq(charger->pdata->irq_wpc_det,
|
|
NULL, p9220_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 */
|
|
if (charger->pdata->irq_wpc_int) {
|
|
msleep(100);
|
|
ret = request_threaded_irq(charger->pdata->irq_wpc_int,
|
|
NULL, p9220_wpc_irq_thread,
|
|
IRQF_TRIGGER_FALLING |
|
|
IRQF_ONESHOT,
|
|
"wpc-irq", charger);
|
|
if (ret) {
|
|
pr_err("%s: Failed to Reqeust IRQ\n", __func__);
|
|
goto err_irq_wpc_int;
|
|
}
|
|
}
|
|
|
|
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__);
|
|
|
|
p9220_reg_read(charger->client, P9220_INT_L_REG, &irq_src[0]);
|
|
p9220_reg_read(charger->client, P9220_INT_H_REG, &irq_src[1]);
|
|
/* clear intterupt */
|
|
p9220_reg_write(charger->client, P9220_INT_CLEAR_L_REG, irq_src[0]); // clear int
|
|
p9220_reg_write(charger->client, P9220_INT_CLEAR_H_REG, irq_src[1]); // clear int
|
|
p9220_set_cmd_reg(charger, 0x20, P9220_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));
|
|
}
|
|
|
|
ret = sysfs_create_group(&charger->psy_chg.dev->kobj, &p9220_attr_group);
|
|
if (ret) {
|
|
dev_info(&client->dev,
|
|
"%s: sysfs_create_group failed\n", __func__);
|
|
}
|
|
dev_info(&client->dev,
|
|
"%s: p9220 Charger Driver Loaded\n", __func__);
|
|
|
|
device_init_wakeup(charger->dev, 1);
|
|
return 0;
|
|
err_irq_wpc_int:
|
|
free_irq(charger->pdata->irq_wpc_det, NULL);
|
|
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 p9220_charger_remove(struct i2c_client *client)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#if defined CONFIG_PM
|
|
static int p9220_charger_suspend(struct i2c_client *client,
|
|
pm_message_t state)
|
|
{
|
|
struct p9220_charger_data *charger = i2c_get_clientdata(client);
|
|
|
|
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 p9220_charger_resume(struct i2c_client *client)
|
|
{
|
|
struct p9220_charger_data *charger = i2c_get_clientdata(client);
|
|
|
|
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 p9220_charger_suspend NULL
|
|
#define p9220_charger_resume NULL
|
|
#endif
|
|
|
|
static void p9220_charger_shutdown(struct i2c_client *client)
|
|
{
|
|
struct p9220_charger_data *charger = i2c_get_clientdata(client);
|
|
|
|
pr_info("%s \n", __func__);
|
|
if(charger->pdata->is_charging)
|
|
p9220_set_vrect_adjust(charger, P9220_HEADROOM_1);
|
|
}
|
|
|
|
static const struct i2c_device_id p9220_charger_id_table[] = {
|
|
{ "p9220-charger", 0 },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, p9220_id_table);
|
|
|
|
#ifdef CONFIG_OF
|
|
static struct of_device_id p9220_charger_match_table[] = {
|
|
{ .compatible = "idt,p9220-charger",},
|
|
{},
|
|
};
|
|
#else
|
|
#define p9220_charger_match_table NULL
|
|
#endif
|
|
|
|
static struct i2c_driver p9220_charger_driver = {
|
|
.driver = {
|
|
.name = "p9220-charger",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = p9220_charger_match_table,
|
|
},
|
|
.shutdown = p9220_charger_shutdown,
|
|
.suspend = p9220_charger_suspend,
|
|
.resume = p9220_charger_resume,
|
|
.probe = p9220_charger_probe,
|
|
.remove = p9220_charger_remove,
|
|
.id_table = p9220_charger_id_table,
|
|
};
|
|
|
|
static int __init p9220_charger_init(void)
|
|
{
|
|
pr_info("%s \n",__func__);
|
|
return i2c_add_driver(&p9220_charger_driver);
|
|
}
|
|
|
|
static void __exit p9220_charger_exit(void)
|
|
{
|
|
pr_info("%s \n",__func__);
|
|
i2c_del_driver(&p9220_charger_driver);
|
|
}
|
|
|
|
module_init(p9220_charger_init);
|
|
module_exit(p9220_charger_exit);
|
|
|
|
MODULE_DESCRIPTION("Samsung p9220 Charger Driver");
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_LICENSE("GPL"); |