/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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");