| /* |
| * linux/drivers/power/twl6030_charger.c |
| * |
| * Based on: OMAP4:TWL6030 charger driver for Linux |
| * |
| * Copyright (C) 2012 Google, Inc. |
| * Copyright (C) 2008-2009 Texas Instruments, Inc. |
| * |
| * This package 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. |
| * |
| * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED |
| * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/device.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/i2c/twl.h> |
| #include <linux/power_supply.h> |
| #include <linux/i2c/twl6030-gpadc.h> |
| #include <linux/i2c/bq2415x.h> |
| #include <linux/wakelock.h> |
| #include <linux/usb/otg.h> |
| #include <asm/div64.h> |
| #include <linux/reboot.h> |
| |
| #define CONTROLLER_INT_MASK 0x00 |
| #define CONTROLLER_CTRL1 0x01 |
| #define CONTROLLER_WDG 0x02 |
| #define CONTROLLER_STAT1 0x03 |
| #define CHARGERUSB_INT_STATUS 0x04 |
| #define CHARGERUSB_INT_MASK 0x05 |
| #define CHARGERUSB_STATUS_INT1 0x06 |
| #define CHARGERUSB_STATUS_INT2 0x07 |
| #define CHARGERUSB_CTRL1 0x08 |
| #define CHARGERUSB_CTRL2 0x09 |
| #define CHARGERUSB_CTRL3 0x0A |
| #define CHARGERUSB_STAT1 0x0B |
| #define CHARGERUSB_VOREG 0x0C |
| #define CHARGERUSB_VICHRG 0x0D |
| #define CHARGERUSB_CINLIMIT 0x0E |
| #define CHARGERUSB_CTRLLIMIT1 0x0F |
| #define CHARGERUSB_CTRLLIMIT2 0x10 |
| #define ANTICOLLAPSE_CTRL1 0x11 |
| #define ANTICOLLAPSE_CTRL2 0x12 |
| #define LED_PWM_CTRL1 0x14 |
| #define LED_PWM_CTRL2 0x15 |
| |
| #define FG_REG_00 0x00 |
| #define FG_REG_01 0x01 |
| #define FG_REG_02 0x02 |
| #define FG_REG_03 0x03 |
| #define FG_REG_04 0x04 |
| #define FG_REG_05 0x05 |
| #define FG_REG_06 0x06 |
| #define FG_REG_07 0x07 |
| #define FG_REG_08 0x08 |
| #define FG_REG_09 0x09 |
| #define FG_REG_10 0x0A |
| #define FG_REG_11 0x0B |
| |
| /* CONTROLLER_INT_MASK */ |
| #define MVAC_FAULT (1 << 7) |
| #define MAC_EOC (1 << 6) |
| #define LINCH_GATED (1 << 5) |
| #define MBAT_REMOVED (1 << 4) |
| #define MFAULT_WDG (1 << 3) |
| #define MBAT_TEMP (1 << 2) |
| #define MVBUS_DET (1 << 1) |
| #define MVAC_DET (1 << 0) |
| |
| /* CONTROLLER_CTRL1 */ |
| #define CONTROLLER_CTRL1_EN_LINCH (1 << 5) |
| #define CONTROLLER_CTRL1_EN_CHARGER (1 << 4) |
| #define CONTROLLER_CTRL1_SEL_CHARGER (1 << 3) |
| |
| /* CONTROLLER_STAT1 */ |
| #define CONTROLLER_STAT1_EXTCHRG_STATZ (1 << 7) |
| #define CONTROLLER_STAT1_LINCH_GATED (1 << 6) |
| #define CONTROLLER_STAT1_CHRG_DET_N (1 << 5) |
| #define CONTROLLER_STAT1_FAULT_WDG (1 << 4) |
| #define CONTROLLER_STAT1_VAC_DET (1 << 3) |
| #define VAC_DET (1 << 3) |
| #define CONTROLLER_STAT1_VBUS_DET (1 << 2) |
| #define VBUS_DET (1 << 2) |
| #define CONTROLLER_STAT1_BAT_REMOVED (1 << 1) |
| #define CONTROLLER_STAT1_BAT_TEMP_OVRANGE (1 << 0) |
| |
| /* CHARGERUSB_INT_STATUS */ |
| #define EN_LINCH (1 << 4) |
| #define CURRENT_TERM_INT (1 << 3) |
| #define CHARGERUSB_STAT (1 << 2) |
| #define CHARGERUSB_THMREG (1 << 1) |
| #define CHARGERUSB_FAULT (1 << 0) |
| |
| /* CHARGERUSB_INT_MASK */ |
| #define MASK_MCURRENT_TERM (1 << 3) |
| #define MASK_MCHARGERUSB_STAT (1 << 2) |
| #define MASK_MCHARGERUSB_THMREG (1 << 1) |
| #define MASK_MCHARGERUSB_FAULT (1 << 0) |
| |
| /* CHARGERUSB_STATUS_INT1 */ |
| #define CHARGERUSB_STATUS_INT1_TMREG (1 << 7) |
| #define CHARGERUSB_STATUS_INT1_NO_BAT (1 << 6) |
| #define CHARGERUSB_STATUS_INT1_BST_OCP (1 << 5) |
| #define CHARGERUSB_STATUS_INT1_TH_SHUTD (1 << 4) |
| #define CHARGERUSB_STATUS_INT1_BAT_OVP (1 << 3) |
| #define CHARGERUSB_STATUS_INT1_POOR_SRC (1 << 2) |
| #define CHARGERUSB_STATUS_INT1_SLP_MODE (1 << 1) |
| #define CHARGERUSB_STATUS_INT1_VBUS_OVP (1 << 0) |
| |
| /* CHARGERUSB_STATUS_INT2 */ |
| #define ICCLOOP (1 << 3) |
| #define CURRENT_TERM (1 << 2) |
| #define CHARGE_DONE (1 << 1) |
| #define ANTICOLLAPSE (1 << 0) |
| |
| /* CHARGERUSB_CTRL1 */ |
| #define SUSPEND_BOOT (1 << 7) |
| #define OPA_MODE (1 << 6) |
| #define HZ_MODE (1 << 5) |
| #define TERM (1 << 4) |
| |
| /* CHARGERUSB_CTRL2 */ |
| #define CHARGERUSB_CTRL2_VITERM_50 (0 << 5) |
| #define CHARGERUSB_CTRL2_VITERM_100 (1 << 5) |
| #define CHARGERUSB_CTRL2_VITERM_150 (2 << 5) |
| #define CHARGERUSB_CTRL2_VITERM_400 (7 << 5) |
| |
| /* CHARGERUSB_CTRL3 */ |
| #define VBUSCHRG_LDO_OVRD (1 << 7) |
| #define CHARGE_ONCE (1 << 6) |
| #define BST_HW_PR_DIS (1 << 5) |
| #define AUTOSUPPLY (1 << 3) |
| #define BUCK_HSILIM (1 << 0) |
| |
| /* CHARGERUSB_VOREG */ |
| #define CHARGERUSB_VOREG_3P52 0x01 |
| #define CHARGERUSB_VOREG_4P0 0x19 |
| #define CHARGERUSB_VOREG_4P2 0x23 |
| #define CHARGERUSB_VOREG_4P76 0x3F |
| |
| /* CHARGERUSB_VICHRG */ |
| #define CHARGERUSB_VICHRG_300 0x0 |
| #define CHARGERUSB_VICHRG_500 0x4 |
| #define CHARGERUSB_VICHRG_1500 0xE |
| |
| /* CHARGERUSB_CINLIMIT */ |
| #define CHARGERUSB_CIN_LIMIT_100 0x1 |
| #define CHARGERUSB_CIN_LIMIT_300 0x5 |
| #define CHARGERUSB_CIN_LIMIT_500 0x9 |
| #define CHARGERUSB_CIN_LIMIT_NONE 0xF |
| |
| /* CHARGERUSB_CTRLLIMIT1 */ |
| #define VOREGL_4P16 0x21 |
| #define VOREGL_4P56 0x35 |
| |
| /* CHARGERUSB_CTRLLIMIT2 */ |
| #define CHARGERUSB_CTRLLIMIT2_1500 0x0E |
| #define LOCK_LIMIT (1 << 4) |
| |
| /* ANTICOLLAPSE_CTRL2 */ |
| #define BUCK_VTH_SHIFT 5 |
| |
| /* FG_REG_00 */ |
| #define CC_ACTIVE_MODE_SHIFT 6 |
| #define CC_AUTOCLEAR (1 << 2) |
| #define CC_CAL_EN (1 << 1) |
| #define CC_PAUSE (1 << 0) |
| |
| #define REG_TOGGLE1 0x90 |
| #define FGDITHS (1 << 7) |
| #define FGDITHR (1 << 6) |
| #define FGS (1 << 5) |
| #define FGR (1 << 4) |
| |
| /* TWL6030_GPADC_CTRL */ |
| #define GPADC_CTRL_TEMP1_EN (1 << 0) /* input ch 1 */ |
| #define GPADC_CTRL_TEMP2_EN (1 << 1) /* input ch 4 */ |
| #define GPADC_CTRL_SCALER_EN (1 << 2) /* input ch 2 */ |
| #define GPADC_CTRL_SCALER_DIV4 (1 << 3 |
| #define GPADC_CTRL_SCALER_EN_CH11 (1 << 4) /* input ch 11 */ |
| #define GPADC_CTRL_TEMP1_EN_MONITOR (1 << 5) |
| #define GPADC_CTRL_TEMP2_EN_MONITOR (1 << 6) |
| #define GPADC_CTRL_ISOURCE_EN (1 << 7) |
| |
| #define GPADC_ISOURCE_22uA 22 |
| #define GPADC_ISOURCE_7uA 7 |
| |
| /* TWL6030/6032 BATTERY VOLTAGE GPADC CHANNELS */ |
| #define TWL6030_GPADC_VBAT_CHNL 0x07 |
| #define TWL6032_GPADC_VBAT_CHNL 0x12 |
| |
| /* TWL6030_GPADC_CTRL2 */ |
| #define GPADC_CTRL2_CH18_SCALER_EN BIT(2) |
| |
| #define REG_MISC1 0xE4 |
| #define VAC_MEAS 0x04 |
| #define VBAT_MEAS 0x02 |
| #define BB_MEAS 0x01 |
| |
| #define REG_USB_VBUS_CTRL_SET 0x04 |
| #define VBUS_MEAS 0x01 |
| #define REG_USB_ID_CTRL_SET 0x06 |
| #define ID_MEAS 0x01 |
| |
| #define BBSPOR_CFG 0xE6 |
| #define BB_CHG_EN (1 << 3) |
| |
| #define STS_HW_CONDITIONS 0x21 |
| #define STS_USB_ID (1 << 2) /* Level status of USB ID */ |
| |
| enum led_mode { |
| LED_PWM_AUTO = 0x0, |
| LED_PWM_ON = 0x1, |
| LED_PWM_OFF= 0x2, |
| }; |
| |
| #if defined(CONFIG_MACH_NOTLE) |
| extern int notle_version_support_battery_temperature(void); |
| #endif |
| |
| /* To get VBUS input limit from twl6030_usb */ |
| #if CONFIG_TWL6030_USB |
| extern unsigned int twl6030_get_usb_max_power(struct otg_transceiver *x); |
| #else |
| static inline unsigned int twl6030_get_usb_max_power(struct otg_transceiver *x) |
| { |
| return 0; |
| }; |
| #endif |
| |
| /* sign extension needs a little care */ |
| static __inline int sign_extend(int n, int num_bits) |
| { |
| int shift = (int)(sizeof(int) * 8 - num_bits); |
| return (n << shift) >> shift; |
| } |
| |
| /* Ptr to thermistor table */ |
| static struct wake_lock usb_wake_lock; |
| |
| #define STATE_BATTERY 0 /* no wall power, charging disabled */ |
| #define STATE_FAULT 1 /* charging off due to fault condition */ |
| #define STATE_FULL 2 /* wall power but battery is charged */ |
| #define STATE_USB 3 /* 500mA wall power, charging enabled */ |
| #define STATE_AC 4 /* 1000mA wall power, charging enabled */ |
| |
| static const char *twl6030_state[] = { |
| "BATTERY", "FAULT", "FULL", "USB", "AC" |
| }; |
| |
| #define is_powered(di) (di->state > STATE_FAULT) |
| #define is_charging(di) (di->state > STATE_FULL) |
| |
| #define LED_STATE_NONE -1 |
| #define LED_STATE_OFF 0 |
| #define LED_STATE_ON 1 |
| #define LED_STATE_RAMP 2 |
| #define LED_STATE_OSCILLATE 3 |
| #define LED_UPDATE_RATE_MS 66 |
| #define LED_RAMPUP_TIME_MS 1280 |
| |
| struct led_state { |
| int state; // current state |
| int next; // state after completing current state |
| |
| enum led_mode mode; // cached led mode |
| int cur; // current value |
| int target; // target value |
| int base; // bottom/top of oscillation |
| |
| long rate; // rate to change value |
| |
| int led_low; // configured low led level |
| int led_high; // configured high led level |
| int led_step; // step to increment or decrement per timer tick |
| }; |
| |
| struct twl6030_charger_device_info { |
| struct device *dev; |
| |
| int voltage_uV; |
| int current_uA; |
| int current_avg_uA; |
| int temperature_cC; |
| int bat_health; |
| int state; |
| int vbus_online; |
| |
| int current_limit_mA; |
| int temperature_critical; |
| int charge_disabled; |
| |
| int charge_top_off; |
| |
| struct led_state led; |
| struct timer_list led_timer; |
| struct work_struct led_work; |
| |
| unsigned int recharge_capacity; |
| |
| unsigned long full_jiffies; |
| unsigned long monitor_interval_jiffies; |
| |
| u8 usb_online; |
| |
| u8 watchdog_duration; |
| unsigned int min_vbus; |
| |
| struct twl4030_charger_platform_data *platform_data; |
| |
| unsigned int charger_incurrent_mA; |
| unsigned int charger_outcurrent_mA; |
| unsigned long usb_max_power; |
| unsigned long usb_event; |
| |
| struct power_supply usb; |
| struct power_supply *battery; |
| |
| struct otg_transceiver *otg; |
| struct notifier_block nb; |
| |
| struct work_struct charge_control_work; |
| struct work_struct charge_fault_work; |
| struct delayed_work monitor_work; |
| |
| struct workqueue_struct *wq; |
| }; |
| |
| /* Intensity curve: 255*((x/255)**2) over [0, 255] */ |
| static const int led_curve[] = { |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, |
| 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, |
| 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, |
| 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x09, 0x09, |
| 0x09, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0d, 0x0d, |
| 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, |
| 0x13, 0x13, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17, 0x17, 0x18, |
| 0x19, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, |
| 0x1f, 0x20, 0x21, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, |
| 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2c, 0x2d, 0x2e, |
| 0x2f, 0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, |
| 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, |
| 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, |
| 0x4c, 0x4d, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x57, |
| 0x58, 0x59, 0x5a, 0x5b, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x63, |
| 0x64, 0x65, 0x66, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x70, |
| 0x71, 0x72, 0x74, 0x75, 0x76, 0x78, 0x79, 0x7a, 0x7c, 0x7d, |
| 0x7f, 0x80, 0x81, 0x83, 0x84, 0x86, 0x87, 0x89, 0x8a, 0x8c, |
| 0x8d, 0x8f, 0x90, 0x92, 0x93, 0x95, 0x96, 0x98, 0x99, 0x9b, |
| 0x9c, 0x9e, 0xa0, 0xa1, 0xa3, 0xa4, 0xa6, 0xa8, 0xa9, 0xab, |
| 0xac, 0xae, 0xb0, 0xb1, 0xb3, 0xb5, 0xb6, 0xb8, 0xba, 0xbc, |
| 0xbd, 0xbf, 0xc1, 0xc3, 0xc4, 0xc6, 0xc8, 0xca, 0xcb, 0xcd, |
| 0xcf, 0xd1, 0xd3, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, 0xe0, |
| 0xe1, 0xe3, 0xe5, 0xe7, 0xe9, 0xeb, 0xed, 0xef, 0xf1, 0xf3, |
| 0xf5, 0xf7, 0xf9, 0xfb, 0xfd, 0xff, |
| }; |
| |
| /* Convert from output curve to linear intensity. Used to convert the |
| * initial boot value of the led into the linear state machine space. |
| * This inverse table selects the mid-points within ranges of equivalent |
| * mappings */ |
| static const int led_curve_inverse[] = { |
| 0x07, 0x13, 0x19, 0x1d, 0x21, 0x25, 0x29, 0x2c, 0x2e, 0x31, |
| 0x33, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 0x40, 0x42, 0x44, 0x46, |
| 0x48, 0x4a, 0x4b, 0x4d, 0x4f, 0x50, 0x52, 0x53, 0x55, 0x56, |
| 0x58, 0x59, 0x5b, 0x5c, 0x5e, 0x5f, 0x60, 0x62, 0x63, 0x64, |
| 0x65, 0x67, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, |
| 0x71, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, |
| 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, |
| 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8d, 0x8e, |
| 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x94, 0x95, 0x96, 0x97, |
| 0x98, 0x99, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9e, 0x9f, |
| 0xa0, 0xa1, 0xa2, 0xa2, 0xa3, 0xa4, 0xa5, 0xa5, 0xa6, 0xa7, |
| 0xa8, 0xa8, 0xa9, 0xaa, 0xab, 0xab, 0xac, 0xad, 0xae, 0xae, |
| 0xaf, 0xb0, 0xb1, 0xb1, 0xb2, 0xb3, 0xb3, 0xb4, 0xb5, 0xb6, |
| 0xb6, 0xb7, 0xb8, 0xb8, 0xb9, 0xba, 0xba, 0xbb, 0xbc, 0xbc, |
| 0xbd, 0xbe, 0xbe, 0xbf, 0xc0, 0xc0, 0xc1, 0xc2, 0xc2, 0xc3, |
| 0xc4, 0xc4, 0xc5, 0xc6, 0xc6, 0xc7, 0xc8, 0xc8, 0xc9, 0xc9, |
| 0xca, 0xcb, 0xcb, 0xcc, 0xcd, 0xcd, 0xce, 0xce, 0xcf, 0xd0, |
| 0xd0, 0xd1, 0xd2, 0xd2, 0xd3, 0xd3, 0xd4, 0xd5, 0xd5, 0xd6, |
| 0xd6, 0xd7, 0xd8, 0xd8, 0xd9, 0xd9, 0xda, 0xda, 0xdb, 0xdc, |
| 0xdc, 0xdd, 0xdd, 0xde, 0xde, 0xdf, 0xe0, 0xe0, 0xe1, 0xe1, |
| 0xe2, 0xe2, 0xe3, 0xe4, 0xe4, 0xe5, 0xe5, 0xe6, 0xe6, 0xe7, |
| 0xe7, 0xe8, 0xe9, 0xe9, 0xea, 0xea, 0xeb, 0xeb, 0xec, 0xec, |
| 0xed, 0xed, 0xee, 0xee, 0xef, 0xf0, 0xf0, 0xf1, 0xf1, 0xf2, |
| 0xf2, 0xf3, 0xf3, 0xf4, 0xf4, 0xf5, 0xf5, 0xf6, 0xf6, 0xf7, |
| 0xf7, 0xf8, 0xf8, 0xf9, 0xf9, 0xfa, 0xfa, 0xfb, 0xfb, 0xfc, |
| 0xfc, 0xfd, 0xfd, 0xfe, 0xfe, 0xff, |
| }; |
| |
| static int twl6030_get_pwm_level(void) |
| { |
| u8 pwm = 0; |
| twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &pwm, LED_PWM_CTRL1); |
| return led_curve_inverse[pwm]; |
| } |
| |
| static void twl6030_set_pwm_level(u8 pwm) |
| { |
| twl_i2c_write_u8(TWL6030_MODULE_CHARGER, led_curve[pwm], LED_PWM_CTRL1); |
| } |
| |
| static int twl6030_get_led_mode(void) |
| { |
| u8 mode = 0; |
| twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &mode, LED_PWM_CTRL2); |
| |
| return mode & 0x3; |
| } |
| |
| static void twl6030_set_led_mode(enum led_mode mode) |
| { |
| u8 oldval = 0; |
| |
| twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &oldval, LED_PWM_CTRL2); |
| oldval = (oldval & 0xfc) | mode; |
| twl_i2c_write_u8(TWL6030_MODULE_CHARGER, oldval, LED_PWM_CTRL2); |
| } |
| |
| static void twl6030_set_led(struct twl6030_charger_device_info *di, |
| enum led_mode mode, int pwm) |
| { |
| if (pwm >= 0) { |
| di->led.cur = pwm; |
| twl6030_set_pwm_level(pwm & 0xff); |
| } |
| |
| di->led.mode = mode; |
| twl6030_set_led_mode(mode); |
| } |
| |
| static void twl6030_led_timer(unsigned long data) |
| { |
| struct twl6030_charger_device_info *di = (struct twl6030_charger_device_info *) data; |
| queue_work(di->wq, &di->led_work); |
| } |
| |
| /* |
| * Read the hardware state and setup the led state struct to match. |
| */ |
| static void twl6030_init_led_state(struct twl6030_charger_device_info *di) |
| { |
| int pwm, mode; |
| |
| pwm = twl6030_get_pwm_level(); |
| mode = twl6030_get_led_mode(); |
| |
| switch (di->led.mode) { |
| case LED_PWM_OFF: |
| di->led.state = LED_STATE_OFF; |
| di->led.cur = pwm; |
| di->led.mode = mode; |
| break; |
| |
| case LED_PWM_ON: |
| di->led.state = LED_STATE_ON; |
| di->led.cur = pwm; |
| di->led.mode = mode; |
| break; |
| |
| default: |
| // set the state to off, since we don't know if the bootloader left the charger on |
| twl6030_set_led(di, LED_PWM_OFF, pwm); |
| di->led.state = LED_STATE_OFF; |
| break; |
| } |
| |
| di->led.next = LED_STATE_NONE; |
| di->led.rate = 0; |
| di->led.target = di->led.cur; |
| di->led.base = 0; |
| di->led.led_low = 0x00; |
| di->led.led_high = 0xff; |
| di->led.led_step = (di->led.led_high - di->led.led_low) / (LED_RAMPUP_TIME_MS / LED_UPDATE_RATE_MS); |
| |
| setup_timer(&di->led_timer, twl6030_led_timer, (unsigned long) di); |
| } |
| |
| static int twl6030_led_add(struct twl6030_charger_device_info *di, int inc) |
| { |
| int value = di->led.cur + inc; |
| |
| /* if the increment crosses the target, clip to target */ |
| if ((value < di->led.target && di->led.cur > di->led.target) || |
| (value > di->led.target && di->led.cur < di->led.target)) { |
| value = di->led.target; |
| } |
| |
| return value; |
| } |
| |
| static void twl6030_led_run(struct twl6030_charger_device_info *di) |
| { |
| switch (di->led.state) { |
| case LED_STATE_OFF: |
| twl6030_set_led(di, LED_PWM_OFF, -1); |
| |
| if (di->led.next > LED_STATE_NONE) { |
| di->led.state = di->led.next; |
| di->led.next = LED_STATE_NONE; |
| mod_timer(&di->led_timer, msecs_to_jiffies(di->led.rate) + jiffies); |
| } |
| break; |
| |
| case LED_STATE_ON: |
| twl6030_set_led(di, LED_PWM_ON, di->led.cur); |
| |
| if (di->led.next > LED_STATE_NONE) { |
| di->led.state = di->led.next; |
| di->led.next = LED_STATE_NONE; |
| mod_timer(&di->led_timer, msecs_to_jiffies(di->led.rate) + jiffies); |
| } |
| break; |
| |
| case LED_STATE_RAMP: |
| if (di->led.mode != LED_PWM_ON) |
| twl6030_set_led(di, LED_PWM_ON, 0); |
| |
| if (di->led.cur < di->led.target) { |
| di->led.cur = twl6030_led_add(di, di->led.led_step); |
| twl6030_set_pwm_level(di->led.cur); |
| } else if (di->led.cur > di->led.target) { |
| di->led.cur = twl6030_led_add(di, -di->led.led_step); |
| twl6030_set_pwm_level(di->led.cur); |
| } |
| |
| if (di->led.cur == di->led.target) { |
| di->led.state = di->led.next; |
| di->led.next = LED_STATE_NONE; |
| } |
| |
| mod_timer(&di->led_timer, msecs_to_jiffies(di->led.rate) + jiffies); |
| break; |
| |
| case LED_STATE_OSCILLATE: |
| if (di->led.mode != LED_PWM_ON) |
| twl6030_set_led(di, LED_PWM_ON, 0); |
| |
| if (di->led.cur < di->led.target) { |
| di->led.cur = twl6030_led_add(di, di->led.led_step); |
| twl6030_set_pwm_level(di->led.cur); |
| } else if (di->led.cur > di->led.target) { |
| di->led.cur = twl6030_led_add(di, -di->led.led_step); |
| twl6030_set_pwm_level(di->led.cur); |
| } |
| |
| if (di->led.cur == di->led.target) { |
| int temp = di->led.target; |
| di->led.target = di->led.base; |
| di->led.base = temp; |
| } |
| |
| mod_timer(&di->led_timer, msecs_to_jiffies(di->led.rate) + jiffies); |
| break; |
| } |
| } |
| |
| static void twl6030_led_work(struct work_struct *work) |
| { |
| struct twl6030_charger_device_info *di = container_of(work, |
| struct twl6030_charger_device_info, led_work); |
| twl6030_led_run(di); |
| } |
| |
| static void twl6030_eval_led_state(struct twl6030_charger_device_info *di) |
| { |
| if (is_powered(di)) { |
| if (is_charging(di)) { |
| if (di->led.state != LED_STATE_OSCILLATE) { |
| del_timer_sync(&di->led_timer); |
| |
| di->led.state = LED_STATE_OSCILLATE; |
| di->led.target = di->led.led_high; |
| di->led.base = di->led.led_low; |
| di->led.rate = LED_UPDATE_RATE_MS; |
| |
| twl6030_led_run(di); |
| } |
| } else { |
| if (di->led.state != LED_STATE_ON && |
| !(di->led.state == LED_STATE_RAMP && di->led.next == LED_STATE_ON |
| && di->led.target == (di->led.led_high >> 2))) { |
| del_timer_sync(&di->led_timer); |
| |
| di->led.state = LED_STATE_RAMP; |
| di->led.target = di->led.led_high >> 2; |
| di->led.rate = LED_UPDATE_RATE_MS; |
| di->led.next = LED_STATE_ON; |
| |
| twl6030_led_run(di); |
| } |
| } |
| } else { |
| if (di->led.state != LED_STATE_OFF && !(di->led.state == LED_STATE_RAMP |
| && di->led.next == LED_STATE_OFF && di->led.target == 0x00)) { |
| del_timer_sync(&di->led_timer); |
| |
| di->led.state = LED_STATE_RAMP; |
| di->led.target = 0x00; |
| di->led.rate = LED_UPDATE_RATE_MS; |
| di->led.next = LED_STATE_OFF; |
| |
| twl6030_led_run(di); |
| } |
| } |
| } |
| |
| static struct power_supply *get_battery_power_supply( |
| struct twl6030_charger_device_info *di) |
| { |
| if (!di->battery) |
| di->battery = power_supply_get_by_name(di->usb.supplied_to[0]); |
| |
| return di->battery; |
| } |
| |
| /* charger configuration */ |
| static void twl6030_config_min_vbus_reg(struct twl6030_charger_device_info *di, |
| unsigned int value) |
| { |
| u8 rd_reg = 0; |
| int ret; |
| |
| if (value > 4760 || value < 4200) { |
| dev_dbg(di->dev, "invalid min vbus\n"); |
| return; |
| } |
| |
| ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &rd_reg, |
| ANTICOLLAPSE_CTRL2); |
| if (ret) |
| goto err; |
| rd_reg = rd_reg & 0x1F; |
| rd_reg = rd_reg | (((value - 4200)/80) << BUCK_VTH_SHIFT); |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, rd_reg, |
| ANTICOLLAPSE_CTRL2); |
| |
| if (!ret) |
| return; |
| err: |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| } |
| |
| static void twl6030_config_iterm_reg(struct twl6030_charger_device_info *di, |
| unsigned int term_currentmA) |
| { |
| int ret; |
| |
| if ((term_currentmA > 400) || (term_currentmA < 50)) { |
| dev_dbg(di->dev, "invalid termination current\n"); |
| return; |
| } |
| |
| term_currentmA = ((term_currentmA - 50)/50) << 5; |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, term_currentmA, |
| CHARGERUSB_CTRL2); |
| if (ret) |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| } |
| |
| static void twl6030_config_voreg_reg(struct twl6030_charger_device_info *di, |
| unsigned int voltagemV) |
| { |
| int ret; |
| |
| if ((voltagemV < 3500) || (voltagemV > 4760)) { |
| dev_dbg(di->dev, "invalid charger_voltagemV\n"); |
| return; |
| } |
| |
| voltagemV = (voltagemV - 3500) / 20; |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, voltagemV, |
| CHARGERUSB_VOREG); |
| if (ret) |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| } |
| |
| static void twl6030_config_vichrg_reg(struct twl6030_charger_device_info *di, |
| unsigned int currentmA) |
| { |
| int ret; |
| |
| if ((currentmA >= 300) && (currentmA <= 450)) |
| currentmA = (currentmA - 300) / 50; |
| else if ((currentmA >= 500) && (currentmA <= 1500)) |
| currentmA = (currentmA - 500) / 100 + 4; |
| else { |
| dev_dbg(di->dev, "invalid charger_currentmA\n"); |
| return; |
| } |
| |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, currentmA, |
| CHARGERUSB_VICHRG); |
| if (ret) |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| } |
| |
| static void twl6030_config_cinlimit_reg(struct twl6030_charger_device_info *di, |
| unsigned int currentmA) |
| { |
| int ret; |
| |
| if ((currentmA >= 50) && (currentmA <= 750)) { |
| currentmA = (currentmA - 50) / 50; |
| } else if (currentmA < 50) { |
| dev_dbg(di->dev, "invalid input current limit\n"); |
| return; |
| } else { |
| /* This is no current limit */ |
| currentmA = 0x0F; |
| } |
| |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, currentmA, |
| CHARGERUSB_CINLIMIT); |
| if (ret) |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| } |
| |
| static void twl6030_config_limit1_reg(struct twl6030_charger_device_info *di, |
| unsigned int voltagemV) |
| { |
| int ret; |
| |
| if ((voltagemV < 3500) || (voltagemV > 4760)) { |
| dev_dbg(di->dev, "invalid max_charger_voltage_mV\n"); |
| return; |
| } |
| |
| voltagemV = (voltagemV - 3500) / 20; |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, voltagemV, |
| CHARGERUSB_CTRLLIMIT1); |
| if (ret) |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| } |
| |
| static void twl6030_config_limit2_reg(struct twl6030_charger_device_info *di, |
| unsigned int currentmA) |
| { |
| int ret; |
| |
| if ((currentmA >= 300) && (currentmA <= 450)) |
| currentmA = (currentmA - 300) / 50; |
| else if ((currentmA >= 500) && (currentmA <= 1500)) |
| currentmA = (currentmA - 500) / 100 + 4; |
| else { |
| dev_dbg(di->dev, "invalid max_charger_current_mA\n"); |
| return; |
| } |
| |
| currentmA |= LOCK_LIMIT; |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, currentmA, |
| CHARGERUSB_CTRLLIMIT2); |
| if (ret) |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| } |
| |
| static int twl6030_set_watchdog(struct twl6030_charger_device_info *di, int val) |
| { |
| di->watchdog_duration = val; |
| return twl_i2c_write_u8(TWL6030_MODULE_CHARGER, val, CONTROLLER_WDG); |
| } |
| |
| |
| static const int vichrg[] = { |
| 300, 350, 400, 450, 500, 600, 700, 800, |
| 900, 1000, 1100, 1200, 1300, 1400, 1500, 300 |
| }; |
| |
| /* |
| * Return channel value |
| * Or < 0 on failure. |
| */ |
| static int twl6030_get_gpadc_conversion(struct twl6030_charger_device_info *di, |
| int channel_no) |
| { |
| struct twl6030_gpadc_request req; |
| int temp = 0; |
| int ret; |
| |
| req.channels = (1 << channel_no); |
| req.method = TWL6030_GPADC_SW2; |
| req.active = 0; |
| req.func_cb = NULL; |
| ret = twl6030_gpadc_conversion(&req); |
| if (ret < 0) |
| return ret; |
| |
| if (req.rbuf[channel_no] > 0) |
| temp = req.rbuf[channel_no]; |
| |
| return temp; |
| } |
| |
| static int is_battery_present(struct twl6030_charger_device_info *di) |
| { |
| /* TODO */ |
| return 1; |
| } |
| |
| /* TODO: remove this hack once notle-usb-mux.c takes over the MUX config completely */ |
| extern void usb_mux_force(int use_usb); |
| |
| static void twl6030_stop_usb_charger(struct twl6030_charger_device_info *di) |
| { |
| int ret; |
| |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, 0, CONTROLLER_CTRL1); |
| if (ret) |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| dev_warn(di->dev, "battery: CHARGER OFF\n"); |
| |
| usb_mux_force(false); |
| } |
| |
| static void twl6030_start_usb_charger(struct twl6030_charger_device_info *di, int mA) |
| { |
| int ret; |
| |
| if (!is_battery_present(di)) { |
| dev_err(di->dev, "BATTERY NOT DETECTED!\n"); |
| return; |
| } |
| |
| if (di->charge_disabled) |
| return; |
| |
| if (mA < 50) { |
| twl6030_stop_usb_charger(di); |
| return; |
| } |
| |
| usb_mux_force(true); |
| |
| twl6030_config_vichrg_reg(di, di->charger_outcurrent_mA); |
| twl6030_config_cinlimit_reg(di, mA); |
| twl6030_config_voreg_reg(di, di->platform_data->max_bat_voltage_mV); |
| twl6030_config_iterm_reg(di, di->platform_data->termination_current_mA); |
| |
| if (mA >= 50) { |
| twl6030_set_watchdog(di, di->watchdog_duration); |
| /* disable current termination, suspend mode, boost mode, etc */ |
| twl_i2c_write_u8(TWL6030_MODULE_CHARGER, 0, CHARGERUSB_CTRL1); |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, CONTROLLER_CTRL1_EN_CHARGER, CONTROLLER_CTRL1); |
| if (ret) |
| goto err; |
| } |
| |
| dev_warn(di->dev, "battery: CHARGER ON\n"); |
| return; |
| |
| err: |
| pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); |
| } |
| |
| /* |
| * Interrupt service routine |
| * |
| * Attends to TWL 6030 power module interruptions events, specifically |
| * USB_PRES (USB charger presence) CHG_PRES (AC charger presence) events |
| * |
| */ |
| static irqreturn_t twl6030_charger_ctrl_interrupt(int irq, void *_di) |
| { |
| struct twl6030_charger_device_info *di = _di; |
| queue_work(di->wq, &di->charge_control_work); |
| dev_warn(di->dev, "battery: CHARGE CTRL IRQ\n"); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t twl6030_charger_fault_interrupt(int irq, void *_di) |
| { |
| struct twl6030_charger_device_info *di = _di; |
| int ret; |
| |
| u8 usb_charge_sts, usb_charge_sts1, usb_charge_sts2; |
| |
| ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &usb_charge_sts, |
| CHARGERUSB_INT_STATUS); |
| if (ret) |
| goto err; |
| |
| ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &usb_charge_sts1, |
| CHARGERUSB_STATUS_INT1); |
| if (ret) |
| goto err; |
| |
| ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &usb_charge_sts2, |
| CHARGERUSB_STATUS_INT2); |
| if (ret) |
| goto err; |
| |
| dev_warn(di->dev, "battery: CHARGE FAULT IRQ: STS %02x INT1 %02x INT2 %02x\n", |
| usb_charge_sts, usb_charge_sts1, usb_charge_sts2); |
| err: |
| queue_work(di->wq, &di->charge_fault_work); |
| return IRQ_HANDLED; |
| } |
| |
| static enum power_supply_property twl6030_usb_props[] = { |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| }; |
| |
| #define to_twl6030_usb_device_info(x) container_of((x), \ |
| struct twl6030_charger_device_info, usb); |
| |
| static int twl6030_usb_get_property(struct power_supply *psy, |
| enum power_supply_property psp, union power_supply_propval *val) |
| { |
| struct twl6030_charger_device_info *di = to_twl6030_usb_device_info(psy); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| val->intval = di->vbus_online; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| val->intval = twl6030_get_gpadc_conversion(di, 10) * 1000; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int twl6030_usb_notifier_call(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| struct twl6030_charger_device_info *di = |
| container_of(nb, struct twl6030_charger_device_info, nb); |
| |
| di->usb_event = event; |
| switch (event) { |
| case USB_EVENT_VBUS: |
| di->usb_online = *((unsigned int *) data); |
| break; |
| case USB_EVENT_ENUMERATED: |
| di->usb_max_power = *((unsigned int *) data); |
| break; |
| case USB_EVENT_CHARGER: |
| case USB_EVENT_NONE: |
| break; |
| case USB_EVENT_ID: |
| default: |
| return NOTIFY_OK; |
| } |
| |
| if (di->usb_event != event) { |
| di->usb_event = event; |
| queue_work(di->wq, &di->charge_control_work); |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static void twl6030_determine_charge_state(struct twl6030_charger_device_info *di) |
| { |
| u8 stat1; |
| int newstate = STATE_BATTERY; |
| |
| /* TODO: i2c error -> fault? */ |
| twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &stat1, CONTROLLER_STAT1); |
| |
| /* printk("battery: determine_charge_state() stat1=%02x int1=%02x\n", stat1, int1); */ |
| |
| if (stat1 & VBUS_DET) { |
| /* dedicated charger detected by PHY? */ |
| if (di->usb_event == USB_EVENT_CHARGER) |
| newstate = STATE_AC; |
| else |
| newstate = STATE_USB; |
| |
| if (!di->vbus_online) { |
| di->vbus_online = 1; |
| wake_lock(&usb_wake_lock); |
| power_supply_changed(&di->usb); |
| } |
| } else { |
| /* ensure we don't have a stale USB_EVENT_CHARGER should detect bounce */ |
| di->usb_event = USB_EVENT_NONE; |
| |
| if (di->vbus_online) { |
| di->vbus_online = 0; |
| /* give USB and userspace some time to react before suspending */ |
| wake_lock_timeout(&usb_wake_lock, HZ / 2); |
| power_supply_changed(&di->usb); |
| } |
| } |
| |
| if (di->state == newstate) |
| return; |
| |
| switch (newstate) { |
| case STATE_FAULT: |
| case STATE_BATTERY: |
| if (is_charging(di)) |
| twl6030_stop_usb_charger(di); |
| break; |
| case STATE_USB: |
| case STATE_AC: |
| /* moving out of STATE_FULL should only happen on unplug |
| * or if we actually run down the battery capacity |
| */ |
| if (di->state == STATE_FULL) { |
| newstate = STATE_FULL; |
| break; |
| } |
| |
| /* TODO: high current? */ |
| if (!is_charging(di)) |
| twl6030_start_usb_charger(di, di->current_limit_mA); |
| break; |
| } |
| |
| if (di->state != newstate) { |
| dev_warn(di->dev, "battery: state %s -> %s\n", |
| twl6030_state[di->state], twl6030_state[newstate]); |
| di->state = newstate; |
| twl6030_eval_led_state(di); |
| } |
| } |
| |
| static void twl6030_charge_control_work(struct work_struct *work) |
| { |
| struct twl6030_charger_device_info *di = |
| container_of(work, struct twl6030_charger_device_info, charge_control_work); |
| |
| twl6030_determine_charge_state(di); |
| } |
| |
| static void twl6030_charge_fault_work(struct work_struct *work) |
| { |
| struct twl6030_charger_device_info *di = |
| container_of(work, struct twl6030_charger_device_info, charge_fault_work); |
| |
| if (is_charging(di)) |
| twl6030_start_usb_charger(di, di->current_limit_mA); |
| |
| msleep(10); |
| |
| twl6030_determine_charge_state(di); |
| } |
| |
| static void twl6030_monitor_work(struct work_struct *work) |
| { |
| struct twl6030_charger_device_info *di = container_of(work, |
| struct twl6030_charger_device_info, monitor_work.work); |
| |
| struct power_supply *battery; |
| union power_supply_propval val; |
| struct timespec ts; |
| |
| int capacity; |
| int raw_capacity; |
| int voltage_uV; |
| int current_uA; |
| int temperature_cC; |
| int current_limit_mA; |
| int qpassed_mAh; |
| int charging; |
| int ret; |
| |
| /* pet the charger watchdog */ |
| if (is_charging(di)) |
| twl6030_set_watchdog(di, di->watchdog_duration); |
| |
| queue_delayed_work(di->wq, &di->monitor_work, di->monitor_interval_jiffies); |
| |
| battery = get_battery_power_supply(di); |
| if (!battery) { |
| dev_err(di->dev, "Failed to get battery power supply supplicant for charger\n"); |
| goto error; |
| } |
| |
| if (!battery->get_property) { |
| dev_err(di->dev, "bettery power supply has null get_property method\n"); |
| goto error; |
| } |
| |
| ret = battery->get_property(battery, POWER_SUPPLY_PROP_CAPACITY, &val); |
| if (ret) { |
| dev_err(di->dev, "Failed to read battery capacity: %d\n", ret); |
| goto error; |
| } |
| capacity = val.intval; |
| |
| ret = battery->get_property(battery, POWER_SUPPLY_PROP_CAPACITY_LEVEL, &val); |
| if (ret) { |
| dev_err(di->dev, "Failed to read battery raw capacity: %d\n", ret); |
| goto error; |
| } |
| raw_capacity = val.intval; |
| |
| ret = battery->get_property(battery, POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); |
| if (ret) { |
| dev_err(di->dev, "Failed to read battery voltage: %d\n", ret); |
| voltage_uV = 0; |
| } else { |
| voltage_uV = val.intval; |
| } |
| |
| ret = battery->get_property(battery, POWER_SUPPLY_PROP_CURRENT_NOW, &val); |
| if (ret) { |
| dev_err(di->dev, "Failed to read battery current: %d\n", ret); |
| current_uA = 0; |
| } else { |
| current_uA = val.intval; |
| } |
| |
| ret = battery->get_property(battery, POWER_SUPPLY_PROP_TEMP, &val); |
| if (ret) { |
| dev_warn(di->dev, "Failed to read battery temperature: %d\n", ret); |
| temperature_cC = 0; |
| } else { |
| temperature_cC = val.intval; |
| } |
| |
| ret = battery->get_property(battery, |
| POWER_SUPPLY_PROP_CHARGE_COUNTER, &val); |
| if (ret) { |
| dev_warn(di->dev, "Failed to read charge counter: %d\n", ret); |
| qpassed_mAh = 0; |
| } else { |
| qpassed_mAh = val.intval; |
| } |
| |
| #if defined(CONFIG_MACH_NOTLE) |
| if (!notle_version_support_battery_temperature()) { |
| // Use a default temp of 275 for 500mA charging on EVT1 |
| temperature_cC = 275; |
| } |
| #endif |
| |
| /* store values in device info */ |
| di->voltage_uV = voltage_uV; |
| di->current_uA = current_uA; |
| di->temperature_cC = temperature_cC; |
| |
| /* manage temperature constraints */ |
| current_limit_mA = di->current_limit_mA; |
| charging = is_charging(di); |
| |
| if (temperature_cC < 100 || temperature_cC > 450 || di->charge_disabled) { |
| di->state = STATE_FAULT; |
| di->current_limit_mA = 0; |
| #if 0 |
| } else if (temperature_cC < 150) { |
| di->current_limit_mA = 169; |
| } else if (temperature_cC < 250) { |
| di->current_limit_mA = 282; |
| #endif |
| } else { |
| di->current_limit_mA = 500; |
| } |
| |
| if (charging && current_limit_mA != di->current_limit_mA) { |
| // update current setting, 0 will stop charging |
| twl6030_start_usb_charger(di, di->current_limit_mA); |
| twl6030_eval_led_state(di); |
| } |
| |
| /* with charging state updated, handle hard poweroff points */ |
| if (temperature_cC < -100 || temperature_cC > 600) { |
| dev_err(di->dev, "Battery temperature critical! temp=%d\n", temperature_cC); |
| di->temperature_critical++; |
| |
| // shut down the device if we get two successive critical readings |
| // this give the device time to log the critical temperature message |
| if (di->temperature_critical >= 2) { |
| dev_err(di->dev, "Powering down!\n"); |
| kernel_power_off(); |
| } |
| } else { |
| // reset counter if we get a non-critical reading |
| di->temperature_critical = 0; |
| } |
| |
| if (is_charging(di) && capacity == 100 && current_uA < 50000) { |
| if (!di->charge_top_off) { |
| di->full_jiffies = msecs_to_jiffies(120 * 1000) + jiffies; |
| di->charge_top_off = 1; |
| } else if (time_after_eq(jiffies, di->full_jiffies)) { |
| di->charge_top_off = 0; |
| di->state = STATE_FULL; |
| twl6030_stop_usb_charger(di); |
| twl6030_eval_led_state(di); |
| } |
| } |
| |
| if (di->state == STATE_FULL && capacity <= di->recharge_capacity) { |
| dev_warn(di->dev, "battery: drained from full to %d%%, charging again\n", capacity); |
| di->state = STATE_USB; |
| twl6030_start_usb_charger(di, di->current_limit_mA); |
| twl6030_eval_led_state(di); |
| } |
| |
| getnstimeofday(&ts); |
| dev_warn(di->dev, |
| "capacity=%d%% raw_capacity=%d%% " |
| "voltage_uV=%d uV current_uA=%d uA " |
| "temperature_cC=%d current_limit_mA=%d " |
| "qpassed=%d mAh %s%s [%016lu]\n", |
| capacity, raw_capacity, |
| voltage_uV, current_uA, |
| temperature_cC, di->current_limit_mA, |
| qpassed_mAh, twl6030_state[di->state], |
| is_charging(di) ? " CHG" : "", (unsigned long) ts.tv_sec); |
| error: |
| twl6030_determine_charge_state(di); |
| } |
| |
| static ssize_t set_led_high(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| long val; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| if ((strict_strtol(buf, 10, &val) < 0) || (val < 0) |
| || (val > 0xff)) |
| return -EINVAL; |
| di->led.led_high = val; |
| |
| return status; |
| } |
| |
| static ssize_t show_led_high(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%d\n", di->led.led_high); |
| } |
| |
| static ssize_t set_led_low(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| long val; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| if ((strict_strtol(buf, 10, &val) < 0) || (val < 0) |
| || (val > 0xff)) |
| return -EINVAL; |
| di->led.led_low = val; |
| |
| return status; |
| } |
| |
| static ssize_t show_led_low(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%d\n", di->led.led_low); |
| } |
| |
| static ssize_t show_vbus_voltage(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = twl6030_get_gpadc_conversion(di, 10); |
| |
| return sprintf(buf, "%d\n", val); |
| } |
| |
| static ssize_t show_id_level(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = twl6030_get_gpadc_conversion(di, 14); |
| |
| return sprintf(buf, "%d\n", val); |
| } |
| |
| static ssize_t set_regulation_voltage(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| long val; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| if ((strict_strtol(buf, 10, &val) < 0) || (val < 3500) |
| || (val > di->platform_data->max_charger_voltage_mV)) |
| return -EINVAL; |
| di->platform_data->max_bat_voltage_mV = val; |
| twl6030_config_voreg_reg(di, val); |
| |
| return status; |
| } |
| |
| static ssize_t show_regulation_voltage(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = di->platform_data->max_bat_voltage_mV; |
| return sprintf(buf, "%u\n", val); |
| } |
| |
| static ssize_t set_termination_current(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| long val; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| if ((strict_strtol(buf, 10, &val) < 0) || (val < 50) || (val > 400)) |
| return -EINVAL; |
| di->platform_data->termination_current_mA = val; |
| twl6030_config_iterm_reg(di, val); |
| |
| return status; |
| } |
| |
| static ssize_t show_termination_current(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = di->platform_data->termination_current_mA; |
| return sprintf(buf, "%u\n", val); |
| } |
| |
| static ssize_t set_cin_limit(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| long val; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| if ((strict_strtol(buf, 10, &val) < 0) || (val < 50) || (val > 1500)) |
| return -EINVAL; |
| di->charger_incurrent_mA = val; |
| twl6030_config_cinlimit_reg(di, val); |
| |
| return status; |
| } |
| |
| static ssize_t show_cin_limit(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| unsigned int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = di->charger_incurrent_mA; |
| return sprintf(buf, "%u\n", val); |
| } |
| |
| static ssize_t set_charge_current(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| long val; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| if ((strict_strtol(buf, 10, &val) < 0) || (val < 300) |
| || (val > di->platform_data->max_charger_current_mA)) |
| return -EINVAL; |
| di->charger_outcurrent_mA = val; |
| twl6030_config_vichrg_reg(di, val); |
| |
| return status; |
| } |
| |
| static ssize_t show_charge_current(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = di->charger_outcurrent_mA; |
| return sprintf(buf, "%u\n", val); |
| } |
| |
| static ssize_t set_min_vbus(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long val; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| if ((strict_strtol(buf, 10, &val) < 0) || (val < 4200) || (val > 4760)) |
| return -EINVAL; |
| di->min_vbus = val; |
| twl6030_config_min_vbus_reg(di, val); |
| |
| return status; |
| } |
| |
| static ssize_t show_min_vbus(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| unsigned int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = di->min_vbus; |
| return sprintf(buf, "%u\n", val); |
| } |
| |
| static ssize_t set_recharge_capacity(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long val; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| if ((strict_strtol(buf, 10, &val) < 0) || (val < 0) || (val > 100)) |
| return -EINVAL; |
| di->recharge_capacity = val; |
| |
| return status; |
| } |
| |
| static ssize_t show_recharge_capacity(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| unsigned int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = di->recharge_capacity; |
| return sprintf(buf, "%u\n", val); |
| } |
| |
| static ssize_t set_charge_disabled(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long val; |
| int charge_disabled; |
| int status = count; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| charge_disabled = di->charge_disabled; |
| |
| if (strict_strtol(buf, 10, &val) < 0) |
| return -EINVAL; |
| |
| di->charge_disabled = !!val; |
| |
| if (di->charge_disabled != charge_disabled) { |
| if (di->charge_disabled) { |
| di->state = STATE_FAULT; |
| twl6030_stop_usb_charger(di); |
| twl6030_eval_led_state(di); |
| } else { |
| queue_delayed_work(di->wq, &di->monitor_work, 0); |
| } |
| } |
| |
| return status; |
| } |
| |
| static ssize_t show_charge_disabled(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| unsigned int val; |
| struct twl6030_charger_device_info *di = dev_get_drvdata(dev); |
| |
| val = di->charge_disabled; |
| return sprintf(buf, "%u\n", !!val); |
| } |
| |
| static DEVICE_ATTR(vbus_voltage, S_IRUGO, show_vbus_voltage, NULL); |
| static DEVICE_ATTR(id_level, S_IRUGO, show_id_level, NULL); |
| static DEVICE_ATTR(regulation_voltage, S_IWUSR | S_IRUGO, |
| show_regulation_voltage, set_regulation_voltage); |
| static DEVICE_ATTR(termination_current, S_IWUSR | S_IRUGO, |
| show_termination_current, set_termination_current); |
| static DEVICE_ATTR(cin_limit, S_IWUSR | S_IRUGO, show_cin_limit, |
| set_cin_limit); |
| static DEVICE_ATTR(charge_current, S_IWUSR | S_IRUGO, show_charge_current, |
| set_charge_current); |
| static DEVICE_ATTR(min_vbus, S_IWUSR | S_IRUGO, show_min_vbus, set_min_vbus); |
| static DEVICE_ATTR(recharge_capacity, S_IWUSR | S_IRUGO, show_recharge_capacity, |
| set_recharge_capacity); |
| static DEVICE_ATTR(led_low, S_IWUSR | S_IRUGO, show_led_low, set_led_low); |
| static DEVICE_ATTR(led_high, S_IWUSR | S_IRUGO, show_led_high, set_led_high); |
| static DEVICE_ATTR(charge_disabled, S_IWUSR | S_IRUGO, show_charge_disabled, |
| set_charge_disabled); |
| |
| static struct attribute *twl6030_charger_attributes[] = { |
| &dev_attr_vbus_voltage.attr, |
| &dev_attr_id_level.attr, |
| &dev_attr_regulation_voltage.attr, |
| &dev_attr_termination_current.attr, |
| &dev_attr_cin_limit.attr, |
| &dev_attr_charge_current.attr, |
| &dev_attr_min_vbus.attr, |
| &dev_attr_recharge_capacity.attr, |
| &dev_attr_led_low.attr, |
| &dev_attr_led_high.attr, |
| &dev_attr_charge_disabled.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group twl6030_charger_attr_group = { |
| .attrs = twl6030_charger_attributes, |
| }; |
| |
| static int __devinit twl6030_charger_probe(struct platform_device *pdev) |
| { |
| struct twl4030_charger_platform_data *pdata = pdev->dev.platform_data; |
| struct twl6030_charger_device_info *di; |
| int irq = -1; |
| int ret; |
| |
| dev_warn(&pdev->dev, "enter\n"); |
| |
| if (!pdata) { |
| dev_dbg(&pdev->dev, "platform_data not available\n"); |
| return -EINVAL; |
| } |
| |
| if (!pdata->supplied_to || pdata->num_supplicants < 1) { |
| dev_err(&pdev->dev, "a supplicant must be sepcified; " |
| "supplied_to=%p num=%zu\n", pdata->supplied_to, |
| pdata->num_supplicants); |
| return -EINVAL; |
| } |
| |
| di = kzalloc(sizeof(*di), GFP_KERNEL); |
| if (!di) |
| return -ENOMEM; |
| |
| di->platform_data = kmemdup(pdata, sizeof(*pdata), GFP_KERNEL); |
| if (!di->platform_data) { |
| kfree(di); |
| return -ENOMEM; |
| } |
| |
| di->dev = &pdev->dev; |
| di->state = STATE_BATTERY; |
| di->charge_top_off = 0; |
| di->recharge_capacity = 94; |
| di->monitor_interval_jiffies = |
| msecs_to_jiffies(pdata->monitor_interval_seconds * 1000); |
| |
| di->usb.name = "twl6030_usb"; |
| di->usb.type = POWER_SUPPLY_TYPE_USB; |
| di->usb.properties = twl6030_usb_props; |
| di->usb.num_properties = ARRAY_SIZE(twl6030_usb_props); |
| di->usb.get_property = twl6030_usb_get_property; |
| di->usb.supplied_to = pdata->supplied_to; |
| di->usb.num_supplicants = pdata->num_supplicants; |
| |
| if (pdata->num_supplicants > 1) { |
| dev_warn(&pdev->dev, "more than one supplicant specified (%zu), only the " |
| "first will be used\n", pdata->num_supplicants); |
| } |
| |
| /* |
| * try to cache the battery power supply now. if it hasn't registered |
| * yet we will cache it next opportunity |
| */ |
| get_battery_power_supply(di); |
| |
| platform_set_drvdata(pdev, di); |
| |
| wake_lock_init(&usb_wake_lock, WAKE_LOCK_SUSPEND, "usb_wake_lock"); |
| |
| di->wq = create_freezable_workqueue(dev_name(&pdev->dev)); |
| |
| ret = power_supply_register(&pdev->dev, &di->usb); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to register usb power supply\n"); |
| goto usb_failed; |
| } |
| |
| INIT_WORK(&di->led_work, twl6030_led_work); |
| INIT_WORK(&di->charge_control_work, twl6030_charge_control_work); |
| INIT_WORK(&di->charge_fault_work, twl6030_charge_fault_work); |
| INIT_DELAYED_WORK_DEFERRABLE(&di->monitor_work, twl6030_monitor_work); |
| |
| /* initialize for USB charging */ |
| twl6030_config_limit1_reg(di, pdata->max_charger_voltage_mV); |
| twl6030_config_limit2_reg(di, pdata->max_charger_current_mA); |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, MBAT_TEMP, |
| CONTROLLER_INT_MASK); |
| if (ret) |
| goto init_failed; |
| |
| ret = twl_i2c_write_u8(TWL6030_MODULE_CHARGER, |
| MASK_MCHARGERUSB_THMREG | MASK_MCURRENT_TERM, |
| CHARGERUSB_INT_MASK); |
| if (ret) |
| goto init_failed; |
| |
| |
| di->charger_outcurrent_mA = di->platform_data->max_charger_current_mA; |
| |
| twl6030_set_watchdog(di, 32); |
| |
| di->nb.notifier_call = twl6030_usb_notifier_call; |
| di->otg = otg_get_transceiver(); |
| if (di->otg) { |
| ret = otg_register_notifier(di->otg, &di->nb); |
| if (ret) |
| dev_err(&pdev->dev, "otg register notifier failed %d\n", ret); |
| } else |
| dev_err(&pdev->dev, "otg_get_transceiver failed %d\n", ret); |
| |
| di->charger_incurrent_mA = twl6030_get_usb_max_power(di->otg); |
| if (di->charger_incurrent_mA < 500) |
| di->current_limit_mA = di->charger_incurrent_mA; |
| else |
| di->current_limit_mA = 500; |
| |
| /* request charger fault interrupt */ |
| irq = platform_get_irq(pdev, 1); |
| ret = request_threaded_irq(irq, NULL, twl6030_charger_fault_interrupt, |
| 0, "twl_charger_fault", di); |
| if (ret) { |
| dev_err(&pdev->dev, "could not request irq %d, status %d\n", irq, ret); |
| goto init_failed; |
| } |
| |
| /* request charger ctrl interrupt */ |
| irq = platform_get_irq(pdev, 0); |
| ret = request_threaded_irq(irq, NULL, twl6030_charger_ctrl_interrupt, |
| 0, "twl_charger_ctrl", di); |
| if (ret) { |
| dev_err(&pdev->dev, "could not request irq %d, status %d\n", irq, ret); |
| goto chg_irq_fail; |
| } |
| |
| twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, REG_INT_MSK_LINE_C); |
| twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, REG_INT_MSK_STS_C); |
| twl6030_interrupt_unmask(TWL6030_CHARGER_FAULT_INT_MASK, REG_INT_MSK_LINE_C); |
| twl6030_interrupt_unmask(TWL6030_CHARGER_FAULT_INT_MASK, REG_INT_MSK_STS_C); |
| |
| ret = sysfs_create_group(&pdev->dev.kobj, &twl6030_charger_attr_group); |
| if (ret) |
| dev_err(&pdev->dev, "could not create sysfs files\n"); |
| |
| /* setup the led state */ |
| twl6030_init_led_state(di); |
| |
| queue_delayed_work(di->wq, &di->monitor_work, 0); |
| |
| dev_warn(&pdev->dev, "exit\n"); |
| return 0; |
| |
| /* TODO: fix fail exit mess */ |
| chg_irq_fail: |
| irq = platform_get_irq(pdev, 1); |
| free_irq(irq, di); |
| |
| init_failed: |
| power_supply_unregister(&di->usb); |
| |
| usb_failed: |
| if (irq != -1) |
| free_irq(irq, di); |
| |
| wake_lock_destroy(&usb_wake_lock); |
| platform_set_drvdata(pdev, NULL); |
| kfree(di); |
| |
| dev_warn(di->dev, "exit with error: %d\n", ret); |
| return ret; |
| } |
| |
| #define twl6030_charger_suspend NULL |
| #define twl6030_charger_resume NULL |
| |
| static const struct dev_pm_ops pm_ops = { |
| .suspend = twl6030_charger_suspend, |
| .resume = twl6030_charger_resume, |
| }; |
| |
| static struct platform_driver twl6030_charger_driver = { |
| .probe = twl6030_charger_probe, |
| .driver = { |
| .name = "twl6030_charger", |
| .pm = &pm_ops, |
| }, |
| }; |
| |
| static int __init twl6030_charger_init(void) |
| { |
| return platform_driver_register(&twl6030_charger_driver); |
| } |
| module_init(twl6030_charger_init); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:twl6030_charger"); |