| /* |
| * 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, ¶m); |
| |
| 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, ¶m); |
| |
| 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, ¶m); |
| |
| 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"); |
| |