summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Xie <xxie@nvidia.com>2012-08-01 16:18:55 -0700
committerSimone Willett <swillett@nvidia.com>2012-08-17 12:13:47 -0700
commitc7f41361bf1ccc58b0e3735f28cf3a8d54602252 (patch)
tree5f92ed90123fc6e756dbcef70195aaae7940028f
parent1217d0ae77b22e8ac16e4430b020202240a4c788 (diff)
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 <xxie@nvidia.com> Reviewed-on: http://git-master/r/120218 Reviewed-by: Simone Willett <swillett@nvidia.com> Tested-by: Simone Willett <swillett@nvidia.com>
-rw-r--r--drivers/usb/gadget/tegra_udc.c120
-rw-r--r--drivers/usb/gadget/tegra_udc.h17
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 */