blob: 1c664b50a7a9a0b8111b8f7b25d58c876d11e31b [file] [log] [blame]
/*
* 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, &reg);
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, &reg) < 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, &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");