summaryrefslogtreecommitdiff
path: root/drivers/mfd/cpcap-usb-det.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/cpcap-usb-det.c')
-rw-r--r--drivers/mfd/cpcap-usb-det.c948
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");