Commit b269fff4 authored by Lee Jones's avatar Lee Jones Committed by Anton Vorontsov
Browse files

ab8500_charger: Detect charger removal



Add two new work queues to provide USB and AC charger disconnect
detection.

Signed-off-by: default avatarLee Jones <lee.jones@linaro.org>
Signed-off-by: default avatarAnton Vorontsov <anton@enomsg.org>
parent 3988a4df
Loading
Loading
Loading
Loading
+141 −1
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
#include <linux/mfd/abx500/ab8500-gpadc.h>
#include <linux/mfd/abx500/ux500_chargalg.h>
#include <linux/usb/otg.h>
#include <linux/mutex.h>

/* Charger constants */
#define NO_PW_CONN			0
@@ -68,6 +69,11 @@
#define MAIN_CH_NOK			0x01
#define VBUS_DET			0x80

#define MAIN_CH_STATUS2_MAINCHGDROP		0x80
#define MAIN_CH_STATUS2_MAINCHARGERDETDBNC	0x40
#define USB_CH_VBUSDROP				0x40
#define USB_CH_VBUSDETDBNC			0x01

/* UsbLineStatus register bit masks */
#define AB8500_USB_LINK_STATUS		0x78
#define AB8500_STD_HOST_SUSP		0x18
@@ -82,6 +88,8 @@
/* Step up/down delay in us */
#define STEP_UDELAY			1000

#define CHARGER_STATUS_POLL 10 /* in ms */

