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