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