/* UsbLineStatus register - usb types */
enum ab8500_charger_link_status {
	USB_STAT_NOT_CONFIGURED,
@@ -203,6 +211,10 @@ struct ab8500_charger_usb_state {
 * @check_usbchgnotok_work:	Work for checking USB charger not ok status
 * @kick_wd_work:		Work for kicking the charger watchdog in case
 *				of ABB rev 1.* due to the watchog logic bug
 * @ac_charger_attached_work:	Work for checking if AC charger is still
 *				connected
 * @usb_charger_attached_work:	Work for checking if USB charger is still
 *				connected
 * @ac_work:			Work for checking AC charger connection
 * @detect_usb_type_work:	Work for detecting the USB type connected
 * @usb_link_status_work:	Work for checking the new USB link status
@@ -211,6 +223,7 @@ struct ab8500_charger_usb_state {
 *				Work for checking Main thermal status
 * @check_usb_thermal_prot_work:
 *				Work for checking USB thermal status
 * @charger_attached_mutex:	For controlling the wakelock
 */
struct ab8500_charger {
	struct device *dev;
@@ -239,6 +252,8 @@ struct ab8500_charger {
	struct delayed_work check_hw_failure_work;
	struct delayed_work check_usbchgnotok_work;
	struct delayed_work kick_wd_work;
	struct delayed_work ac_charger_attached_work;
	struct delayed_work usb_charger_attached_work;
	struct work_struct ac_work;
	struct work_struct detect_usb_type_work;
	struct work_struct usb_link_status_work;
@@ -247,6 +262,7 @@ struct ab8500_charger {
	struct work_struct check_usb_thermal_prot_work;
	struct usb_phy *usb_phy;
	struct notifier_block nb;
	struct mutex charger_attached_mutex;
};

/* AC properties */
@@ -349,6 +365,19 @@ static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
		dev_dbg(di->dev, "USB connected:%i\n", connected);
		di->usb.charger_connected = connected;
		sysfs_notify(&di->usb_chg.psy.dev->kobj, NULL, "present");

		if (connected) {
			mutex_lock(&di->charger_attached_mutex);
			mutex_unlock(&di->charger_attached_mutex);

			queue_delayed_work(di->charger_wq,
					   &di->usb_charger_attached_work,
					   HZ);
		} else {
			cancel_delayed_work_sync(&di->usb_charger_attached_work);
			mutex_lock(&di->charger_attached_mutex);
			mutex_unlock(&di->charger_attached_mutex);
		}
	}
}

@@ -1706,6 +1735,84 @@ static void ab8500_charger_ac_work(struct work_struct *work)
	sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present");
}

static void ab8500_charger_usb_attached_work(struct work_struct *work)
{
	struct ab8500_charger *di = container_of(work,
						 struct ab8500_charger,
						 usb_charger_attached_work.work);
	int usbch = (USB_CH_VBUSDROP | USB_CH_VBUSDETDBNC);
	int ret, i;
	u8 statval;

	for (i = 0; i < 10; i++) {
		ret = abx500_get_register_interruptible(di->dev,
							AB8500_CHARGER,
							AB8500_CH_USBCH_STAT1_REG,
							&statval);
		if (ret < 0) {
			dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
			goto reschedule;
		}
		if ((statval & usbch) != usbch)
			goto reschedule;

		msleep(CHARGER_STATUS_POLL);
	}

	ab8500_charger_usb_en(&di->usb_chg, 0, 0, 0);

	mutex_lock(&di->charger_attached_mutex);
	mutex_unlock(&di->charger_attached_mutex);

	return;

reschedule:
	queue_delayed_work(di->charger_wq,
			   &di->usb_charger_attached_work,
			   HZ);
}

static void ab8500_charger_ac_attached_work(struct work_struct *work)
{

	struct ab8500_charger *di = container_of(work,
						 struct ab8500_charger,
						 ac_charger_attached_work.work);
	int mainch = (MAIN_CH_STATUS2_MAINCHGDROP |
		      MAIN_CH_STATUS2_MAINCHARGERDETDBNC);
	int ret, i;
	u8 statval;

	for (i = 0; i < 10; i++) {
		ret = abx500_get_register_interruptible(di->dev,
							AB8500_CHARGER,
							AB8500_CH_STATUS2_REG,
							&statval);
		if (ret < 0) {
			dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
			goto reschedule;
		}

		if ((statval & mainch) != mainch)
			goto reschedule;

		msleep(CHARGER_STATUS_POLL);
	}

	ab8500_charger_ac_en(&di->ac_chg, 0, 0, 0);
	queue_work(di->charger_wq, &di->ac_work);

	mutex_lock(&di->charger_attached_mutex);
	mutex_unlock(&di->charger_attached_mutex);

	return;

reschedule:
	queue_delayed_work(di->charger_wq,
			   &di->ac_charger_attached_work,
			   HZ);
}

/**
 * ab8500_charger_detect_usb_type_work() - work to detect USB type
 * @work:	Pointer to the work_struct structure
@@ -1986,6 +2093,10 @@ static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di)
	dev_dbg(di->dev, "Main charger unplugged\n");
	queue_work(di->charger_wq, &di->ac_work);

	cancel_delayed_work_sync(&di->ac_charger_attached_work);
	mutex_lock(&di->charger_attached_mutex);
	mutex_unlock(&di->charger_attached_mutex);

	return IRQ_HANDLED;
}

@@ -2003,6 +2114,11 @@ static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di)
	dev_dbg(di->dev, "Main charger plugged\n");
	queue_work(di->charger_wq, &di->ac_work);

	mutex_lock(&di->charger_attached_mutex);
	mutex_unlock(&di->charger_attached_mutex);
	queue_delayed_work(di->charger_wq,
			   &di->ac_charger_attached_work,
			   HZ);
	return IRQ_HANDLED;
}

@@ -2634,7 +2750,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
	struct device_node *np = pdev->dev.of_node;
	struct abx500_bm_data *plat = pdev->dev.platform_data;
	struct ab8500_charger *di;
	int irq, i, charger_status, ret = 0;
	int irq, i, charger_status, ret = 0, ch_stat;

	di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
	if (!di) {
@@ -2713,12 +2829,19 @@ static int ab8500_charger_probe(struct platform_device *pdev)
		return -ENOMEM;
	}

	mutex_init(&di->charger_attached_mutex);

	/* Init work for HW failure check */
	INIT_DEFERRABLE_WORK(&di->check_hw_failure_work,
		ab8500_charger_check_hw_failure_work);
	INIT_DEFERRABLE_WORK(&di->check_usbchgnotok_work,
		ab8500_charger_check_usbchargernotok_work);

	INIT_DELAYED_WORK(&di->ac_charger_attached_work,
			  ab8500_charger_ac_attached_work);
	INIT_DELAYED_WORK(&di->usb_charger_attached_work,
			  ab8500_charger_usb_attached_work);

	/*
	 * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
	 * logic. That means we have to continously kick the charger
@@ -2832,6 +2955,23 @@ static int ab8500_charger_probe(struct platform_device *pdev)

	platform_set_drvdata(pdev, di);

	mutex_lock(&di->charger_attached_mutex);

	ch_stat = ab8500_charger_detect_chargers(di);

	if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) {
		queue_delayed_work(di->charger_wq,
				   &di->ac_charger_attached_work,
				   HZ);
	}
	if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) {
		queue_delayed_work(di->charger_wq,
				   &di->usb_charger_attached_work,
				   HZ);
	}

	mutex_unlock(&di->charger_attached_mutex);

	return ret;

free_irq: