diff options
Diffstat (limited to 'drivers/power/abx500_chargalg.c')
| -rw-r--r-- | drivers/power/abx500_chargalg.c | 453 | 
1 files changed, 320 insertions, 133 deletions
diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c index f043c0851a7..9863e423602 100644 --- a/drivers/power/abx500_chargalg.c +++ b/drivers/power/abx500_chargalg.c @@ -1,5 +1,6 @@  /*   * Copyright (C) ST-Ericsson SA 2012 + * Copyright (c) 2012 Sony Mobile Communications AB   *   * Charging algorithm driver for abx500 variants   * @@ -8,11 +9,13 @@   *	Johan Palsson <johan.palsson@stericsson.com>   *	Karl Komierowski <karl.komierowski@stericsson.com>   *	Arun R Murthy <arun.murthy@stericsson.com> + *	Author: Imre Sunyi <imre.sunyi@sonymobile.com>   */  #include <linux/init.h>  #include <linux/module.h>  #include <linux/device.h> +#include <linux/hrtimer.h>  #include <linux/interrupt.h>  #include <linux/delay.h>  #include <linux/slab.h> @@ -24,8 +27,10 @@  #include <linux/of.h>  #include <linux/mfd/core.h>  #include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h>  #include <linux/mfd/abx500/ux500_chargalg.h>  #include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/notifier.h>  /* Watchdog kick interval */  #define CHG_WD_INTERVAL			(6 * HZ) @@ -33,6 +38,18 @@  /* End-of-charge criteria counter */  #define EOC_COND_CNT			10 +/* One hour expressed in seconds */ +#define ONE_HOUR_IN_SECONDS            3600 + +/* Five minutes expressed in seconds */ +#define FIVE_MINUTES_IN_SECONDS        300 + +/* Plus margin for the low battery threshold */ +#define BAT_PLUS_MARGIN                (100) + +#define CHARGALG_CURR_STEP_LOW		0 +#define CHARGALG_CURR_STEP_HIGH	100 +  #define to_abx500_chargalg_device_info(x) container_of((x), \  	struct abx500_chargalg, chargalg_psy); @@ -66,6 +83,11 @@ struct abx500_chargalg_suspension_status {  	bool usb_suspended;  }; +struct abx500_chargalg_current_step_status { +	bool curr_step_change; +	int curr_step; +}; +  struct abx500_chargalg_battery_data {  	int temp;  	int volt; @@ -82,6 +104,7 @@ enum abx500_chargalg_states {  	STATE_HW_TEMP_PROTECT_INIT,  	STATE_HW_TEMP_PROTECT,  	STATE_NORMAL_INIT, +	STATE_USB_PP_PRE_CHARGE,  	STATE_NORMAL,  	STATE_WAIT_FOR_RECHARGE_INIT,  	STATE_WAIT_FOR_RECHARGE, @@ -113,6 +136,7 @@ static const char *states[] = {  	"HW_TEMP_PROTECT_INIT",  	"HW_TEMP_PROTECT",  	"NORMAL_INIT", +	"USB_PP_PRE_CHARGE",  	"NORMAL",  	"WAIT_FOR_RECHARGE_INIT",  	"WAIT_FOR_RECHARGE", @@ -204,6 +228,8 @@ enum maxim_ret {   * @batt_data:		data of the battery   * @susp_status:	current charger suspension status   * @bm:           	Platform specific battery management information + * @curr_status:	Current step status for over-current protection + * @parent:		pointer to the struct abx500   * @chargalg_psy:	structure that holds the battery properties exposed by   *			the charging algorithm   * @events:		structure for information about events triggered @@ -227,6 +253,8 @@ struct abx500_chargalg {  	struct abx500_chargalg_charger_info chg_info;  	struct abx500_chargalg_battery_data batt_data;  	struct abx500_chargalg_suspension_status susp_status; +	struct ab8500 *parent; +	struct abx500_chargalg_current_step_status curr_status;  	struct abx500_bm_data *bm;  	struct power_supply chargalg_psy;  	struct ux500_charger *ac_chg; @@ -236,51 +264,69 @@ struct abx500_chargalg {  	struct delayed_work chargalg_periodic_work;  	struct delayed_work chargalg_wd_work;  	struct work_struct chargalg_work; -	struct timer_list safety_timer; -	struct timer_list maintenance_timer; +	struct hrtimer safety_timer; +	struct hrtimer maintenance_timer;  	struct kobject chargalg_kobject;  }; +/*External charger prepare notifier*/ +BLOCKING_NOTIFIER_HEAD(charger_notifier_list); +  /* Main battery properties */  static enum power_supply_property abx500_chargalg_props[] = {  	POWER_SUPPLY_PROP_STATUS,  	POWER_SUPPLY_PROP_HEALTH,  }; +struct abx500_chargalg_sysfs_entry { +	struct attribute attr; +	ssize_t (*show)(struct abx500_chargalg *, char *); +	ssize_t (*store)(struct abx500_chargalg *, const char *, size_t); +}; +  /**   * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer - * @data:	pointer to the abx500_chargalg structure + * @timer:     pointer to the hrtimer structure   *   * This function gets called when the safety timer for the charger   * expires   */ -static void abx500_chargalg_safety_timer_expired(unsigned long data) +static enum hrtimer_restart +abx500_chargalg_safety_timer_expired(struct hrtimer *timer)  { -	struct abx500_chargalg *di = (struct abx500_chargalg *) data; +	struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg, +						  safety_timer);  	dev_err(di->dev, "Safety timer expired\n");  	di->events.safety_timer_expired = true;  	/* Trigger execution of the algorithm instantly */  	queue_work(di->chargalg_wq, &di->chargalg_work); + +	return HRTIMER_NORESTART;  }  /**   * abx500_chargalg_maintenance_timer_expired() - Expiration of   * the maintenance timer - * @i:		pointer to the abx500_chargalg structure + * @timer:     pointer to the timer structure   *   * This function gets called when the maintenence timer   * expires   */ -static void abx500_chargalg_maintenance_timer_expired(unsigned long data) +static enum hrtimer_restart +abx500_chargalg_maintenance_timer_expired(struct hrtimer *timer)  { -	struct abx500_chargalg *di = (struct abx500_chargalg *) data; +	struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg, +						  maintenance_timer); +  	dev_dbg(di->dev, "Maintenance timer expired\n");  	di->events.maintenance_timer_expired = true;  	/* Trigger execution of the algorithm instantly */  	queue_work(di->chargalg_wq, &di->chargalg_work); + +	return HRTIMER_NORESTART;  }  /** @@ -303,6 +349,30 @@ static void abx500_chargalg_state_to(struct abx500_chargalg *di,  	di->charge_state = state;  } +static int abx500_chargalg_check_charger_enable(struct abx500_chargalg *di) +{ +	switch (di->charge_state) { +	case STATE_NORMAL: +	case STATE_MAINTENANCE_A: +	case STATE_MAINTENANCE_B: +		break; +	default: +		return 0; +	} + +	if (di->chg_info.charger_type & USB_CHG) { +		return di->usb_chg->ops.check_enable(di->usb_chg, +                         di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, +                         di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); +	} else if ((di->chg_info.charger_type & AC_CHG) && +		   !(di->ac_chg->external)) { +		return di->ac_chg->ops.check_enable(di->ac_chg, +                         di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, +                         di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); +	} +	return 0; +} +  /**   * abx500_chargalg_check_charger_connection() - Check charger connection change   * @di:		pointer to the abx500_chargalg structure @@ -348,6 +418,22 @@ static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)  }  /** + * abx500_chargalg_check_current_step_status() - Check charging current + * step status. + * @di:		pointer to the abx500_chargalg structure + * + * This function will check if there is a change in the charging current step + * and change charge state accordingly. + */ +static void abx500_chargalg_check_current_step_status +	(struct abx500_chargalg *di) +{ +	if (di->curr_status.curr_step_change) +		abx500_chargalg_state_to(di, STATE_NORMAL_INIT); +	di->curr_status.curr_step_change = false; +} + +/**   * abx500_chargalg_start_safety_timer() - Start charging safety timer   * @di:		pointer to the abx500_chargalg structure   * @@ -356,19 +442,16 @@ static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)   */  static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)  { -	unsigned long timer_expiration = 0; +	/* Charger-dependent expiration time in hours*/ +	int timer_expiration = 0;  	switch (di->chg_info.charger_type) {  	case AC_CHG: -		timer_expiration = -		round_jiffies(jiffies + -			(di->bm->main_safety_tmr_h * 3600 * HZ)); +		timer_expiration = di->bm->main_safety_tmr_h;  		break;  	case USB_CHG: -		timer_expiration = -		round_jiffies(jiffies + -			(di->bm->usb_safety_tmr_h * 3600 * HZ)); +		timer_expiration = di->bm->usb_safety_tmr_h;  		break;  	default: @@ -377,11 +460,10 @@ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)  	}  	di->events.safety_timer_expired = false; -	di->safety_timer.expires = timer_expiration; -	if (!timer_pending(&di->safety_timer)) -		add_timer(&di->safety_timer); -	else -		mod_timer(&di->safety_timer, timer_expiration); +	hrtimer_set_expires_range(&di->safety_timer, +		ktime_set(timer_expiration * ONE_HOUR_IN_SECONDS, 0), +		ktime_set(FIVE_MINUTES_IN_SECONDS, 0)); +	hrtimer_start_expires(&di->safety_timer, HRTIMER_MODE_REL);  }  /** @@ -392,8 +474,8 @@ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)   */  static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)  { -	di->events.safety_timer_expired = false; -	del_timer(&di->safety_timer); +	if (hrtimer_try_to_cancel(&di->safety_timer) >= 0) +		di->events.safety_timer_expired = false;  }  /** @@ -408,17 +490,11 @@ static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)  static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,  	int duration)  { -	unsigned long timer_expiration; - -	/* Convert from hours to jiffies */ -	timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ)); - +	hrtimer_set_expires_range(&di->maintenance_timer, +		ktime_set(duration * ONE_HOUR_IN_SECONDS, 0), +		ktime_set(FIVE_MINUTES_IN_SECONDS, 0));  	di->events.maintenance_timer_expired = false; -	di->maintenance_timer.expires = timer_expiration; -	if (!timer_pending(&di->maintenance_timer)) -		add_timer(&di->maintenance_timer); -	else -		mod_timer(&di->maintenance_timer, timer_expiration); +	hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL);  }  /** @@ -430,8 +506,8 @@ static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,   */  static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di)  { -	di->events.maintenance_timer_expired = false; -	del_timer(&di->maintenance_timer); +	if (hrtimer_try_to_cancel(&di->maintenance_timer) >= 0) +		di->events.maintenance_timer_expired = false;  }  /** @@ -477,6 +553,8 @@ static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di)  static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,  	int vset, int iset)  { +	static int abx500_chargalg_ex_ac_enable_toggle; +  	if (!di->ac_chg || !di->ac_chg->ops.enable)  		return -ENXIO; @@ -489,6 +567,14 @@ static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,  	di->chg_info.ac_iset = iset;  	di->chg_info.ac_vset = vset; +	/* Enable external charger */ +	if (enable && di->ac_chg->external && +	    !abx500_chargalg_ex_ac_enable_toggle) { +		blocking_notifier_call_chain(&charger_notifier_list, +					     0, di->dev); +		abx500_chargalg_ex_ac_enable_toggle++; +	} +  	return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset);  } @@ -520,6 +606,37 @@ static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable,  	return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset);  } + /** + * ab8540_chargalg_usb_pp_en() - Enable/ disable USB power path + * @di:                pointer to the abx500_chargalg structure + * @enable:    power path enable/disable + * + * The USB power path will be enable/ disable + */ +static int ab8540_chargalg_usb_pp_en(struct abx500_chargalg *di, bool enable) +{ +	if (!di->usb_chg || !di->usb_chg->ops.pp_enable) +		return -ENXIO; + +	return di->usb_chg->ops.pp_enable(di->usb_chg, enable); +} + +/** + * ab8540_chargalg_usb_pre_chg_en() - Enable/ disable USB pre-charge + * @di:                pointer to the abx500_chargalg structure + * @enable:    USB pre-charge enable/disable + * + * The USB USB pre-charge will be enable/ disable + */ +static int ab8540_chargalg_usb_pre_chg_en(struct abx500_chargalg *di, +					  bool enable) +{ +	if (!di->usb_chg || !di->usb_chg->ops.pre_chg_enable) +		return -ENXIO; + +	return di->usb_chg->ops.pre_chg_enable(di->usb_chg, enable); +} +  /**   * abx500_chargalg_update_chg_curr() - Update charger current   * @di:		pointer to the abx500_chargalg structure @@ -613,8 +730,6 @@ static void abx500_chargalg_hold_charging(struct abx500_chargalg *di)  static void abx500_chargalg_start_charging(struct abx500_chargalg *di,  	int vset, int iset)  { -	bool start_chargalg_wd = true; -  	switch (di->chg_info.charger_type) {  	case AC_CHG:  		dev_dbg(di->dev, @@ -632,12 +747,8 @@ static void abx500_chargalg_start_charging(struct abx500_chargalg *di,  	default:  		dev_err(di->dev, "Unknown charger to charge from\n"); -		start_chargalg_wd = false;  		break;  	} - -	if (start_chargalg_wd && !delayed_work_pending(&di->chargalg_wd_work)) -		queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0);  }  /** @@ -725,6 +836,9 @@ static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di)  		di->batt_data.avg_curr > 0) {  		if (++di->eoc_cnt >= EOC_COND_CNT) {  			di->eoc_cnt = 0; +			if ((di->chg_info.charger_type & USB_CHG) && +			   (di->usb_chg->power_path)) +				ab8540_chargalg_usb_pp_en(di, true);  			di->charge_status = POWER_SUPPLY_STATUS_FULL;  			di->maintenance_chg = true;  			dev_dbg(di->dev, "EOC reached!\n"); @@ -1217,6 +1331,8 @@ static void abx500_chargalg_external_power_changed(struct power_supply *psy)  static void abx500_chargalg_algorithm(struct abx500_chargalg *di)  {  	int charger_status; +	int ret; +	int curr_step_lvl;  	/* Collect data from all power_supply class devices */  	class_for_each_device(power_supply_class, NULL, @@ -1227,6 +1343,15 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)  	abx500_chargalg_check_charger_voltage(di);  	charger_status = abx500_chargalg_check_charger_connection(di); +	abx500_chargalg_check_current_step_status(di); + +	if (is_ab8500(di->parent)) { +		ret = abx500_chargalg_check_charger_enable(di); +		if (ret < 0) +			dev_err(di->dev, "Checking charger is enabled error" +					": Returned Value %d\n", ret); +	} +  	/*  	 * First check if we have a charger connected.  	 * Also we don't allow charging of unknown batteries if configured @@ -1416,9 +1541,34 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)  		break;  	case STATE_NORMAL_INIT: -		abx500_chargalg_start_charging(di, -			di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, -			di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); +		if ((di->chg_info.charger_type & USB_CHG) && +				di->usb_chg->power_path) { +			if (di->batt_data.volt > +			    (di->bm->fg_params->lowbat_threshold + +			     BAT_PLUS_MARGIN)) { +				ab8540_chargalg_usb_pre_chg_en(di, false); +				ab8540_chargalg_usb_pp_en(di, false); +			} else { +				ab8540_chargalg_usb_pp_en(di, true); +				ab8540_chargalg_usb_pre_chg_en(di, true); +				abx500_chargalg_state_to(di, +					STATE_USB_PP_PRE_CHARGE); +				break; +			} +		} + +		if (di->curr_status.curr_step == CHARGALG_CURR_STEP_LOW) +			abx500_chargalg_stop_charging(di); +		else { +			curr_step_lvl = di->bm->bat_type[ +				di->bm->batt_id].normal_cur_lvl +				* di->curr_status.curr_step +				/ CHARGALG_CURR_STEP_HIGH; +			abx500_chargalg_start_charging(di, +				di->bm->bat_type[di->bm->batt_id] +				.normal_vol_lvl, curr_step_lvl); +		} +  		abx500_chargalg_state_to(di, STATE_NORMAL);  		abx500_chargalg_start_safety_timer(di);  		abx500_chargalg_stop_maintenance_timer(di); @@ -1430,6 +1580,13 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)  		break; +	case STATE_USB_PP_PRE_CHARGE: +		if (di->batt_data.volt > +			(di->bm->fg_params->lowbat_threshold + +			BAT_PLUS_MARGIN)) +			abx500_chargalg_state_to(di, STATE_NORMAL_INIT); +		break; +  	case STATE_NORMAL:  		handle_maxim_chg_curr(di);  		if (di->charge_status == POWER_SUPPLY_STATUS_FULL && @@ -1653,99 +1810,134 @@ static int abx500_chargalg_get_property(struct power_supply *psy,  /* Exposure to the sysfs interface */ -/** - * abx500_chargalg_sysfs_show() - sysfs show operations - * @kobj:      pointer to the struct kobject - * @attr:      pointer to the struct attribute - * @buf:       buffer that holds the parameter to send to userspace - * - * Returns a buffer to be displayed in user space - */ -static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj, -					  struct attribute *attr, char *buf) +static ssize_t abx500_chargalg_curr_step_show(struct abx500_chargalg *di, +					      char *buf)  { -	struct abx500_chargalg *di = container_of(kobj, -               struct abx500_chargalg, chargalg_kobject); +	return sprintf(buf, "%d\n", di->curr_status.curr_step); +} + +static ssize_t abx500_chargalg_curr_step_store(struct abx500_chargalg *di, +					       const char *buf, size_t length) +{ +	long int param; +	int ret; + +	ret = kstrtol(buf, 10, ¶m); +	if (ret < 0) +		return ret; +	di->curr_status.curr_step = param; +	if (di->curr_status.curr_step >= CHARGALG_CURR_STEP_LOW && +		di->curr_status.curr_step <= CHARGALG_CURR_STEP_HIGH) { +		di->curr_status.curr_step_change = true; +		queue_work(di->chargalg_wq, &di->chargalg_work); +	} else +		dev_info(di->dev, "Wrong current step\n" +			"Enter 0. Disable AC/USB Charging\n" +			"1--100. Set AC/USB charging current step\n" +			"100. Enable AC/USB Charging\n"); + +	return strlen(buf); +} + + +static ssize_t abx500_chargalg_en_show(struct abx500_chargalg *di, +				       char *buf) +{  	return sprintf(buf, "%d\n",  		       di->susp_status.ac_suspended &&  		       di->susp_status.usb_suspended);  } -/** - * abx500_chargalg_sysfs_charger() - sysfs store operations - * @kobj:      pointer to the struct kobject - * @attr:      pointer to the struct attribute - * @buf:       buffer that holds the parameter passed from userspace - * @length:    length of the parameter passed - * - * Returns length of the buffer(input taken from user space) on success - * else error code on failure - * The operation to be performed on passing the parameters from the user space. - */ -static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, -	struct attribute *attr, const char *buf, size_t length) +static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di, +	const char *buf, size_t length)  { -	struct abx500_chargalg *di = container_of(kobj, -		struct abx500_chargalg, chargalg_kobject);  	long int param;  	int ac_usb;  	int ret; -	char entry = *attr->name; -	switch (entry) { -	case 'c': -		ret = strict_strtol(buf, 10, ¶m); -		if (ret < 0) -			return ret; +	ret = kstrtol(buf, 10, ¶m); +	if (ret < 0) +		return ret; -		ac_usb = param; -		switch (ac_usb) { -		case 0: -			/* Disable charging */ -			di->susp_status.ac_suspended = true; -			di->susp_status.usb_suspended = true; -			di->susp_status.suspended_change = true; -			/* Trigger a state change */ -			queue_work(di->chargalg_wq, -				&di->chargalg_work); -			break; -		case 1: -			/* Enable AC Charging */ -			di->susp_status.ac_suspended = false; -			di->susp_status.suspended_change = true; -			/* Trigger a state change */ -			queue_work(di->chargalg_wq, -				&di->chargalg_work); -			break; -		case 2: -			/* Enable USB charging */ -			di->susp_status.usb_suspended = false; -			di->susp_status.suspended_change = true; -			/* Trigger a state change */ -			queue_work(di->chargalg_wq, -				&di->chargalg_work); -			break; -		default: -			dev_info(di->dev, "Wrong input\n" -				"Enter 0. Disable AC/USB Charging\n" -				"1. Enable AC charging\n" -				"2. Enable USB Charging\n"); -		}; +	ac_usb = param; +	switch (ac_usb) { +	case 0: +		/* Disable charging */ +		di->susp_status.ac_suspended = true; +		di->susp_status.usb_suspended = true; +		di->susp_status.suspended_change = true; +		/* Trigger a state change */ +		queue_work(di->chargalg_wq, +			&di->chargalg_work); +		break; +	case 1: +		/* Enable AC Charging */ +		di->susp_status.ac_suspended = false; +		di->susp_status.suspended_change = true; +		/* Trigger a state change */ +		queue_work(di->chargalg_wq, +			&di->chargalg_work); +		break; +	case 2: +		/* Enable USB charging */ +		di->susp_status.usb_suspended = false; +		di->susp_status.suspended_change = true; +		/* Trigger a state change */ +		queue_work(di->chargalg_wq, +			&di->chargalg_work);  		break; +	default: +		dev_info(di->dev, "Wrong input\n" +			"Enter 0. Disable AC/USB Charging\n" +			"1. Enable AC charging\n" +			"2. Enable USB Charging\n");  	};  	return strlen(buf);  } -static struct attribute abx500_chargalg_en_charger = \ +static struct abx500_chargalg_sysfs_entry abx500_chargalg_en_charger = +	__ATTR(chargalg, 0644, abx500_chargalg_en_show, +				abx500_chargalg_en_store); + +static struct abx500_chargalg_sysfs_entry abx500_chargalg_curr_step = +	__ATTR(chargalg_curr_step, 0644, abx500_chargalg_curr_step_show, +					abx500_chargalg_curr_step_store); + +static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj, +	struct attribute *attr, char *buf)  { -	.name = "chargalg", -	.mode = S_IRUGO | S_IWUSR, -}; +	struct abx500_chargalg_sysfs_entry *entry = container_of(attr, +		struct abx500_chargalg_sysfs_entry, attr); + +	struct abx500_chargalg *di = container_of(kobj, +		struct abx500_chargalg, chargalg_kobject); + +	if (!entry->show) +		return -EIO; + +	return entry->show(di, buf); +} + +static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, +	struct attribute *attr, const char *buf, size_t length) +{ +	struct abx500_chargalg_sysfs_entry *entry = container_of(attr, +		struct abx500_chargalg_sysfs_entry, attr); + +	struct abx500_chargalg *di = container_of(kobj, +		struct abx500_chargalg, chargalg_kobject); + +	if (!entry->store) +		return -EIO; + +	return entry->store(di, buf, length); +}  static struct attribute *abx500_chargalg_chg[] = { -	&abx500_chargalg_en_charger, -	NULL +	&abx500_chargalg_en_charger.attr, +	&abx500_chargalg_curr_step.attr, +	NULL,  };  static const struct sysfs_ops abx500_chargalg_sysfs_ops = { @@ -1832,10 +2024,16 @@ static int abx500_chargalg_remove(struct platform_device *pdev)  	/* sysfs interface to enable/disbale charging from user space */  	abx500_chargalg_sysfs_exit(di); +	hrtimer_cancel(&di->safety_timer); +	hrtimer_cancel(&di->maintenance_timer); + +	cancel_delayed_work_sync(&di->chargalg_periodic_work); +	cancel_delayed_work_sync(&di->chargalg_wd_work); +	cancel_work_sync(&di->chargalg_work); +  	/* Delete the work queue */  	destroy_workqueue(di->chargalg_wq); -	flush_scheduled_work();  	power_supply_unregister(&di->chargalg_psy);  	platform_set_drvdata(pdev, NULL); @@ -1873,8 +2071,9 @@ static int abx500_chargalg_probe(struct platform_device *pdev)  		}  	} -	/* get device struct */ +	/* get device struct and parent */  	di->dev = &pdev->dev; +	di->parent = dev_get_drvdata(pdev->dev.parent);  	/* chargalg supply */  	di->chargalg_psy.name = "abx500_chargalg"; @@ -1888,15 +2087,13 @@ static int abx500_chargalg_probe(struct platform_device *pdev)  		abx500_chargalg_external_power_changed;  	/* Initilialize safety timer */ -	init_timer(&di->safety_timer); +	hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);  	di->safety_timer.function = abx500_chargalg_safety_timer_expired; -	di->safety_timer.data = (unsigned long) di;  	/* Initilialize maintenance timer */ -	init_timer(&di->maintenance_timer); +	hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);  	di->maintenance_timer.function =  		abx500_chargalg_maintenance_timer_expired; -	di->maintenance_timer.data = (unsigned long) di;  	/* Create a work queue for the chargalg */  	di->chargalg_wq = @@ -1933,6 +2130,7 @@ static int abx500_chargalg_probe(struct platform_device *pdev)  		dev_err(di->dev, "failed to create sysfs entry\n");  		goto free_psy;  	} +	di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH;  	/* Run the charging algorithm */  	queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); @@ -1964,18 +2162,7 @@ static struct platform_driver abx500_chargalg_driver = {  	},  }; -static int __init abx500_chargalg_init(void) -{ -	return platform_driver_register(&abx500_chargalg_driver); -} - -static void __exit abx500_chargalg_exit(void) -{ -	platform_driver_unregister(&abx500_chargalg_driver); -} - -module_init(abx500_chargalg_init); -module_exit(abx500_chargalg_exit); +module_platform_driver(abx500_chargalg_driver);  MODULE_LICENSE("GPL v2");  MODULE_AUTHOR("Johan Palsson, Karl Komierowski");  |