blob: 5568eb48c45a6d2e2d7064395c6c81def88fbfea [file] [log] [blame]
/*
* Driver for the TI zx234502 battery charger.
*
* Author: Mark A. Greer <mgreer@animalcreek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/kthread.h>
//#include <linux/mutex.h>
#include <linux/semaphore.h>
#include <linux/power/aw3215_charger.h>
#include <linux/mfd/zx234290.h>
#include <mach/gpio.h>
#include <mach/pcu.h>
#include <mach/zx29_usb.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <asm/uaccess.h>
#include <linux/wakelock.h>
#define CHG_DEBUG
#ifdef CHG_DEBUG
#define GIC_DIST_ENABLE_SET 0x100
#define GIC_DIST_PENDING_SET 0x200
extern void __iomem *base_testtt;
#endif
static volatile int s_chg_chin_cnt = 0;
static volatile int s_chg_chstate_cnt = 0;
#define USB_IN GPIO_LOW
#define USB_OUT GPIO_HIGH
#define CHG_START GPIO_LOW
#define CHG_STOP GPIO_HIGH
#ifdef _USE_V3PHONE_TYPE_XRSD_
#define CHG_EN_TYPE GPIO_HIGH
#define CHG_DISEN_TYPE GPIO_LOW
#else
#define CHG_EN_TYPE GPIO_LOW
#define CHG_DISEN_TYPE GPIO_HIGH
#endif
//*when the bat voltage <3.2V usb will not enum ,then the sys fall in lowpower*/
#define USB_ENUM_MIN 3400
/*
* The FAULT register is latched by the zx234502 (except for NTC_FAULT)
* so the first read after a fault returns the latched value and subsequent
* reads return the current value. In order to return the fault status
* to the user, have the interrupt handler save the reg's value and retrieve
* it in the appropriate health/status routine. Each routine has its own
* flag indicating whether it should use the value stored by the last run
* of the interrupt handler or do an actual reg read. That way each routine
* can report back whatever fault may have occured.
*/
enum chg_stop_reason{
CHG_STOP_REASON_NO = 0,
CHG_STOP_REASON_TEMP= 1,
CHG_STOP_REASON_FULL = 2,
CHG_STOP_DEFALT= 0xff
};
struct aw3215_dev_info {
struct device *dev;
struct power_supply charger;
struct power_supply battery;
kernel_ulong_t model;
unsigned int chgin_irq;
unsigned int chgstate_irq;
//struct task_struct *chgin_irq_thread;
struct task_struct *chgstate_irq_thread;
u8 watchdog;
struct aw3215_platform_data *pdata;
unsigned int chgin_type;
unsigned int chgstate_type;
bool chg_en;
unsigned int chg_state;
//struct semaphore chgin_sem;
struct semaphore chgstate_sem;
enum chg_stop_reason stopchg_flag;
struct timer_list changed_timer;
struct wake_lock wlock_chgfull;
};
struct aw3215_dev_info *g_bdi = NULL;
/* Charger power supply property routines */
static int aw3215_charger_get_charge_type(struct aw3215_dev_info *bdi,union power_supply_propval *val)
{
val->intval = bdi->charger.type;
return 0;
}
static int aw3215_charger_get_status(struct aw3215_dev_info *bdi,union power_supply_propval *val)
{
#if 1
val->intval = bdi->chg_state;
#else
if(false==bdi->chg_en){
val->intval=POWER_SUPPLY_STATUS_NOT_CHARGING;/*diaable chg*/
return 0;
}
if (USB_IN==bdi->chgin_type) {
if(CHG_STOP==bdi->chgstate_type)
val->intval= POWER_SUPPLY_STATUS_FULL;
else if (CHG_START==bdi->chgstate_type)
val->intval= POWER_SUPPLY_STATUS_CHARGING;
else
val->intval=POWER_SUPPLY_STATUS_NOT_CHARGING;/*diaable chg*/
} else {
val->intval=POWER_SUPPLY_STATUS_DISCHARGING;/*usb not insert*/
}
#endif
return 0;
}
static int aw3215_charger_get_health(struct aw3215_dev_info *bdi,union power_supply_propval *val)
{
val->intval = POWER_SUPPLY_HEALTH_GOOD;
return 0;
}
int aw3215_charger_get_online(struct aw3215_dev_info *bdi,union power_supply_propval *val)
{
if (USB_IN==bdi->chgin_type) {
val->intval= 1;
} else {
val->intval=0;/*usb not insert*/
}
return 0;
}EXPORT_SYMBOL (aw3215_charger_get_online);
static int aw3215_charger_get_charger_enabled(struct aw3215_dev_info *bdi,union power_supply_propval *val)
{
val->intval = bdi->chg_en;
return 0;
}
static int aw3215_charger_get_voltage_max(struct aw3215_dev_info *bdi,union power_supply_propval *val)
{
val->intval = 4200;
return 0;
}
static int aw3215_charger_set_voltage(struct aw3215_dev_info *bdi,const union power_supply_propval *val)
{
return 0;
}
static int aw3215_charger_set_charger_config(struct aw3215_dev_info *bdi,const union power_supply_propval *val)
{
int ret = 0;
int gpio_state = 0;
if (val->intval==1) {
if (bdi->chg_en)
return 0;
gpio_state = CHG_EN_TYPE ;/*gpio low en chg*/
printk("mmi start chg\n");
} else {
if (!bdi->chg_en)
return 0;
gpio_state = CHG_DISEN_TYPE ;/*gpio high stop chg*/
printk("mmi stop chg\n");
}
disable_irq(bdi->chgstate_irq);
if (gpio_state == CHG_EN_TYPE) {
bdi->chg_en = true; /*(~gpio_state)*/
bdi->chgstate_type = CHG_START;
bdi->stopchg_flag = CHG_STOP_REASON_NO;
gpio_set_value(bdi->pdata->gpio_chgen, gpio_state);
if (bdi->chgin_type == USB_IN) {
bdi->chg_state = POWER_SUPPLY_STATUS_CHARGING;
irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_HIGH);
/* start charging in 5.3ms after enable */
if (gpio_get_value(bdi->pdata->gpio_chgstate))
mdelay(40);
if (gpio_get_value(bdi->pdata->gpio_chgstate)){
printk(KERN_INFO "chg still not chargin\n"); /* should not go here */
bdi->chg_state = POWER_SUPPLY_STATUS_FULL;
irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_LOW);
}
}
} else {
bdi->chg_en = false;
bdi->chgstate_type = CHG_STOP;
bdi->stopchg_flag = CHG_STOP_REASON_TEMP;
if (2==val->intval){/*chg full stop*/
bdi->stopchg_flag = CHG_STOP_REASON_FULL;
bdi->chg_state = POWER_SUPPLY_STATUS_FULL;
printk("mmi full stop chg\n");
}
else if (bdi->chgin_type == USB_IN)
bdi->chg_state = POWER_SUPPLY_STATUS_NOT_CHARGING;
gpio_set_value(bdi->pdata->gpio_chgen, gpio_state);
/* charger state changes from 0 to 1 in 0.12ms */
if (!gpio_get_value(bdi->pdata->gpio_chgstate))
udelay(500);
irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_LOW);
}
enable_irq(bdi->chgstate_irq);
#ifdef CHG_DEBUG
printk("irq1 chg_in= %d,chg_state_cnt= %d\n",s_chg_chstate_cnt,s_chg_chin_cnt);
printk("chg int enable reg1 =0x%x:\n", zx_read_reg(base_testtt + GIC_DIST_ENABLE_SET+0x4));
printk("chg int enable reg2 =0x%x:\n", zx_read_reg(base_testtt + GIC_DIST_ENABLE_SET+0x8));
printk("chg int pending reg1 =0x%x:\n", zx_read_reg(base_testtt + GIC_DIST_PENDING_SET+0x4));
printk("chg int pending reg2 =0x%x:\n", zx_read_reg(base_testtt + GIC_DIST_PENDING_SET+0x8));
printk("irq2 chg_in= %d,chg_state_cnt= %d\n",s_chg_chstate_cnt,s_chg_chin_cnt);
#endif
power_supply_changed(&bdi->charger);
return ret;
}
static int aw3215_charger_get_property(struct power_supply *psy,enum power_supply_property psp, union power_supply_propval *val)
{
struct aw3215_dev_info *bdi = container_of(psy, struct aw3215_dev_info, charger);
int ret;
//dev_dbg(bdi->dev, "prop: %d\n", psp);
//pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_PC1_AC2:
ret = aw3215_charger_get_charge_type(bdi, val);
break;
case POWER_SUPPLY_PROP_STATUS:
ret = aw3215_charger_get_status(bdi, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = aw3215_charger_get_health(bdi, val);
break;
case POWER_SUPPLY_PROP_ONLINE:
ret = aw3215_charger_get_online(bdi, val);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
ret = aw3215_charger_get_voltage_max(bdi, val);
break;
case POWER_SUPPLY_PROP_CHARGE_ENABLED:
ret = aw3215_charger_get_charger_enabled(bdi, val);
break;
default:
ret = -ENODATA;
}
//pm_runtime_put_sync(bdi->dev);
return ret;
}
static int aw3215_charger_set_property(struct power_supply *psy,enum power_supply_property psp,
const union power_supply_propval *val)
{
struct aw3215_dev_info *bdi =
container_of(psy, struct aw3215_dev_info, charger);
int ret;
//dev_dbg(bdi->dev, "prop: %d\n", psp);
//pm_runtime_get_sync(bdi->dev);
switch (psp) {
#if 0
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = zx234502_charger_set_current(bdi, val);
break;
#endif
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
ret = aw3215_charger_set_voltage(bdi, val);
break;
case POWER_SUPPLY_PROP_CHARGE_ENABLED:
ret = aw3215_charger_set_charger_config(bdi, val);
break;
default:
ret = -EINVAL;
}
//pm_runtime_put_sync(bdi->dev);
return ret;
}
static int aw3215_charger_property_is_writeable(struct power_supply *psy,enum power_supply_property psp)
{
int ret;
switch (psp)
{
//case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_CHARGE_ENABLED:
ret = 1;
break;
default:
ret = 0;
break;
}
return ret;
}
static enum power_supply_property aw3215_charger_properties[] = {
POWER_SUPPLY_PROP_PC1_AC2,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
//POWER_SUPPLY_PROP_CURRENT_NOW,
//POWER_SUPPLY_PROP_CURRENT_MAX,
//POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CHARGE_ENABLED,
};
static char *aw3215_charger_supplied_to[] = {
"main-battery",
};
static void aw3215_charger_init(struct power_supply *charger)
{
charger->name = "charger";
charger->type = POWER_SUPPLY_PCAC_UNKNOWN;
charger->properties = aw3215_charger_properties;
charger->num_properties = ARRAY_SIZE(aw3215_charger_properties);
charger->supplied_to = aw3215_charger_supplied_to;
//charger->num_supplies = ARRAY_SIZE(aw3215_charger_supplied_to);
charger->get_property = aw3215_charger_get_property;
charger->set_property = aw3215_charger_set_property;
charger->property_is_writeable = aw3215_charger_property_is_writeable;
}
/* Battery power supply property routines */
static int aw3215_battery_get_health(struct aw3215_dev_info *bdi,union power_supply_propval *val)
{
val->intval = POWER_SUPPLY_HEALTH_GOOD;
return 0;
}
static int aw3215_battery_get_online(struct aw3215_dev_info *bdi,union power_supply_propval *val)
{
val->intval = 1;/*bat on*/
return 0;
}
static int aw3215_battery_set_online(struct aw3215_dev_info *bdi,const union power_supply_propval *val)
{
return 0;
}
static int aw3215_battery_get_property(struct power_supply *psy,enum power_supply_property psp,
union power_supply_propval *val)
{
struct aw3215_dev_info *bdi =
container_of(psy, struct aw3215_dev_info, battery);
int ret;
//dev_dbg(bdi->dev, "prop: %d\n", psp);
//pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_HEALTH:
ret = aw3215_battery_get_health(bdi, val);
break;
case POWER_SUPPLY_PROP_ONLINE:
ret = aw3215_battery_get_online(bdi, val);
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = get_adc2_voltage();
ret = 0;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
//#ifdef CONFIG_ZX234290_ADC
val->intval = get_adc1_voltage() - 15;
ret = 0;
break;
default:
ret = -ENODATA;
}
//pm_runtime_put_sync(bdi->dev);
return ret;
}
static int aw3215_battery_set_property(struct power_supply *psy,enum power_supply_property psp,
const union power_supply_propval *val)
{
struct aw3215_dev_info *bdi =
container_of(psy, struct aw3215_dev_info, battery);
int ret;
//dev_dbg(bdi->dev, "prop: %d\n", psp);
//pm_runtime_put_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = aw3215_battery_set_online(bdi, val);
break;
default:
ret = -EINVAL;
}
//pm_runtime_put_sync(bdi->dev);
return ret;
}
static int aw3215_battery_property_is_writeable(struct power_supply *psy,enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static enum power_supply_property aw3215_battery_properties[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
//POWER_SUPPLY_PROP_CAPACITY,
};
static void aw3215_battery_init(struct power_supply *battery)
{
battery->name = "battery";
battery->type = POWER_SUPPLY_PCAC_UNKNOWN;
battery->properties = aw3215_battery_properties;
battery->num_properties = ARRAY_SIZE(aw3215_battery_properties);
battery->get_property = aw3215_battery_get_property;
battery->set_property = aw3215_battery_set_property;
battery->property_is_writeable = aw3215_battery_property_is_writeable;
}
#if 1
static irqreturn_t aw3215_charger_in_irq_primary_handler(int irq, struct aw3215_dev_info * bdi)
{
s_chg_chin_cnt++;
disable_irq_nosync(irq);
//pcu_int_clear(irq);
pcu_clr_irq_pending(irq);
up(&bdi->chgstate_sem);
//up(&bdi->chgin_sem);
return IRQ_HANDLED;
}
static irqreturn_t aw3215_charger_state_irq_primary_handler(int irq, struct aw3215_dev_info * bdi)
{
s_chg_chstate_cnt++;
disable_irq_nosync(irq);
//pcu_int_clear(irq);
pcu_clr_irq_pending(irq);
up(&bdi->chgstate_sem);
return IRQ_HANDLED;
}
#endif
#if 1
static irqreturn_t aw3215_chg_irq_handler_thread(void *data)
{
struct aw3215_dev_info *bdi = data;
struct aw3215_platform_data *pdata = bdi->pdata;
//unsigned int state_rcd = bdi->chg_state;
int g_gpio_state=CHG_STOP;
int g_gpio_in =USB_IN;
bool chg_changed_flag = false;
struct sched_param param = { .sched_priority = 2 };
param.sched_priority= 31;
sched_setscheduler(current, SCHED_FIFO, &param);
while(1)
{
down(&bdi->chgstate_sem);
g_gpio_in = gpio_get_value(pdata->gpio_chgin);
g_gpio_state = gpio_get_value(pdata->gpio_chgstate);
if(bdi->chgin_type!=g_gpio_in){
bdi->chgin_type = g_gpio_in;
if(g_gpio_in == USB_IN){
irq_set_irq_type(bdi->chgin_irq, IRQ_TYPE_LEVEL_HIGH);
printk("chg usb in\n");
dwc_otg_chg_inform(0);/*usb in*/
if(CHG_START == g_gpio_state){
bdi->chg_state = POWER_SUPPLY_STATUS_CHARGING;
printk("chg charging\n");
}
else if(bdi->stopchg_flag == CHG_STOP_REASON_TEMP){
bdi->chg_state = POWER_SUPPLY_STATUS_NOT_CHARGING;
printk("chg temp abnormal\n");
}
else{
bdi->chg_state = POWER_SUPPLY_STATUS_FULL;
printk("chg full\n");
}
}
else{/*usb out*/
bdi->chg_state = POWER_SUPPLY_STATUS_DISCHARGING;
printk("chg usb out\n");
irq_set_irq_type(bdi->chgin_irq, IRQ_TYPE_LEVEL_LOW);
dwc_otg_chg_inform(1);/*usb out*/
}
chg_changed_flag = true;
s_chg_chin_cnt--;
enable_irq(bdi->chgin_irq);
}
else if(bdi->chgstate_type!=g_gpio_state){
bdi->chgstate_type=g_gpio_state;
if (CHG_START == g_gpio_state){
printk("chg state charging\n");
bdi->chg_state=POWER_SUPPLY_STATUS_CHARGING;
irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_HIGH);
chg_changed_flag = true;
}
else {
if( bdi->chgin_type == USB_IN){
printk("chg state maybe full\n");
bdi->chg_state=POWER_SUPPLY_STATUS_FULL;
wake_lock(&(bdi->wlock_chgfull));
mod_timer(&bdi->changed_timer, jiffies + msecs_to_jiffies(500));
}
else{
printk("chg state dischargeing\n");
bdi->chg_state=POWER_SUPPLY_STATUS_DISCHARGING;
chg_changed_flag = true;
}
irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_LOW);
}
s_chg_chstate_cnt--;
enable_irq(bdi->chgstate_irq);
}
else{
printk("chg int maybe handled,in=%d,state = %d\n",s_chg_chin_cnt,s_chg_chstate_cnt);
if(s_chg_chstate_cnt){
s_chg_chstate_cnt--;
enable_irq(bdi->chgstate_irq);
}
if(s_chg_chin_cnt){
s_chg_chin_cnt--;
enable_irq(bdi->chgin_irq);
}
}
if(true == chg_changed_flag){
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
chg_changed_flag = false;
}
}
return 0;
}
#endif
#if 0
static irqreturn_t aw3215_chgin_irq_handler_thread(void *data)
{
struct aw3215_dev_info *bdi = data;
struct aw3215_platform_data *pdata = bdi->pdata;
int g_gpio_in =0;
struct sched_param param = { .sched_priority = 2 };
param.sched_priority= 31;
sched_setscheduler(current, SCHED_FIFO, &param);
while(1)
{
down(&bdi->chgin_sem);
g_gpio_in = gpio_get_value(pdata->gpio_chgin);
/*charging status*/
if (g_gpio_in == USB_IN) {
if (bdi->chgin_type == USB_IN) {
printk(KERN_INFO"chg usb in err\n");
}
else if (bdi->stopchg_flag == CHG_STOP_REASON_TEMP){
bdi->chgin_type = USB_IN;
printk(KERN_INFO"chg usb in temp err\n");
bdi->chg_state = POWER_SUPPLY_STATUS_NOT_CHARGING;
dwc_otg_chg_inform(0);/*usb in*/
}
else{
bdi->chgin_type = USB_IN;
disable_irq(bdi->chgstate_irq);
//gpio_set_value(pdata->gpio_chgen, CHG_EN_TYPE);
//bdi->chg_en = true;
//bdi->chgstate_type = CHG_START;
/* start charging in 5.3ms after enable */
if (gpio_get_value(pdata->gpio_chgstate))
mdelay(7);
if (bdi->chgstate_type == gpio_get_value(pdata->gpio_chgstate))
printk(KERN_INFO "chg still not chargin"); /* should not go here */
bdi->chgstate_type = gpio_get_value(pdata->gpio_chgstate);
bdi->chg_state = POWER_SUPPLY_STATUS_CHARGING;
irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_HIGH);
enable_irq(bdi->chgstate_irq);
printk(KERN_INFO"chg usb in\n");
dwc_otg_chg_inform(0);/*usb in*/
//irq_set_irq_type(bdi->chgin_irq, IRQ_TYPE_LEVEL_HIGH);
}
irq_set_irq_type(bdi->chgin_irq, IRQ_TYPE_LEVEL_HIGH);
} else {
bdi->chg_state = POWER_SUPPLY_STATUS_DISCHARGING;
if (bdi->chgin_type == USB_OUT) {
printk(KERN_INFO"chg usb out err\n");
} else {
bdi->chgin_type = USB_OUT;
//SINT32 Usb_plug = DISCONNECTED_FROM_HOST;
printk(KERN_INFO"chg usb out\n");
dwc_otg_chg_inform(1);/*usb out*/
//irq_set_irq_type(bdi->chgin_irq, IRQ_TYPE_LEVEL_LOW);
}
irq_set_irq_type(bdi->chgin_irq, IRQ_TYPE_LEVEL_LOW);
}
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
enable_irq(bdi->chgin_irq);
}
return 0;
}
static irqreturn_t aw3215_chgstate_irq_handler_thread(void *data)
{
struct aw3215_dev_info *bdi = data;
struct aw3215_platform_data *pdata = bdi->pdata;
//unsigned int state_rcd = bdi->chg_state;
int g_gpio_state=CHG_STOP;
uint adc1_v= 0;
struct sched_param param = { .sched_priority = 2 };
param.sched_priority= 31;
sched_setscheduler(current, SCHED_FIFO, &param);
while(1)
{
down(&bdi->chgstate_sem);
g_gpio_state = gpio_get_value(pdata->gpio_chgstate);
/*charging status*/
if (g_gpio_state == CHG_START) { /*low charging*/
bdi->chg_state = POWER_SUPPLY_STATUS_CHARGING;
if (bdi->chgstate_type == CHG_START) {
printk(KERN_INFO"chg chging err!\n");
} else {
bdi->chgstate_type = CHG_START;
//irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_HIGH);
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
printk(KERN_INFO"chg charging\n");
}
irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_HIGH);
} else {/*high stop charging*/
if (bdi->chgstate_type == CHG_STOP) {
printk(KERN_INFO"chg full err!\n");
} else {
if ((bdi->chgin_type == USB_IN)&&(bdi->stopchg_flag == CHG_STOP_REASON_TEMP)){
bdi->chg_state = POWER_SUPPLY_STATUS_NOT_CHARGING;
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
}
else if(bdi->chgin_type == USB_IN){
bdi->chg_state = POWER_SUPPLY_STATUS_FULL;
wake_lock(&(bdi->wlock_chgfull));
mod_timer(&bdi->changed_timer, jiffies + msecs_to_jiffies(500));
}
else{
bdi->chg_state = POWER_SUPPLY_STATUS_DISCHARGING;
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
}
bdi->chgstate_type = CHG_STOP;
printk(KERN_INFO "chg %s %s stop\n",
(bdi->chg_state == POWER_SUPPLY_STATUS_FULL) ? "full" : " ",
(bdi->chg_state == POWER_SUPPLY_STATUS_NOT_CHARGING) ? "temp error":"discharging");
}
irq_set_irq_type(bdi->chgstate_irq, IRQ_TYPE_LEVEL_LOW);
}
enable_irq(bdi->chgstate_irq);
}
return 0;
}
#endif
static int aw3215_setup_pdata(struct aw3215_dev_info *bdi,
struct aw3215_platform_data *pdata)
{
int ret;
if (!gpio_is_valid(pdata->gpio_chgen))
return -1;
ret = gpio_request(pdata->gpio_chgen, "chg_en");
if (ret < 0)
goto out;
ret = zx29_gpio_config(pdata->gpio_chgen, pdata->gpio_chgen_gpio_sel);
ret = gpio_direction_output(pdata->gpio_chgen, CHG_EN_TYPE);
bdi->chg_en =true;
bdi->stopchg_flag=CHG_STOP_DEFALT;
#ifndef CONFIG_AIC8800_MIFI_EN
//chg termination current ctrl
if (!gpio_is_valid(pdata->gpio_chgctrl))
return -1;
ret = gpio_request(pdata->gpio_chgctrl, "chg_ctrl");
if (ret < 0)
goto out;
ret = zx29_gpio_config(pdata->gpio_chgctrl, pdata->gpio_chgctrl_gpio_sel);
if (ret < 0)
goto out;
ret = gpio_direction_output(pdata->gpio_chgctrl, 0);
if (ret < 0)
goto out;
#endif
return 0;
out:
//gpio_free(pdata->gpio_int);
return -1;
}
static int aw3215_init_state(struct aw3215_dev_info *bdi)
{
struct aw3215_platform_data *pdata;
int ret = 0;
unsigned int g_gpio_in,g_gpio_state;
unsigned int chgin_irq_type=IRQ_TYPE_NONE;
unsigned int chgstate_irq_type=IRQ_TYPE_NONE;
pdata = bdi->pdata;
if (!gpio_is_valid(pdata->gpio_chgin))
goto error;
ret = gpio_request(pdata->gpio_chgin, "chg_usbin");
if (ret < 0)
goto error;
zx29_gpio_pd_pu_set(pdata->gpio_chgin, IO_CFG_PULL_DISABLE);
ret = zx29_gpio_config(pdata->gpio_chgin, pdata->gpio_chgin_gpio_sel);
if (ret < 0)
goto error;
ret = gpio_direction_input(pdata->gpio_chgin);
if (ret < 0)
goto error;
mdelay(20);/*?*/
g_gpio_in = gpio_get_value(pdata->gpio_chgin);
if ( USB_IN == g_gpio_in){
bdi->chgin_type = USB_IN;
printk(KERN_INFO"init usb in\n");
chgin_irq_type = IRQ_TYPE_LEVEL_HIGH;
#ifdef _CHARGER_UNNOTIFY_USB_
if(get_adc1_voltage()>USB_ENUM_MIN)
#endif
dwc_otg_chg_inform(0);/*usb in*/
}
else {
bdi->chgin_type = USB_OUT;
bdi->chg_state=POWER_SUPPLY_STATUS_DISCHARGING;
printk(KERN_INFO"init usb out\n");
chgin_irq_type = IRQ_TYPE_LEVEL_LOW;
dwc_otg_chg_inform(1);/*usb out*/
}
bdi->chgin_irq= gpio_to_irq(pdata->gpio_chgin);
ret = zx29_gpio_config(pdata->gpio_chgin,pdata->gpio_chgin_fun_sel);
if (ret < 0)
goto error;
//zx29_gpio_set_inttype(pdata->gpio_chgin,chgin_irq_type); //INT_POSEDGE
//pcu_clr_irq_pending(bdi->chgin_irq);
irq_set_irq_type(bdi->chgin_irq, chgin_irq_type);
if (!gpio_is_valid(pdata->gpio_chgstate))
goto error;
ret = gpio_request(pdata->gpio_chgstate, "chg_state");
if (ret < 0)
goto error;
zx29_gpio_pd_pu_set(pdata->gpio_chgstate, IO_CFG_PULL_DISABLE);
bdi->chgstate_irq= gpio_to_irq(pdata->gpio_chgstate);
ret = zx29_gpio_config(pdata->gpio_chgstate, pdata->gpio_chgstate_gpio_sel);
if (ret < 0)
goto error;
ret = gpio_direction_input(pdata->gpio_chgstate);
if (ret < 0)
goto error;
mdelay(20);/*?*/
g_gpio_state = gpio_get_value(pdata->gpio_chgstate);
if (CHG_START == g_gpio_state){
bdi->chgstate_type=CHG_START ;
bdi->chg_state=POWER_SUPPLY_STATUS_CHARGING;
printk(KERN_INFO"init chg state chargeing\n");
chgstate_irq_type = IRQ_TYPE_LEVEL_HIGH;
//dwc_otg_chg_inform(0);/*usb in*/
}
else {
bdi->chgstate_type =CHG_STOP ;
printk(KERN_INFO"init chg state discharger or full\n");
chgstate_irq_type = IRQ_TYPE_LEVEL_LOW;
//dwc_otg_chg_inform(1);/*usb out*/
if( bdi->chgin_type == USB_IN)
bdi->chg_state=POWER_SUPPLY_STATUS_FULL;
else
bdi->chg_state=POWER_SUPPLY_STATUS_DISCHARGING;
}
ret = zx29_gpio_config(pdata->gpio_chgstate,pdata->gpio_chgstate_fun_sel);
if (ret < 0)
goto error;
//zx29_gpio_set_inttype(pdata->gpio_chgstate,chgstate_irq_type); //INT_POSEDGE
//pcu_clr_irq_pending(bdi->chgstate_irq);
irq_set_irq_type(bdi->chgstate_irq, chgstate_irq_type);
return 0;
error:
printk(KERN_INFO"chg gpio error ret = %d\n",ret);
return -1;
}
static void aw3215_charge_typedet(T_TYPE_USB_DETECT chg_type)
{
//u8 ret;
#ifdef DBG_CHARGE
//printk(KERN_INFO"charge type is %d in\n",chg_type);
#endif
if(TYPE_ADAPTER == chg_type){
printk(KERN_INFO"chg type DC\n");
g_bdi->charger.type = POWER_SUPPLY_PCAC__AC;
}
else{
printk(KERN_INFO"chg type PC\n");
g_bdi->charger.type = POWER_SUPPLY_PCAC__PC;
}
}
#if defined(CONFIG_DEBUG_FS)
static ssize_t debugfs_regs_write(struct file *file, const char __user *buf,size_t nbytes, loff_t *ppos)
{
unsigned int val1, val2;
//u8 reg, value;
int ret = 0;
char *kern_buf;
//struct seq_file *s = file->private_data;
//struct aw3215_dev_info *aw3215 = s->private;
kern_buf = kzalloc(nbytes, GFP_KERNEL);
if (!kern_buf) {
printk(KERN_INFO "aw3215_charger: Failed to allocate buffer\n");
return -ENOMEM;
}
if (copy_from_user(kern_buf, (void __user *)buf, nbytes)) {
kfree(kern_buf);
return -ENOMEM;
}
printk(KERN_INFO "%s input str=%s,nbytes=%d \n", __func__, kern_buf,nbytes);
ret = sscanf(kern_buf, "%x:%x", &val1, &val2);
if (ret < 2) {
printk(KERN_INFO "sgm40561_charger: failed to read user buf, ret=%d, input 0x%x:0x%x\n",
ret,val1, val2);
kfree(kern_buf);
return -EINVAL;
}
kfree(kern_buf);
return ret ? ret : nbytes;
}
static int debugfs_regs_show(struct seq_file *s, void *v)
{
//int i;
int ret=0;
//int curr = 0;
struct aw3215_dev_info *aw3215 = s->private;
/*charger type*/
if((int)aw3215->charger.type == POWER_SUPPLY_PCAC__PC){
seq_printf(s, "charger type is PC\n");
}
else if((int)aw3215->charger.type == POWER_SUPPLY_PCAC__AC){
seq_printf(s, "charger type is AC\n");
}
else
seq_printf(s, "charger type is unknow = %d\n",aw3215->charger.type);
seq_printf(s, "mmi charger config state = %d\n",aw3215->chg_en);
seq_printf(s, "chg in state = %s\n",(aw3215->chgin_type==USB_IN)? "USB_IN": "USB_OUT");
seq_printf(s, "chg_state state = %s\n",aw3215->chgstate_type ? "CHG_STOP": "CHG_START");
#ifdef CHG_DEBUG
seq_printf("chgstate int en reg1 =0x%x:\n", zx_read_reg(base_testtt + GIC_DIST_ENABLE_SET+0x4));
seq_printf("chgstate int en reg2 =0x%x:\n", zx_read_reg(base_testtt + GIC_DIST_ENABLE_SET+0x8));
seq_printf("chgstate int pending reg1 =0x%x:\n", zx_read_reg(base_testtt + GIC_DIST_PENDING_SET+0x4));
seq_printf("chgstate int pending reg2 =0x%x:\n", zx_read_reg(base_testtt + GIC_DIST_PENDING_SET+0x8));
#endif
return ret;
}
#define DEBUGFS_FILE_ENTRY(name) \
static int debugfs_##name##_open(struct inode *inode, struct file *file) \
{\
return single_open(file, debugfs_##name##_show, inode->i_private); \
}\
\
static const struct file_operations debugfs_##name##_fops = { \
.owner= THIS_MODULE, \
.open= debugfs_##name##_open, \
.write=debugfs_##name##_write, \
.read= seq_read, \
.llseek= seq_lseek, \
.release= single_release, \
}
DEBUGFS_FILE_ENTRY(regs);
static struct dentry *g_charger_root;
static void debugfs_charger_init(struct aw3215_dev_info *aw3215)
{
struct dentry *root;
struct dentry *node;
//int i;
if(!aw3215)
return;
//create root
root = debugfs_create_dir("charger_zx29", NULL);
if (!root) {
dev_err(aw3215->dev, "debugfs_create_dir err=%ld\n", IS_ERR(root));
goto err;
}
//print regs;
node = debugfs_create_file("regs", S_IRUGO | S_IWUGO, root, aw3215, &debugfs_regs_fops);
if (!node){
dev_err(aw3215->dev, "debugfs_create_dir err=%ld\n", IS_ERR(node));
goto err;
}
g_charger_root = (void *)root;
return;
err:
dev_err(aw3215->dev, "debugfs_charger_init err\n");
}
#endif
static void aw3215_changed_timer_function(unsigned long data)
{
struct aw3215_dev_info *bdi = (void *)data;
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
//printk("chg timer callback\n");
del_timer(&bdi->changed_timer);
wake_unlock(&(bdi->wlock_chgfull));
printk("chg timer callback end\n");
}
static int __devinit aw3215_charger_probe(struct platform_device *pdev)
{
struct aw3215_platform_data *pdata = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct aw3215_dev_info *bdi;
//unsigned long flag;
int ret;
bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL);
if (!bdi) {
dev_err(dev, "Can't alloc bdi struct\n");
return -ENOMEM;
}
bdi->dev = dev;
bdi->pdata = pdata;
//printk(KERN_INFO "charger probe.\n");
bdi->chg_state = POWER_SUPPLY_STATUS_UNKNOWN;
bdi->charger.type = POWER_SUPPLY_TYPE_UNKNOWN;
g_bdi = bdi;
ret = aw3215_setup_pdata(bdi, pdata);
if (ret) {
dev_err(dev, "Can't get irq info\n");
return -EINVAL;
}
aw3215_charger_init(&bdi->charger);
ret = power_supply_register(dev, &bdi->charger);
if (ret) {
dev_err(dev, "Can't register charger\n");
goto out2;
}
//printk(KERN_INFO "aw3215_probe power_supply_register charger ok.\n");
aw3215_battery_init(&bdi->battery);
ret = power_supply_register(dev, &bdi->battery);
if (ret) {
dev_err(dev, "Can't register battery\n");
goto out1;
}
//printk(KERN_INFO "aw3215_probe power_supply_register battery ok.\n");
//sema_init(&bdi->chgin_sem, 0);
sema_init(&bdi->chgstate_sem, 0);
dwc_chg_Regcallback(aw3215_charge_typedet);/*register for usb*/
aw3215_init_state(bdi);
init_timer(&bdi->changed_timer);
bdi->changed_timer.function = aw3215_changed_timer_function;
bdi->changed_timer.data = (unsigned long)bdi;
wake_lock_init(&(bdi->wlock_chgfull), WAKE_LOCK_SUSPEND, "aw3215_wake_lock_chgfull");
/*chg in*/
ret = request_irq(bdi->chgin_irq, aw3215_charger_in_irq_primary_handler,IRQF_NO_THREAD, "aw3215-chgin", bdi);
if (ret < 0) {
dev_err(dev, "Can't set up irq handler\n");
if (bdi->pdata->gpio_chgin)
gpio_free(bdi->pdata->gpio_chgin);
goto out3;
}
irq_set_irq_wake(bdi->chgin_irq, 1);
//bdi->chgin_irq_thread = kthread_run(aw3215_chgin_irq_handler_thread, bdi, "aw3215-chgin");
//BUG_ON(IS_ERR(bdi->chgin_irq_thread));
/*chg state*/
ret = request_irq(bdi->chgstate_irq, aw3215_charger_state_irq_primary_handler,IRQF_NO_THREAD, "aw3215-chgstate", bdi);
if (ret < 0) {
dev_err(dev, "Can't set up irq handler\n");
if (bdi->pdata->gpio_chgstate)
gpio_free(bdi->pdata->gpio_chgstate);
goto out3;
}
irq_set_irq_wake(bdi->chgstate_irq, 1);
//bdi->chgstate_irq_thread = kthread_run(aw3215_chgstate_irq_handler_thread, bdi, "aw3215-chgstate");
//BUG_ON(IS_ERR(bdi->chgstate_irq_thread));
bdi->chgstate_irq_thread = kthread_run(aw3215_chg_irq_handler_thread, bdi, "aw3215-chgstate");
BUG_ON(IS_ERR(bdi->chgstate_irq_thread));
#if defined(CONFIG_DEBUG_FS)
debugfs_charger_init(bdi);
#endif
#ifdef DBG_CHARGE
//printk(KERN_INFO "aw3215_probe end.\n");
#endif
return 0;
out1:
power_supply_unregister(&bdi->battery);
out2:
//pm_runtime_disable(dev);
power_supply_unregister(&bdi->charger);
out3:
return ret;
}
static int aw3215_charger_remove(struct platform_device *pdev)
{
struct aw3215_platform_data *pdata = pdev->dev.platform_data;
power_supply_unregister(&(g_bdi->battery));
power_supply_unregister(&(g_bdi->charger));
pm_runtime_disable(g_bdi->dev);
if (pdata->gpio_chgctrl)
gpio_free(pdata->gpio_chgctrl);
if (pdata->gpio_chgen)
gpio_free(pdata->gpio_chgen);
if (pdata->gpio_chgin)
gpio_free(pdata->gpio_chgin);
if (pdata->gpio_chgstate)
gpio_free(pdata->gpio_chgstate);
#if defined(CONFIG_DEBUG_FS)
if(g_charger_root){
//printk(KERN_INFO "aw3215_device_exit:debugfs_remove_recursive \n");
debugfs_remove_recursive(g_charger_root);
}
#endif
return 0;
}
static struct platform_driver zx29_charger_driver = {
.probe = aw3215_charger_probe,
.remove = __devexit_p(aw3215_charger_remove),
.driver = {
.name = "aw3215-charger",
.owner = THIS_MODULE,
},
};
//module_platform_driver(zx29_charger_driver);
static int __init zx29_charger_init(void)
{
return platform_driver_register(&zx29_charger_driver);
}
static void __exit zx29_charger_exit(void)
{
platform_driver_unregister(&zx29_charger_driver);
}
module_init(zx29_charger_init);
module_exit(zx29_charger_exit);
MODULE_AUTHOR("Mark A. Greer <mgreer@animalcreek.com>");
MODULE_DESCRIPTION("AW3215 Charger Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:zx29_charger");