diff options
Diffstat (limited to 'drivers/mfd/cpcap-usb-det.c')
| -rw-r--r-- | drivers/mfd/cpcap-usb-det.c | 948 | 
1 files changed, 948 insertions, 0 deletions
| diff --git a/drivers/mfd/cpcap-usb-det.c b/drivers/mfd/cpcap-usb-det.c new file mode 100644 index 00000000000..d594b123098 --- /dev/null +++ b/drivers/mfd/cpcap-usb-det.c @@ -0,0 +1,948 @@ +/* + * Copyright (C) 2007 - 2010 Motorola, Inc. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/ktime.h> +#include <linux/gpio.h> + +#include <linux/regulator/consumer.h> + +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> +#include <linux/spi/spi.h> + +#define MS_TO_NS(x)		((x) * NSEC_PER_MSEC) +#define CPCAP_SENSE4_LS		8 +#define CPCAP_BIT_DP_S_LS	(CPCAP_BIT_DP_S << CPCAP_SENSE4_LS) +#define CPCAP_BIT_DM_S_LS	(CPCAP_BIT_DM_S << CPCAP_SENSE4_LS) + +#define SENSE_USB           (CPCAP_BIT_ID_FLOAT_S  | \ +			     CPCAP_BIT_CHRGCURR1_S | \ +			     CPCAP_BIT_VBUSVLD_S   | \ +			     CPCAP_BIT_SESSVLD_S) + +#define SENSE_2WIRE         (CPCAP_BIT_ID_FLOAT_S  | \ +			     CPCAP_BIT_CHRGCURR1_S | \ +			     CPCAP_BIT_VBUSVLD_S   | \ +			     CPCAP_BIT_SESSVLD_S   | \ +			     CPCAP_BIT_DP_S_LS) + +#define SENSE_USB_FLASH     (CPCAP_BIT_CHRGCURR1_S | \ +			     CPCAP_BIT_VBUSVLD_S   | \ +			     CPCAP_BIT_SESSVLD_S) + +#define SENSE_FACTORY       (CPCAP_BIT_ID_FLOAT_S  | \ +			     CPCAP_BIT_ID_GROUND_S | \ +			     CPCAP_BIT_CHRGCURR1_S | \ +			     CPCAP_BIT_VBUSVLD_S   | \ +			     CPCAP_BIT_SESSVLD_S) + +/* This Sense mask is needed because on TI the CHRGCURR1 interrupt is not always + * set.  In Factory Mode the comparator follows the Charge current only. */ +#define SENSE_FACTORY_COM   (CPCAP_BIT_ID_FLOAT_S  | \ +			     CPCAP_BIT_ID_GROUND_S | \ +			     CPCAP_BIT_VBUSVLD_S   | \ +			     CPCAP_BIT_SESSVLD_S) + +#define SENSE_CHARGER_FLOAT (CPCAP_BIT_ID_FLOAT_S  | \ +			     CPCAP_BIT_CHRGCURR1_S | \ +			     CPCAP_BIT_VBUSVLD_S   | \ +			     CPCAP_BIT_SESSVLD_S   | \ +			     CPCAP_BIT_SE1_S       | \ +			     CPCAP_BIT_DM_S_LS     | \ +			     CPCAP_BIT_DP_S_LS) + +#define SENSE_CHARGER       (CPCAP_BIT_CHRGCURR1_S | \ +			     CPCAP_BIT_VBUSVLD_S   | \ +			     CPCAP_BIT_SESSVLD_S   | \ +				 CPCAP_BIT_SE1_S       | \ +				 CPCAP_BIT_DM_S_LS     | \ +				 CPCAP_BIT_DP_S_LS) + +#define SENSE_IDLOW_CHARGER  (CPCAP_BIT_CHRGCURR1_S | \ +			     CPCAP_BIT_VBUSVLD_S   | \ +			     CPCAP_BIT_SESSVLD_S   | \ +			     CPCAP_BIT_ID_GROUND_S | \ +			     CPCAP_BIT_DP_S_LS) + +#define SENSE_CHARGER_MASK  (CPCAP_BIT_ID_GROUND_S | \ +			     CPCAP_BIT_SESSVLD_S) + +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +#define TWOWIRE_HANDSHAKE_LEN   6   /* Number of bytes in handshake sequence */ +#define TWOWIRE_DELAY           MS_TO_NS(10)  /* delay between edges in ns */ +#define BI2BY                   8   /* bits per byte */ +#define TWOWIRE_HANDSHAKE_SEQUENCE {0x07, 0xC1, 0xF3, 0xE7, 0xCF, 0x9F} +#endif + +#define UNDETECT_TRIES		5 + +#define CPCAP_USB_DET_PRINT_STATUS (1U << 0) +#define CPCAP_USB_DET_PRINT_TRANSITION (1U << 1) +static int cpcap_usb_det_debug_mask; + +module_param_named(cpcap_usb_det_debug_mask, cpcap_usb_det_debug_mask, int, +		   S_IRUGO | S_IWUSR | S_IWGRP); + +#define cpcap_usb_det_debug(debug_level_mask, args...) \ +	do { \ +		if (cpcap_usb_det_debug_mask & \ +		    CPCAP_USB_DET_PRINT_##debug_level_mask) { \ +			pr_info(args); \ +		} \ +	} while (0) + +enum cpcap_det_state { +	CONFIG, +	SAMPLE_1, +	SAMPLE_2, +	IDENTIFY, +	USB, +	FACTORY, +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +	START2WIRE, +	FINISH2WIRE, +#endif +}; + +enum cpcap_accy { +	CPCAP_ACCY_USB, +	CPCAP_ACCY_FACTORY, +	CPCAP_ACCY_CHARGER, +	CPCAP_ACCY_NONE, +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +	CPCAP_ACCY_2WIRE, +#endif +	/* Used while debouncing the accessory. */ +	CPCAP_ACCY_UNKNOWN, +}; + +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +enum cpcap_twowire_state { +	CPCAP_TWOWIRE_RUNNING, +	CPCAP_TWOWIRE_DONE, +}; + +struct cpcap_usb_det_2wire { +	int gpio; +	unsigned short pos; +	unsigned char data[TWOWIRE_HANDSHAKE_LEN]; +	enum cpcap_twowire_state state; +}; +#endif + +struct cpcap_usb_det_data { +	struct cpcap_device *cpcap; +	struct delayed_work work; +	unsigned short sense; +	unsigned short prev_sense; +	enum cpcap_det_state state; +	enum cpcap_accy usb_accy; +	struct platform_device *usb_dev; +	struct platform_device *usb_connected_dev; +	struct platform_device *charger_connected_dev; +	struct regulator *regulator; +	struct wake_lock wake_lock; +	unsigned char is_vusb_enabled; +	unsigned char undetect_cnt; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +	struct hrtimer hr_timer; +	struct cpcap_usb_det_2wire twowire_data; +#endif +}; + +static unsigned char vbus_valid_adc_check(struct cpcap_usb_det_data *data); + +static const char *accy_devices[] = { +	"cpcap_usb_charger", +	"cpcap_factory", +	"cpcap_charger", +}; + +#ifdef CONFIG_USB_TESTING_POWER +static int testing_power_enable = -1; +module_param(testing_power_enable, int, 0644); +MODULE_PARM_DESC(testing_power_enable, "Enable factory cable power " +	"supply function for testing"); +#endif + +static void vusb_enable(struct cpcap_usb_det_data *data) +{ +	int ret; +	if (!data->is_vusb_enabled) { +		wake_lock(&data->wake_lock); +		ret = regulator_enable(data->regulator); +		data->is_vusb_enabled = 1; +	} +} + +static void vusb_disable(struct cpcap_usb_det_data *data) +{ +	int ret; +	if (data->is_vusb_enabled) { +		wake_unlock(&data->wake_lock); +		ret = regulator_disable(data->regulator); +		data->is_vusb_enabled = 0; +	} +} + +static int get_sense(struct cpcap_usb_det_data *data) +{ +	int retval = -EFAULT; +	unsigned short value; +	struct cpcap_device *cpcap; + +	if (!data) +		return -EFAULT; +	cpcap = data->cpcap; + +	retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS1, &value); +	if (retval) +		return retval; + +	/* Clear ASAP after read. */ +	retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT1, +				     (CPCAP_BIT_CHRG_DET_I | +				      CPCAP_BIT_ID_FLOAT_I | +				      CPCAP_BIT_ID_GROUND_I), +				     (CPCAP_BIT_CHRG_DET_I | +				      CPCAP_BIT_ID_FLOAT_I | +				      CPCAP_BIT_ID_GROUND_I)); +	if (retval) +		return retval; + +	data->sense = value & (CPCAP_BIT_ID_FLOAT_S | +			       CPCAP_BIT_ID_GROUND_S); + +	retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS2, &value); +	if (retval) +		return retval; + +	/* Clear ASAP after read. */ +	retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT2, +				    (CPCAP_BIT_CHRGCURR1_I | +				     CPCAP_BIT_VBUSVLD_I | +				     CPCAP_BIT_SESSVLD_I | +				     CPCAP_BIT_SE1_I), +				    (CPCAP_BIT_CHRGCURR1_I | +				     CPCAP_BIT_VBUSVLD_I | +				     CPCAP_BIT_SESSVLD_I | +				     CPCAP_BIT_SE1_I)); +	if (retval) +		return retval; + +	data->sense |= value & (CPCAP_BIT_CHRGCURR1_S | +				CPCAP_BIT_VBUSVLD_S | +				CPCAP_BIT_SESSVLD_S | +				CPCAP_BIT_SE1_S); + +	retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS4, &value); +	if (retval) +		return retval; + +	/* Clear ASAP after read. */ +	retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT4, +				     (CPCAP_BIT_DP_I | +				      CPCAP_BIT_DM_I), +				     (CPCAP_BIT_DP_I | +				      CPCAP_BIT_DM_I)); +	if (retval) +		return retval; + +	data->sense |= (value & (CPCAP_BIT_DP_S | +			       CPCAP_BIT_DM_S)) << CPCAP_SENSE4_LS; + +	return 0; +} + +static int configure_hardware(struct cpcap_usb_det_data *data, +			      enum cpcap_accy accy) +{ +	int retval; + +	/* Take control of pull up from ULPI. */ +	retval  = cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, +				     CPCAP_BIT_PU_SPI, +				     CPCAP_BIT_PU_SPI); +	retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, +				    CPCAP_BIT_DP150KPU, +				    (CPCAP_BIT_DP150KPU | CPCAP_BIT_DP1K5PU | +				     CPCAP_BIT_DM1K5PU | CPCAP_BIT_DPPD | +				     CPCAP_BIT_DMPD)); + +	switch (accy) { +	case CPCAP_ACCY_USB: +	case CPCAP_ACCY_FACTORY: +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, 0, +					     CPCAP_BIT_VBUSPD); +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC2, +					     CPCAP_BIT_USBXCVREN, +					     CPCAP_BIT_USBXCVREN); +		/* Give USB driver control of pull up via ULPI. */ +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, +					     0, +					     CPCAP_BIT_PU_SPI | +					     CPCAP_BIT_DMPD_SPI | +					     CPCAP_BIT_DPPD_SPI | +					     CPCAP_BIT_SUSPEND_SPI | +					     CPCAP_BIT_ULPI_SPI_SEL); + +		if ((data->cpcap->vendor == CPCAP_VENDOR_ST) && +			(data->cpcap->revision == CPCAP_REVISION_2_0)) +				vusb_enable(data); + +		break; + +	case CPCAP_ACCY_CHARGER: +		/* Disable Reverse Mode */ +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_CRM, +					     0, CPCAP_BIT_RVRSMODE); +		/* Enable VBus PullDown */ +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, +					     CPCAP_BIT_VBUSPD, +					     CPCAP_BIT_VBUSPD); +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, 0, +					     CPCAP_BIT_VBUSSTBY_EN); +		break; + +	case CPCAP_ACCY_UNKNOWN: +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, 0, +					     CPCAP_BIT_VBUSPD); +		break; + +	case CPCAP_ACCY_NONE: +	default: +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, +					     CPCAP_BIT_VBUSPD, +					     CPCAP_BIT_VBUSPD); +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC2, 0, +					     CPCAP_BIT_USBXCVREN); +		retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, +					     CPCAP_BIT_DMPD_SPI | +					     CPCAP_BIT_DPPD_SPI | +					     CPCAP_BIT_SUSPEND_SPI | +					     CPCAP_BIT_ULPI_SPI_SEL, +					     CPCAP_BIT_DMPD_SPI | +					     CPCAP_BIT_DPPD_SPI | +					     CPCAP_BIT_SUSPEND_SPI | +					     CPCAP_BIT_ULPI_SPI_SEL); +		break; +	} + +	if (retval != 0) +		retval = -EFAULT; + +	return retval; +} + +static unsigned char vbus_valid_adc_check(struct cpcap_usb_det_data *data) +{ +	struct cpcap_adc_request req; +	int ret; + +	req.format = CPCAP_ADC_FORMAT_CONVERTED; +	req.timing = CPCAP_ADC_TIMING_IMM; +	req.type = CPCAP_ADC_TYPE_BANK_0; + +	ret = cpcap_adc_sync_read(data->cpcap, &req); +	if (ret) { +		dev_err(&data->cpcap->spi->dev, +		 "%s: ADC Read failed\n", __func__); +		return false; +	} +	return ((req.result[CPCAP_ADC_CHG_ISENSE] < 50) && +		(req.result[CPCAP_ADC_VBUS] < +		(req.result[CPCAP_ADC_BATTP]))) ? false : true; +} + + +static void notify_accy(struct cpcap_usb_det_data *data, enum cpcap_accy accy) +{ +	dev_info(&data->cpcap->spi->dev, "notify_accy: accy=%d\n", accy); + +	if ((data->usb_accy != CPCAP_ACCY_NONE) && (data->usb_dev != NULL)) { +		platform_device_del(data->usb_dev); +		data->usb_dev = NULL; +	} + +	configure_hardware(data, accy); +	data->usb_accy = accy; + +	if (accy != CPCAP_ACCY_NONE) { +		data->usb_dev = platform_device_alloc(accy_devices[accy], -1); +		if (data->usb_dev) { +			data->usb_dev->dev.platform_data = data->cpcap; +			platform_device_add(data->usb_dev); +		} +	} else +		vusb_disable(data); + +	if ((accy == CPCAP_ACCY_USB) || (accy == CPCAP_ACCY_FACTORY)) { +		if (!data->usb_connected_dev) { +			data->usb_connected_dev = +			    platform_device_alloc("cpcap_usb_connected", -1); +			platform_device_add(data->usb_connected_dev); +		} +	} else if (data->usb_connected_dev) { +		platform_device_del(data->usb_connected_dev); +		data->usb_connected_dev = NULL; +	} + +	if (accy == CPCAP_ACCY_CHARGER) { +		if (!data->charger_connected_dev) { +			data->charger_connected_dev = +			    platform_device_alloc("cpcap_charger_connected", +						  -1); +			platform_device_add(data->charger_connected_dev); +		} +	} else if (data->charger_connected_dev) { +		platform_device_del(data->charger_connected_dev); +		data->charger_connected_dev = NULL; +	} +} + +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +static enum hrtimer_restart cpcap_send_2wire_sendbit(struct hrtimer *timer) +{ +	struct cpcap_usb_det_data *usb_det_data = +		container_of(timer, struct cpcap_usb_det_data, hr_timer); +	struct cpcap_usb_det_2wire *twd = &(usb_det_data->twowire_data); +	enum hrtimer_restart ret = HRTIMER_NORESTART; +	bool value; + +	if (gpio_is_valid(twd->gpio) && +	   (twd->pos < TWOWIRE_HANDSHAKE_LEN * BI2BY)) { +		value = !!(twd->data[twd->pos/BI2BY] & +			(1 << (BI2BY - (twd->pos % BI2BY) - 1))); +		gpio_set_value(twd->gpio, value); +		ret = HRTIMER_RESTART; +	} + +	if (++twd->pos == TWOWIRE_HANDSHAKE_LEN * BI2BY || +		!gpio_is_valid(twd->gpio)) { +		twd->state = CPCAP_TWOWIRE_DONE; +		ret = HRTIMER_NORESTART; +	} + +	if (ret == HRTIMER_RESTART) +		hrtimer_forward(timer, ktime_get(), ns_to_ktime(TWOWIRE_DELAY)); + +	return ret; +} +#endif + +static void detection_work(struct work_struct *work) +{ +	struct cpcap_usb_det_data *data = +		container_of(work, struct cpcap_usb_det_data, work.work); +	unsigned char isVBusValid = 0; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +	ktime_t next_time; +	int sessvalid; +	unsigned char handshake[TWOWIRE_HANDSHAKE_LEN] = +						 TWOWIRE_HANDSHAKE_SEQUENCE; +#endif + +	switch (data->state) { +	case CONFIG: +		vusb_enable(data); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_DET); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_CURR1); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SE1); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDGND); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDFLOAT); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_DPI); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_DMI); +		cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SESSVLD); + +		configure_hardware(data, CPCAP_ACCY_UNKNOWN); + +		data->undetect_cnt = 0; +		data->state = SAMPLE_1; +		schedule_delayed_work(&data->work, msecs_to_jiffies(11)); +		break; + +	case SAMPLE_1: +		get_sense(data); +		data->state = SAMPLE_2; +		schedule_delayed_work(&data->work, msecs_to_jiffies(100)); +		break; + +	case SAMPLE_2: +		data->prev_sense = data->sense; +		get_sense(data); + +		if (data->prev_sense != data->sense) { +			/* Stay in this state */ +			data->state = SAMPLE_2; +			schedule_delayed_work(&data->work, +					      msecs_to_jiffies(100)); +		} else if (!(data->sense & CPCAP_BIT_SE1_S) && +			   (data->sense & CPCAP_BIT_ID_FLOAT_S) && +			   !(data->sense & CPCAP_BIT_ID_GROUND_S) && +			   !(data->sense & CPCAP_BIT_SESSVLD_S)) { +			data->state = IDENTIFY; +			schedule_delayed_work(&data->work, +					      msecs_to_jiffies(100)); +		} else { +			data->state = IDENTIFY; +			schedule_delayed_work(&data->work, 0); +		} +		break; + +	case IDENTIFY: +		get_sense(data); +		data->state = CONFIG; +		isVBusValid = vbus_valid_adc_check(data); + +		if ((data->sense == SENSE_USB) || +		    (data->sense == SENSE_USB_FLASH)) { +			notify_accy(data, CPCAP_ACCY_USB); + +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); + +			/* Special handling of USB cable undetect. */ +			data->state = USB; +		} else if ((data->sense == SENSE_FACTORY) || +			   (data->sense == SENSE_FACTORY_COM)) { +#ifdef CONFIG_USB_TESTING_POWER +			if (testing_power_enable > 0) { +				notify_accy(data, CPCAP_ACCY_NONE); +				cpcap_irq_unmask(data->cpcap, +					CPCAP_IRQ_CHRG_DET); +				cpcap_irq_unmask(data->cpcap, +					CPCAP_IRQ_CHRG_CURR1); +				cpcap_irq_unmask(data->cpcap, +				CPCAP_IRQ_VBUSVLD); +				break; +			} +#endif +			notify_accy(data, CPCAP_ACCY_FACTORY); + +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + +			/* Special handling of factory cable undetect. */ +			data->state = FACTORY; +		} else if (((data->sense | CPCAP_BIT_VBUSVLD_S) == \ +				SENSE_CHARGER_FLOAT) || +			   ((data->sense | CPCAP_BIT_VBUSVLD_S) == \ +				SENSE_CHARGER) || +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +			   (data->usb_accy == CPCAP_ACCY_2WIRE) || +#endif +			   (data->sense == SENSE_IDLOW_CHARGER)) { + +			if ((isVBusValid) && ((data->sense == \ +				SENSE_CHARGER_FLOAT) || +				(data->sense == SENSE_CHARGER) || +				(data->sense == SENSE_IDLOW_CHARGER) || +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +				(data->usb_accy == CPCAP_ACCY_2WIRE) || +#endif +				(data->sense & CPCAP_BIT_SESSVLD_S))) { +				/* Wakeup device from Suspend especially when +				 * you are coming from dipping voltage[<4.2V] +				 * to higher one [4.6V - VBUS,5V] +				 */ +				if (!(wake_lock_active(&data->wake_lock))) +					wake_lock(&data->wake_lock); + +				notify_accy(data, CPCAP_ACCY_CHARGER); +				/* VBUS is valid and also session valid bit +				 * is set hence, we notify that charger is +				 * connected +				 */ +				cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); +				cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +				data->state = FINISH2WIRE; +				schedule_delayed_work(&data->work, +					msecs_to_jiffies(500)); +#else +				data->state = CONFIG; +#endif +			} else if ((!isVBusValid) && +				((!(data->sense & CPCAP_BIT_SESSVLD_S) || +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +				(data->usb_accy == CPCAP_ACCY_2WIRE) || +#endif +				(!(data->sense & CPCAP_BIT_VBUSVLD_S))))) { +				/* Condition when the USB charger is connected & +				 * for some reason Voltage falls below the 4.4V +				 * threshold. Since USB is connected, we reset +				 * the State Machine and wait for the voltage to +				 * reach the high threshold +				 */ +				data->state = CONFIG; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +				data->usb_accy = CPCAP_ACCY_NONE; +				if (gpio_is_valid(data->twowire_data.gpio)) +					gpio_set_value(data->twowire_data.gpio, +						0); +#endif + +				cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); +				cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); +				cpcap_irq_unmask(data->cpcap, +						CPCAP_IRQ_VBUSVLD); +				cpcap_irq_unmask(data->cpcap, +						CPCAP_IRQ_CHRG_DET); + +				cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD); +				schedule_delayed_work(&data->work, 0); +			} +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +		} else if ((data->sense == SENSE_2WIRE) && +			   (data->usb_accy == CPCAP_ACCY_NONE)) { +			/* wait 750ms with GPIO low to force idle state */ +			if (gpio_is_valid(data->twowire_data.gpio)) { +				gpio_set_value(data->twowire_data.gpio, 0); +				data->state = START2WIRE; +				schedule_delayed_work(&data->work, +						      msecs_to_jiffies(750)); +			} else { +				printk(KERN_ERR "Detected 2wire charger but " +					"GPIO is not configured\n"); +				data->state = CONFIG; +				cpcap_irq_unmask(data->cpcap, +					CPCAP_IRQ_CHRG_DET); +				cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI); +				cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI); +				cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); +				cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); +			} +#endif +		} else if ((data->sense & CPCAP_BIT_VBUSVLD_S) && +				(data->usb_accy == CPCAP_ACCY_NONE)) { +			data->state = CONFIG; +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); +		} else { +			notify_accy(data, CPCAP_ACCY_NONE); + +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1); + +			/* When a charger is unpowered by unplugging from the +			 * wall, VBUS voltage will drop below CHRG_DET (3.5V) +			 * until the ICHRG bits are cleared.  Once ICHRG is +			 * cleared, VBUS will rise above CHRG_DET, but below +			 * VBUSVLD (4.4V) briefly as it decays.  If the charger +			 * is re-powered while VBUS is within this window, the +			 * VBUSVLD interrupt is needed to trigger charger +			 * detection. +			 * +			 * VBUSVLD must be masked before going into suspend. +			 * See cpcap_usb_det_suspend() for details. +			 */ +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_VBUSVLD); +		} +		break; + +	case USB: +		get_sense(data); + +		if ((data->sense & CPCAP_BIT_SE1_S) || +			(data->sense & CPCAP_BIT_ID_GROUND_S)) { +				data->state = CONFIG; +				schedule_delayed_work(&data->work, 0); +		} else if (!(data->sense & CPCAP_BIT_VBUSVLD_S)) { +			if (data->undetect_cnt++ < UNDETECT_TRIES) { +				cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_DET); +				cpcap_irq_mask(data->cpcap, +					       CPCAP_IRQ_CHRG_CURR1); +				cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SE1); +				cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDGND); +				data->state = USB; +				schedule_delayed_work(&data->work, +						      msecs_to_jiffies(100)); +			} else { +				data->state = CONFIG; +				schedule_delayed_work(&data->work, 0); +			} +		} else { +			data->state = USB; +			data->undetect_cnt = 0; +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); +		} +		break; + +	case FACTORY: +		get_sense(data); + +		/* The removal of a factory cable can only be detected if a +		 * charger is attached. +		 */ +		if (data->sense & CPCAP_BIT_SE1_S) { +#ifdef CONFIG_TTA_CHARGER +			enable_tta(); +#endif +			data->state = CONFIG; +			schedule_delayed_work(&data->work, 0); +		} else { +			data->state = FACTORY; +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); +		} +		break; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +	case START2WIRE: +		sessvalid = (data->sense & CPCAP_BIT_SESSVLD_S); +		memcpy(data->twowire_data.data, handshake, +			TWOWIRE_HANDSHAKE_LEN); +		data->twowire_data.pos = 5; +		data->twowire_data.state = CPCAP_TWOWIRE_RUNNING; +		next_time = ktime_set(0, TWOWIRE_DELAY); +		hrtimer_start(&data->hr_timer, next_time, HRTIMER_MODE_REL); + +		while (sessvalid && data->twowire_data.state != +			CPCAP_TWOWIRE_DONE) { +			msleep(10); +			get_sense(data); +			sessvalid = (data->sense & CPCAP_BIT_SESSVLD_S); +		} + +		if (sessvalid && data->twowire_data.state == +			CPCAP_TWOWIRE_DONE) { +			data->usb_accy = CPCAP_ACCY_2WIRE; +			data->state = IDENTIFY; +			schedule_delayed_work(&data->work, 0); +		} else { +			printk(KERN_ERR "2wire removed durring handshake\n"); +			hrtimer_cancel(&data->hr_timer); +			if (gpio_is_valid(data->twowire_data.gpio)) +				gpio_set_value(data->twowire_data.gpio, 0); +			data->state = CONFIG; +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); +			cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); +		} +		break; +	case FINISH2WIRE: +		if (gpio_is_valid(data->twowire_data.gpio)) +			gpio_set_value(data->twowire_data.gpio, 0); +		data->state = CONFIG; +		break; +#endif +	default: +		/* This shouldn't happen.  Need to reset state machine. */ +		vusb_disable(data); +		data->state = CONFIG; +		schedule_delayed_work(&data->work, 0); +		break; +	} +} + +static void int_handler(enum cpcap_irqs int_event, void *data) +{ +	struct cpcap_usb_det_data *usb_det_data = data; +	schedule_delayed_work(&(usb_det_data->work), 0); +} + +static int cpcap_usb_det_probe(struct platform_device *pdev) +{ +	int retval; +	struct cpcap_usb_det_data *data; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +	struct cpcap_platform_data *platform_data; +#endif + +	if (pdev->dev.platform_data == NULL) { +		dev_err(&pdev->dev, "no platform_data\n"); +		return -EINVAL; +	} + +	data = kzalloc(sizeof(*data), GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	data->cpcap = pdev->dev.platform_data; +	data->state = CONFIG; +	platform_set_drvdata(pdev, data); +	INIT_DELAYED_WORK(&data->work, detection_work); +	data->usb_accy = CPCAP_ACCY_NONE; +	wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "usb"); +	data->undetect_cnt = 0; + +	data->regulator = regulator_get(NULL, "vusb"); +	if (IS_ERR(data->regulator)) { +		dev_err(&pdev->dev, "Could not get regulator for cpcap_usb\n"); +		retval = PTR_ERR(data->regulator); +		goto free_mem; +	} +	regulator_set_voltage(data->regulator, 3300000, 3300000); + +	retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_CHRG_DET, +				    int_handler, data); +	retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_CHRG_CURR1, +				     int_handler, data); +	retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_SE1, +				     int_handler, data); +	retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_IDGND, +				     int_handler, data); +	retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_VBUSVLD, +				     int_handler, data); +	retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_IDFLOAT, +				     int_handler, data); +	retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_DPI, +				     int_handler, data); +	retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_DMI, +				     int_handler, data); +	retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_SESSVLD, +				     int_handler, data); + +	/* Now that HW initialization is done, give USB control via ULPI. */ +	retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, +				     0, CPCAP_BIT_ULPI_SPI_SEL); + +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +	hrtimer_init(&(data->hr_timer), CLOCK_REALTIME, HRTIMER_MODE_REL); +	data->hr_timer.function = &cpcap_send_2wire_sendbit; +	if (data->cpcap->spi && data->cpcap->spi->controller_data) { +		platform_data = data->cpcap->spi->controller_data; +		data->twowire_data.gpio = platform_data->twowire_hndshk_gpio; +	} else { +		data->twowire_data.gpio = -1; +		dev_err(&pdev->dev, "SPI platform_data missing\n"); +		retval = -EINVAL; +	} +#endif + +	if (retval != 0) { +		dev_err(&pdev->dev, "Initialization Error\n"); +		retval = -ENODEV; +		goto free_irqs; +	} + +	dev_info(&pdev->dev, "CPCAP USB detection device probed\n"); + +	/* Perform initial detection */ +	detection_work(&(data->work.work)); + +	return 0; + +free_irqs: +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_VBUSVLD); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDGND); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_SE1); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_CURR1); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_DET); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDFLOAT); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_DPI); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_DMI); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_SESSVLD); +	regulator_put(data->regulator); +free_mem: +	wake_lock_destroy(&data->wake_lock); +	kfree(data); + +	return retval; +} + +static int cpcap_usb_det_remove(struct platform_device *pdev) +{ +	struct cpcap_usb_det_data *data = platform_get_drvdata(pdev); + +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_DET); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_CURR1); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_SE1); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDGND); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_VBUSVLD); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDFLOAT); +	cpcap_irq_free(data->cpcap, CPCAP_IRQ_SESSVLD); + +	configure_hardware(data, CPCAP_ACCY_NONE); +	cancel_delayed_work_sync(&data->work); + +	if ((data->usb_accy != CPCAP_ACCY_NONE) && (data->usb_dev != NULL)) +		platform_device_del(data->usb_dev); + +	vusb_disable(data); +	regulator_put(data->regulator); + +	wake_lock_destroy(&data->wake_lock); + +	kfree(data); +	return 0; +} + +#ifdef CONFIG_PM +static int cpcap_usb_det_suspend(struct platform_device *pdev, +				 pm_message_t state) +{ +	struct cpcap_usb_det_data *data = platform_get_drvdata(pdev); + +	/* VBUSVLD cannot be unmasked when entering suspend. If left +	 * unmasked, a false interrupt will be received, keeping the +	 * device out of suspend. The interrupt does not need to be +	 * unmasked when resuming from suspend since the use case +	 * for having the interrupt unmasked is over. +	 */ +	cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD); + +	return 0; +} +#else +#define cpcap_usb_det_suspend NULL +#endif + +static struct platform_driver cpcap_usb_det_driver = { +	.probe		= cpcap_usb_det_probe, +	.remove		= cpcap_usb_det_remove, +	.suspend	= cpcap_usb_det_suspend, +	.driver		= { +		.name	= "cpcap_usb_det", +		.owner	= THIS_MODULE, +	}, +}; + +static int __init cpcap_usb_det_init(void) +{ +	return cpcap_driver_register(&cpcap_usb_det_driver); +} +/* The CPCAP USB detection driver must be started later to give the MUSB + * driver time to complete its initialization. */ +late_initcall(cpcap_usb_det_init); + +static void __exit cpcap_usb_det_exit(void) +{ +	platform_driver_unregister(&cpcap_usb_det_driver); +} +module_exit(cpcap_usb_det_exit); + +MODULE_ALIAS("platform:cpcap_usb_det"); +MODULE_DESCRIPTION("CPCAP USB detection driver"); +MODULE_LICENSE("GPL"); |