android_kernel_samsung_msm8976/drivers/muic/universal/muic_afc.c

491 lines
14 KiB
C

/*
* muic_afc.c
*
* Copyright (C) 2014 Samsung Electronics
* Jeongrae Kim <jryu.kim@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/muic/muic.h>
#include <linux/muic/muic_afc.h>
#include <linux/qcom/sec_param.h>
#include "muic-internal.h"
#include "muic_regmap.h"
#include "muic_i2c.h"
#if defined(CONFIG_MUIC_NOTIFIER)
#include <linux/muic/muic_notifier.h>
#endif /* CONFIG_MUIC_NOTIFIER */
/* Bit 0 : VBUS_VAILD, Bit 1~7 : Reserved */
#define REG_RSVDID1 0x15
#define REG_RSVDID2 0x16
#define REG_AFCTXD 0x19
#define REG_VBUSSTAT 0x1b
#define REG_DEVT1 0x0a
muic_data_t *gpmuic;
static int afc_work_state;
static int muic_is_afc_voltage(void);
static int muic_dpreset_afc(void);
static int muic_restart_afc(void);
/* To make AFC work properly on boot */
static int is_charger_ready;
static struct work_struct muic_afc_init_work;
void muic_afc_delay_check_state(int state);
int muic_check_afc_state(int state)
{
struct afc_ops *afcops = gpmuic->regmapdesc->afcops;
int ret, retry;
pr_info("%s state = %d\n", __func__, state);
if (gpmuic->afc_disable == 1) {
pr_info("AFC Disabled in a setting\n");
return 1;
}
if (state) {
/* Flash on state */
if (muic_is_afc_voltage() && gpmuic->is_afc_device) {
ret = muic_dpreset_afc();
if (ret < 0) {
pr_err("%s:failed to AFC reset(%d)\n",
__func__, ret);
}
msleep(60); // 60ms delay
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_VBUS_READ, 1);
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_VBUS_READ, 0);
for (retry = 0; retry <20; retry++) {
mdelay(20);
ret = muic_is_afc_voltage();
if (!ret) {
pr_info("%s:AFC Reset Success(%d)\n",
__func__, ret);
gpmuic->is_flash_on = 1;
return 1;
} else {
pr_info("%s:AFC Reset Failed(%d)\n",
__func__, ret);
gpmuic->is_flash_on = -1;
}
}
} else {
pr_info("%s:Not connected AFC\n",__func__);
gpmuic->is_flash_on = 1;
return 1;
}
} else {
/* Flash off state */
if ((gpmuic->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC) ||
((gpmuic->is_afc_device) && (gpmuic->attached_dev != ATTACHED_DEV_AFC_CHARGER_9V_MUIC)))
muic_restart_afc();
gpmuic->is_flash_on = 0;
return 1;
}
return 0;
}
EXPORT_SYMBOL(muic_check_afc_state);
int muic_torch_prepare(int state)
{
struct afc_ops *afcops = gpmuic->regmapdesc->afcops;
int ret, retry;
pr_info("%s state = %d\n", __func__, state);
if (afc_work_state == 1) {
pr_info("%s:%s cancel_delayed_work afc_work_state=%d\n",MUIC_DEV_NAME, __func__, afc_work_state);
cancel_delayed_work(&gpmuic->afc_restart_work);
afc_work_state = 0;
}
if (state) {
/* Torch on state */
if (muic_is_afc_voltage() && gpmuic->is_afc_device) {
ret = muic_dpreset_afc();
msleep(60); // 60ms delay
if (ret < 0) {
pr_err("%s:failed to AFC reset(%d)\n",
__func__, ret);
}
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_VBUS_READ, 1);
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_VBUS_READ, 0);
for (retry = 0; retry <20; retry++) {
mdelay(20);
ret = muic_is_afc_voltage();
if (!ret) {
pr_info("%s:AFC Reset Success(%d)\n",
__func__, ret);
gpmuic->is_flash_on = 1;
return 1;
} else {
pr_info("%s:AFC Reset Failed(%d)\n",
__func__, ret);
gpmuic->is_flash_on = -1;
}
}
} else {
pr_info("%s:Not connected AFC\n",__func__);
gpmuic->is_flash_on = 1;
return 1;
}
} else {
/* Torch off state */
gpmuic->is_flash_on = 0;
if ((gpmuic->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC) ||
((gpmuic->is_afc_device) && (gpmuic->attached_dev != ATTACHED_DEV_AFC_CHARGER_9V_MUIC))) {
schedule_delayed_work(&gpmuic->afc_restart_work, msecs_to_jiffies(5000)); // 20sec
pr_info("%s:%s AFC_torch_work start \n",MUIC_DEV_NAME, __func__ );
afc_work_state = 1;
}
return 1;
}
return 0;
}
EXPORT_SYMBOL(muic_torch_prepare);
static int muic_is_afc_voltage(void)
{
struct i2c_client *i2c = gpmuic->i2c;
int vbus_status;
if (gpmuic->attached_dev == ATTACHED_DEV_NONE_MUIC) {
pr_info("%s attached_dev None \n", __func__);
return 0;
}
vbus_status = muic_i2c_read_byte(i2c, REG_VBUSSTAT);
vbus_status = (vbus_status & 0x0F);
pr_info("%s vbus_status (%d)\n", __func__, vbus_status);
if (vbus_status == 0x00)
return 0;
else
return 1;
}
static int muic_dpreset_afc(void)
{
struct afc_ops *afcops = gpmuic->regmapdesc->afcops;
pr_info("%s: gpmuic->attached_dev = %d\n", __func__, gpmuic->attached_dev);
if ((gpmuic->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC) ||
(gpmuic->attached_dev == ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC) ||
(muic_is_afc_voltage()) ) {
// ENAFC set '0'
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_ENAFC, 0);
msleep(50); // 50ms delay
muic_afc_delay_check_state(0);
// DP_RESET
pr_info("%s:AFC Disable \n", __func__);
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_DIS_AFC, 1);
msleep(60);
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_DIS_AFC, 0);
gpmuic->attached_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC;
muic_notifier_attach_attached_dev(ATTACHED_DEV_AFC_CHARGER_5V_MUIC);
}
return 0;
}
static int muic_restart_afc(void)
{
struct i2c_client *i2c = gpmuic->i2c;
int ret, value;
struct afc_ops *afcops = gpmuic->regmapdesc->afcops;
pr_info("%s:AFC Restart attached_dev = 0x%x\n", __func__, gpmuic->attached_dev);
msleep(120); // 120ms delay
if (gpmuic->attached_dev == ATTACHED_DEV_NONE_MUIC) {
pr_info("%s:%s Device type is None\n",MUIC_DEV_NAME, __func__);
return 0;
}
gpmuic->attached_dev = ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC;
muic_notifier_attach_attached_dev(ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC);
cancel_delayed_work(&gpmuic->afc_retry_work);
schedule_delayed_work(&gpmuic->afc_retry_work, msecs_to_jiffies(5000)); // 5sec
// voltage(9.0V) + current(1.65A) setting : 0x
value = 0x46;
ret = muic_i2c_write_byte(i2c, REG_AFCTXD, value);
if (ret < 0)
printk(KERN_ERR "[muic] %s: err write AFC_TXD(%d)\n", __func__, ret);
pr_info("%s:AFC_TXD [0x%02x]\n", __func__, value);
// ENAFC set '1'
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_ENAFC, 1);
return 0;
}
static void muic_afc_restart_work(struct work_struct *work)
{
struct i2c_client *i2c = gpmuic->i2c;
int ret, value;
struct afc_ops *afcops = gpmuic->regmapdesc->afcops;
pr_info("%s:AFC Restart\n", __func__);
msleep(120); // 120ms delay
if (gpmuic->attached_dev == ATTACHED_DEV_NONE_MUIC) {
pr_info("%s:%s Device type is None\n",MUIC_DEV_NAME, __func__);
return;
}
gpmuic->attached_dev = ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC;
muic_notifier_attach_attached_dev(ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC);
cancel_delayed_work(&gpmuic->afc_retry_work);
schedule_delayed_work(&gpmuic->afc_retry_work, msecs_to_jiffies(5000)); // 5sec
// voltage(9.0V) + current(1.65A) setting : 0x
value = 0x46;
ret = muic_i2c_write_byte(i2c, REG_AFCTXD, value);
if (ret < 0)
printk(KERN_ERR "[muic] %s: err write AFC_TXD(%d)\n", __func__, ret);
pr_info("%s:AFC_TXD [0x%02x]\n", __func__, value);
// ENAFC set '1'
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_ENAFC, 1);
afc_work_state = 0;
}
static void muic_afc_retry_work(struct work_struct *work)
{
struct afc_ops *afcops = gpmuic->regmapdesc->afcops;
struct i2c_client *i2c = gpmuic->i2c;
int ret, vbus;
//Reason of AFC fail
ret = muic_i2c_read_byte(i2c, 0x3C);
pr_info("%s : Read 0x3C = [0x%02x]\n", __func__, ret);
pr_info("%s:AFC retry work\n", __func__);
if (gpmuic->attached_dev == ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC) {
vbus = muic_i2c_read_byte(i2c, REG_RSVDID1);
if (!(vbus & 0x01)) {
pr_info("%s:%s VBUS is nothing\n",MUIC_DEV_NAME, __func__);
gpmuic->attached_dev = ATTACHED_DEV_NONE_MUIC;
muic_notifier_attach_attached_dev(ATTACHED_DEV_NONE_MUIC);
return;
}
muic_afc_delay_check_state(0);
pr_info("%s: [MUIC] devtype is afc prepare - Disable AFC\n", __func__);
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_DIS_AFC, 1);
msleep(20);
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_DIS_AFC, 0);
}
}
void muic_afc_delay_check_state(int state)
{
struct i2c_client *i2c = gpmuic->i2c;
pr_info("%s : state = %d \n", __func__, state);
if ( state == 1 ) {
muic_i2c_write_byte(i2c, REG_RSVDID2, 0x2C); // VDP_SRC_EN '1'
gpmuic->delay_check_count = 0;
cancel_delayed_work(&gpmuic->afc_delay_check_work);
schedule_delayed_work(&gpmuic->afc_delay_check_work, msecs_to_jiffies(1700)); // 1.7 sec
pr_info("%s: afc_delay_check_work start\n", __func__);
} else {
muic_i2c_write_byte(i2c, REG_RSVDID2, 0x24); // VDP_SRC_EN '0'
cancel_delayed_work(&gpmuic->afc_delay_check_work);
pr_info("%s: afc_delay_check_work cancel (%d)\n", __func__ ,gpmuic->delay_check_count);
gpmuic->delay_check_count = 0;
}
}
static void muic_afc_delay_check_work(struct work_struct *work)
{
struct afc_ops *afcops = gpmuic->regmapdesc->afcops;
struct i2c_client *i2c = gpmuic->i2c;
int val1;
pr_info("%s: attached_dev = %d\n", __func__, gpmuic->attached_dev);
pr_info("%s: delay_check_count = %d , is_flash_on=%d\n", __func__, gpmuic->delay_check_count, gpmuic->is_flash_on);
if (gpmuic->delay_check_count > 5) {
muic_afc_delay_check_state(0);
return;
}
if (gpmuic->is_flash_on == 1) {
muic_afc_delay_check_state(0);
pr_info("%s: skip\n", __func__);
return;
}
val1 = muic_i2c_read_byte(i2c, REG_DEVT1);
pr_info("%s:val1 = 0x%x \n", __func__, val1);
if ( (gpmuic->attached_dev == ATTACHED_DEV_TA_MUIC) && (val1 == 0x40) ){
pr_info("%s: DP_RESET\n", __func__);
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_DIS_AFC, 1);
msleep(60);
afcops->afc_ctrl_reg(gpmuic->regmapdesc, AFCCTRL_DIS_AFC, 0);
cancel_delayed_work(&gpmuic->afc_delay_check_work);
schedule_delayed_work(&gpmuic->afc_delay_check_work, msecs_to_jiffies(1700)); // 1.7 sec
gpmuic->delay_check_count++;
pr_info("%s: afc_delay_check_work retry : %d \n", __func__, gpmuic->delay_check_count);
}
}
static void muic_focrced_detection_by_charger(struct work_struct *work)
{
struct afc_ops *afcops = gpmuic->regmapdesc->afcops;
pr_info("%s\n", __func__);
mutex_lock(&gpmuic->muic_mutex);
afcops->afc_init_check(gpmuic->regmapdesc);
mutex_unlock(&gpmuic->muic_mutex);
}
void muic_charger_init(void)
{
pr_info("%s\n", __func__);
if (!gpmuic) {
pr_info("%s: MUIC AFC is not ready.\n", __func__);
return;
}
if (is_charger_ready) {
pr_info("%s: charger is already ready.\n", __func__);
return;
}
is_charger_ready = true;
if (gpmuic->attached_dev == ATTACHED_DEV_TA_MUIC)
schedule_work(&muic_afc_init_work);
}
static ssize_t afc_off_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
muic_data_t *pmuic = dev_get_drvdata(dev);
return snprintf(buf, 4, "%d\n", pmuic->is_flash_on);
}
static ssize_t afc_off_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (!strncmp(buf, "1", 1)) {
pr_info("%s, Disable AFC\n", __func__);
muic_check_afc_state(1);
} else {
pr_info("%s, Enable AFC\n", __func__);
muic_check_afc_state(0);
}
return size;
}
static DEVICE_ATTR(afc_off, S_IRUGO | S_IWUSR,
afc_off_show, afc_off_store);
static ssize_t afc_disable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
muic_data_t *pmuic = dev_get_drvdata(dev);
return snprintf(buf, 4, "%d\n", pmuic->afc_disable);
}
static ssize_t afc_disable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
muic_data_t *pmuic = dev_get_drvdata(dev);
struct muic_platform_data *pdata = pmuic->pdata;
unsigned int param_val;
int ret = 0;
/* Disable AFC */
if (!strncmp(buf, "1", 1)) {
pr_info("%s, Disable AFC\n", __func__);
param_val = 1;
pmuic->afc_disable = 1;
} else {
/* Enable AFC */
pr_info("%s, Enable AFC\n", __func__);
param_val = 0;
pmuic->afc_disable = 0;
}
pr_info("%s: param_val:%d\n",__func__,param_val);
ret = sec_set_param(param_index_afc_disable, &param_val);
if (ret == false) {
pr_err("%s:set_param failed - %02x:(%d)\n", __func__,
param_val, ret);
return ret;
}else{
pr_info("%s:%s afc_disable:%d (AFC %s)\n", MUIC_DEV_NAME, __func__,
pdata->afc_disable, pdata->afc_disable ? "Disabled": "Enabled");
}
return size;
}
static DEVICE_ATTR(afc_disable, S_IRUGO | S_IWUSR,
afc_disable_show, afc_disable_store);
void muic_init_afc_state(muic_data_t *pmuic)
{
int ret;
gpmuic = pmuic;
gpmuic->is_flash_on = 0;
/* To make AFC work properly on boot */
INIT_WORK(&muic_afc_init_work, muic_focrced_detection_by_charger);
gpmuic->is_afc_device = 0;
INIT_DELAYED_WORK(&gpmuic->afc_restart_work, muic_afc_restart_work);
INIT_DELAYED_WORK(&gpmuic->afc_retry_work, muic_afc_retry_work);
INIT_DELAYED_WORK(&gpmuic->afc_delay_check_work, muic_afc_delay_check_work);
ret = device_create_file(switch_device, &dev_attr_afc_off);
if (ret < 0) {
pr_err("[MUIC] Failed to create file (disable AFC)!\n");
}
ret = device_create_file(switch_device, &dev_attr_afc_disable);
if (ret < 0) {
pr_err("[MUIC] Failed to create file (disable AFC)!\n");
}
pr_info("%s:attached_dev = %d\n", __func__, gpmuic->attached_dev);
}
MODULE_DESCRIPTION("MUIC driver");
MODULE_AUTHOR("<jryu.kim@samsung.com>");
MODULE_LICENSE("GPL");