From c7f41361bf1ccc58b0e3735f28cf3a8d54602252 Mon Sep 17 00:00:00 2001 From: Xin Xie Date: Wed, 1 Aug 2012 16:18:55 -0700 Subject: usb: gadget: tegra_udc: use USB charging spec 1.2 Enable the USB charging based on the USB charging spec 1.2. Now we can detect: 1. SDP (stanardard downstream port) 2. CDP (charging downstream port) 3. DCP (dedicated charging port) 4. None (no cable connected) 5. Non-standard charger For some non-standard charger, we cannot detect it as DCP device. If we find a charger detected as USB device but no EP0 packet recieved with 1s, we then decided this is 1A charger. bug 968345 Change-Id: I804f90ea6e4794da8f52b1c3ebd694828d99f40e Signed-off-by: Xin Xie Reviewed-on: http://git-master/r/120218 Reviewed-by: Simone Willett Tested-by: Simone Willett --- drivers/usb/gadget/tegra_udc.c | 120 +++++++++++++++++++++++++++++++---------- drivers/usb/gadget/tegra_udc.h | 17 +++++- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/drivers/usb/gadget/tegra_udc.c b/drivers/usb/gadget/tegra_udc.c index ace9f2ecaff1..8b8828d731af 100644 --- a/drivers/usb/gadget/tegra_udc.c +++ b/drivers/usb/gadget/tegra_udc.c @@ -1240,6 +1240,76 @@ static int tegra_set_selfpowered(struct usb_gadget *gadget, int is_on) return 0; } +static int tegra_usb_set_charging_current(struct tegra_udc *udc) +{ + int max_ua; + + if (NULL == udc->vbus_reg) + return 0; + + switch (udc->connect_type) { + case CONNECT_TYPE_NONE: + pr_debug("detected USB charging is disabled"); + max_ua = 0; + break; + case CONNECT_TYPE_SDP: + pr_debug("detected SDP port"); + max_ua = USB_CHARGING_SDP_CURRENT_LIMIT_UA; + break; + case CONNECT_TYPE_DCP: + pr_debug("detected DCP port(wall charger)"); + max_ua = USB_CHARGING_DCP_CURRENT_LIMIT_UA; + break; + case CONNECT_TYPE_CDP: + pr_debug("detected CDP port(1A USB port)"); + max_ua = USB_CHARGING_CDP_CURRENT_LIMIT_UA; + break; + case CONNECT_TYPE_NON_STANDARD_CHARGER: + pr_debug("detected non-standard charging port"); + max_ua = USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA; + break; + default: + pr_debug("detected USB charging type is unknown"); + max_ua = 0; + } + + return regulator_set_current_limit(udc->vbus_reg, 0, max_ua); +} + +static void tegra_detect_charging_type_is_cdp_or_dcp(struct tegra_udc *udc) +{ + u32 portsc; + u32 temp; + unsigned long flags; + + /* use spinlock to prevent kernel preemption here */ + spin_lock_irqsave(&udc->lock, flags); + + /* set controller to run which cause D+ pull high */ + temp = udc_readl(udc, USB_CMD_REG_OFFSET); + temp |= USB_CMD_RUN_STOP; + udc_writel(udc, temp, USB_CMD_REG_OFFSET); + + udelay(10); + + /* use D+ and D- status to check it is CDP or DCP */ + portsc = udc_readl(udc, PORTSCX_REG_OFFSET) & PORTSCX_LINE_STATUS_BITS; + if (portsc == (PORTSCX_LINE_STATUS_DP_BIT | PORTSCX_LINE_STATUS_DM_BIT)) + udc->connect_type = CONNECT_TYPE_DCP; + else if (portsc == PORTSCX_LINE_STATUS_DP_BIT) + udc->connect_type = CONNECT_TYPE_CDP; + else + /* + * If it take more 100mS between D+ pull high and read Line + * Status, host might initiate the RESET, then we see both + * line status as 0 (SE0). This really should not happen as we + * disabled the kernel preemption before reaching here. + */ + BUG(); + + spin_unlock_irqrestore(&udc->lock, flags); +} + /** * Notify controller that VBUS is powered, called by whatever * detects VBUS sessions @@ -1262,13 +1332,10 @@ static int tegra_vbus_session(struct usb_gadget *gadget, int is_active) dr_controller_reset(udc); udc->vbus_active = 0; udc->usb_state = USB_STATE_DEFAULT; + udc->connect_type = CONNECT_TYPE_NONE; spin_unlock_irqrestore(&udc->lock,flags); tegra_usb_phy_power_off(udc->phy); - if (udc->vbus_reg) { - /* set the current limit to 0mA */ - regulator_set_current_limit( - udc->vbus_reg, 0, 0); - } + tegra_usb_set_charging_current(udc); } else if (!udc->vbus_active && is_active) { tegra_usb_phy_power_on(udc->phy); /* setup the controller in the device mode */ @@ -1280,18 +1347,23 @@ static int tegra_vbus_session(struct usb_gadget *gadget, int is_active) udc->ep0_state = WAIT_FOR_SETUP; udc->ep0_dir = 0; udc->vbus_active = 1; + if (tegra_usb_phy_charger_detected(udc->phy)) { + tegra_detect_charging_type_is_cdp_or_dcp(udc); + } else { + udc->connect_type = CONNECT_TYPE_SDP; + /* + * Schedule work to wait for 1000 msec and check for + * a non-standard charger if setup packet is not + * received. + */ + schedule_delayed_work(&udc->work, msecs_to_jiffies( + USB_CHARGER_DETECTION_WAIT_TIME_MS)); + } /* start the controller */ dr_controller_run(udc); - if (udc->vbus_reg) { - /* set the current limit to 100mA */ - regulator_set_current_limit( - udc->vbus_reg, 0, 100); - } - /* Schedule work to wait for 1000 msec and check for - * charger if setup packet is not received */ - schedule_delayed_work(&udc->work, - USB_CHARGER_DETECTION_WAIT_TIME_MS); + tegra_usb_set_charging_current(udc); } + return 0; } @@ -2119,26 +2191,18 @@ static void tegra_udc_irq_work(struct work_struct *irq_work) DBG("%s(%d) END\n", __func__, __LINE__); } -/** - * If VBUS is detected and setup packet is not received in 100ms then - * work thread starts and checks for the USB charger detection. +/* + * When VBUS is detected we already know it is DCP/SDP/CDP devices if it is a + * standard device; If we did not receive EP0 setup packet, we can assuming it + * is a charger capable of 1.8A charging. */ static void tegra_udc_charger_detect_work(struct work_struct *work) { struct tegra_udc *udc = container_of(work, struct tegra_udc, work.work); DBG("%s(%d) BEGIN\n", __func__, __LINE__); - /* check for the platform charger detection */ - if (tegra_usb_phy_charger_detected(udc->phy)) { - printk(KERN_INFO "USB compliant charger detected\n"); - /* check udc regulator is available for drawing vbus current*/ - if (udc->vbus_reg) { - /* set the current limit in uA */ - regulator_set_current_limit( - udc->vbus_reg, 0, - USB_CHARGING_CURRENT_LIMIT_MA*1000); - } - } + udc->connect_type = CONNECT_TYPE_NON_STANDARD_CHARGER; + tegra_usb_set_charging_current(udc); DBG("%s(%d) END\n", __func__, __LINE__); } diff --git a/drivers/usb/gadget/tegra_udc.h b/drivers/usb/gadget/tegra_udc.h index 50e2a3193648..094e3eb0e458 100644 --- a/drivers/usb/gadget/tegra_udc.h +++ b/drivers/usb/gadget/tegra_udc.h @@ -38,7 +38,11 @@ #define USB_MAX_CTRL_PAYLOAD 64 /* Charger current limit=1800mA, as per the USB charger spec */ -#define USB_CHARGING_CURRENT_LIMIT_MA 1800 +#define USB_CHARGING_DCP_CURRENT_LIMIT_UA 1800000 +#define USB_CHARGING_CDP_CURRENT_LIMIT_UA 900000 +#define USB_CHARGING_SDP_CURRENT_LIMIT_UA 100000 +#define USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA 1800000 + /* 1 sec wait time for charger detection after vbus is detected */ #define USB_CHARGER_DETECTION_WAIT_TIME_MS 1000 #define BOOST_TRIGGER_SIZE 4096 @@ -156,6 +160,8 @@ #define PORTSCX_PORT_SUSPEND 0x00000080 #define PORTSCX_PORT_RESET 0x00000100 #define PORTSCX_LINE_STATUS_BITS 0x00000C00 +#define PORTSCX_LINE_STATUS_DP_BIT 0x00000800 +#define PORTSCX_LINE_STATUS_DM_BIT 0x00000400 #define PORTSCX_PORT_POWER 0x00001000 #define PORTSCX_PORT_INDICTOR_CTRL 0x0000C000 #define PORTSCX_PORT_TEST_CTRL 0x000F0000 @@ -397,6 +403,14 @@ struct tegra_ep { unsigned stopped:1; }; +enum tegra_connect_type { + CONNECT_TYPE_NONE, + CONNECT_TYPE_SDP, + CONNECT_TYPE_DCP, + CONNECT_TYPE_CDP, + CONNECT_TYPE_NON_STANDARD_CHARGER +}; + struct tegra_udc { struct usb_gadget gadget; struct usb_gadget_driver *driver; @@ -417,6 +431,7 @@ struct tegra_udc { struct work_struct boost_cpufreq_work; /* irq work for controlling the usb power */ struct work_struct irq_work; + enum tegra_connect_type connect_type; void __iomem *regs; size_t ep_qh_size; /* size after alignment adjustment*/ dma_addr_t ep_qh_dma; /* dma address of QH */ -- cgit v1.2.3