usb: gadget: tegra_udc: use USB charging spec 1.2
Xin Xie [Wed, 1 Aug 2012 23:18:55 +0000 (16:18 -0700)]
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>

drivers/usb/gadget/tegra_udc.c
drivers/usb/gadget/tegra_udc.h

index ace9f2e..8b8828d 100644 (file)
@@ -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__);
 }
index 50e2a31..094e3eb 100644 (file)
 #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
 #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 */