diff options
author | Xin Xie <xxie@nvidia.com> | 2013-02-25 17:35:22 -0800 |
---|---|---|
committer | Simone Willett <swillett@nvidia.com> | 2013-03-25 15:27:40 -0700 |
commit | 11b0eb2c4f0bd2447445ec2f0bdc6c892e142a54 (patch) | |
tree | afdfd537a1bb89bff83b9afbb333d0b42ecf724a /drivers/usb | |
parent | 7d5345bfe041a23462defc92209e812fdcae9f84 (diff) |
usb: gadget: tegra: fix NV-charger detection
NV charger is using resistor network to set 2.8v/2.0v for D+/D-. Doing
so, we cannot detect SDP and NV-charger reliably. Instead we need:
* Implement Data Contact Detection mechanism, so SDP and NV-charger can
be detected reliably.
* We also need make difference between NV-charger and non-standard
charger based on D+/D- line status (non-standard charger D+/D- lines
can be pulled high or low individually, NV-charger D+/D- line is
always high)
bug 1236790
bug 1234552
Change-Id: I1f40aafdcc6a97eb8a8b7fd85392dd9aadf78771
Signed-off-by: Xin Xie <xxie@nvidia.com>
Signed-off-by: Anshul Jain <anshulj@nvidia.com>
Reviewed-on: http://git-master/r/#change,207217
Reviewed-on: http://git-master/r/210848
GVS: Gerrit_Virtual_Submit
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/gadget/tegra_udc.c | 163 | ||||
-rw-r--r-- | drivers/usb/gadget/tegra_udc.h | 16 |
2 files changed, 83 insertions, 96 deletions
diff --git a/drivers/usb/gadget/tegra_udc.c b/drivers/usb/gadget/tegra_udc.c index 0c3430fc7553..8577d05aea6f 100644 --- a/drivers/usb/gadget/tegra_udc.c +++ b/drivers/usb/gadget/tegra_udc.c @@ -1285,73 +1285,54 @@ static int tegra_set_selfpowered(struct usb_gadget *gadget, int is_on) udc->selfpowered = (is_on != 0); return 0; } -static void dump_charger_type(struct tegra_udc *udc, int max_ua) -{ - switch (udc->connect_type) { - case CONNECT_TYPE_NONE: - pr_info("cable/charger is not connected\n"); - break; - case CONNECT_TYPE_SDP: - pr_info("detected SDP charger, charging current " \ - "limit: %d uA\n", max_ua); - break; - case CONNECT_TYPE_DCP: - pr_info("detected DCP charger, charging current " \ - "limit: %d uA\n", max_ua); - break; - case CONNECT_TYPE_CDP: - pr_info("detected CDP charger, charging current " \ - "limit: %d uA\n", max_ua); - break; - case CONNECT_TYPE_NV_CHARGER: - pr_info("detected NV charger, charging current " \ - "limit: %d uA\n", max_ua); - break; - case CONNECT_TYPE_NON_STANDARD_CHARGER: - pr_info("detected non-standard charger, charging" \ - "current limit %d uA\n", max_ua); - break; - default: - pr_info("Detected USB charging type is unknown\n"); - } -} + static int tegra_usb_set_charging_current(struct tegra_udc *udc) { int max_ua; + struct device *dev; - if (NULL == udc->vbus_reg) - return 0; - + dev = &udc->pdev->dev; switch (udc->connect_type) { case CONNECT_TYPE_NONE: - pr_debug("cable/charger is not connected"); + dev_info(dev, "cable/charger is not connected\n"); max_ua = 0; break; case CONNECT_TYPE_SDP: - pr_debug("detected SDP port"); - max_ua = USB_CHARGING_SDP_CURRENT_LIMIT_UA; + dev_info(dev, "detected SDP port\n"); + max_ua = min(udc->current_limit * 1000, + USB_CHARGING_SDP_CURRENT_LIMIT_UA); break; case CONNECT_TYPE_DCP: - pr_debug("detected DCP port(wall charger)"); + dev_info(dev, "detected DCP port(wall charger)\n"); 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; + dev_info(dev, "detected CDP port(1A USB port)\n"); + /* + * if current is more than VBUS suspend current, we draw CDP + * allowed maximum current (override SDP max current which is + * set by the upper level driver). + */ + if (udc->current_limit > 2) + max_ua = USB_CHARGING_CDP_CURRENT_LIMIT_UA; + else + max_ua = udc->current_limit * 1000; break; case CONNECT_TYPE_NV_CHARGER: - pr_debug("detected NV charging port"); + dev_info(dev, "detected NV charging port\n"); max_ua = USB_CHARGING_NV_CHARGER_CURRENT_LIMIT_UA; break; case CONNECT_TYPE_NON_STANDARD_CHARGER: - pr_debug("detected non-standard charging port"); + dev_info(dev, "detected non-standard charging port\n"); max_ua = USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA; break; default: - pr_debug("detected USB charging type is unknown"); + dev_info(dev, "detected USB charging type is unknown\n"); max_ua = 0; } - dump_charger_type(udc, max_ua); + + if (NULL == udc->vbus_reg) + return 0; return regulator_set_current_limit(udc->vbus_reg, 0, max_ua); } @@ -1391,6 +1372,26 @@ static void tegra_detect_charging_type_is_cdp_or_dcp(struct tegra_udc *udc) spin_unlock_irqrestore(&udc->lock, flags); } +static int tegra_detect_cable_type(struct tegra_udc *udc) +{ + if (tegra_usb_phy_charger_detected(udc->phy)) + tegra_detect_charging_type_is_cdp_or_dcp(udc); + else if (tegra_usb_phy_nv_charger_detected(udc->phy)) + udc->connect_type = CONNECT_TYPE_NV_CHARGER; + else + udc->connect_type = CONNECT_TYPE_SDP; + + /* + * If it is charger types, we start charging now. If it connect to USB + * host, we let upper gadget driver to decide the current capability. + */ + if ((udc->connect_type != CONNECT_TYPE_SDP) && + (udc->connect_type != CONNECT_TYPE_CDP)) + tegra_usb_set_charging_current(udc); + + return 0; +} + /** * Notify controller that VBUS is powered, called by whatever * detects VBUS sessions @@ -1404,7 +1405,7 @@ static int tegra_vbus_session(struct usb_gadget *gadget, int is_active) if (udc->vbus_active && !is_active) { /* If cable disconnected, cancel any delayed work */ - cancel_delayed_work(&udc->non_std_charger_work); + cancel_delayed_work_sync(&udc->non_std_charger_work); spin_lock_irqsave(&udc->lock, flags); /* reset all internal Queues and inform client driver */ reset_queues(udc); @@ -1428,26 +1429,11 @@ 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 { - if (tegra_usb_phy_nv_charger_detected(udc->phy)) { - udc->connect_type = CONNECT_TYPE_NV_CHARGER; - } else { - udc->connect_type = CONNECT_TYPE_SDP; - /* Schedule work to wait for 4000 msec and check - * for a non-standard charger if setup packet is - * not received. - */ - schedule_delayed_work(&udc->non_std_charger_work - , msecs_to_jiffies( - USB_CHARGER_DETECTION_WAIT_TIME_MS)); - } - } - /* start the controller */ - dr_controller_run(udc); - if (udc->connect_type != CONNECT_TYPE_SDP) - tegra_usb_set_charging_current(udc); + tegra_detect_cable_type(udc); + /* start the controller if USB host detected */ + if ((udc->connect_type == CONNECT_TYPE_SDP) || + (udc->connect_type == CONNECT_TYPE_CDP)) + dr_controller_run(udc); } return 0; @@ -1466,11 +1452,9 @@ static int tegra_vbus_draw(struct usb_gadget *gadget, unsigned mA) struct tegra_udc *udc; udc = container_of(gadget, struct tegra_udc, gadget); - /* Do not set current limits for CDP ports */ - if (udc->connect_type == CONNECT_TYPE_SDP) { - udc->current_limit = mA; - schedule_work(&udc->current_work); - } + + udc->current_limit = mA; + schedule_work(&udc->current_work); if (udc->transceiver) return usb_phy_set_power(udc->transceiver, mA); @@ -1493,13 +1477,24 @@ static int tegra_pullup(struct usb_gadget *gadget, int is_on) return 0; tmp = udc_readl(udc, USB_CMD_REG_OFFSET); - if (can_pullup(udc)) - udc_writel(udc, tmp | USB_CMD_RUN_STOP, - USB_CMD_REG_OFFSET); - else - udc_writel(udc, (tmp & ~USB_CMD_RUN_STOP), - USB_CMD_REG_OFFSET); + if (can_pullup(udc)) { + udc_writel(udc, tmp | USB_CMD_RUN_STOP, USB_CMD_REG_OFFSET); + /* + * We cannot tell difference between a SDP and non-standard + * charger (which has D+/D- line floating) based on line status + * at the time VBUS is detected. + * + * We can schedule a 4s delayed work and verify it is an + * non-standard charger if no setup packet is received after + * enumeration started. + */ + if (udc->connect_type == CONNECT_TYPE_SDP) + schedule_delayed_work(&udc->non_std_charger_work, + msecs_to_jiffies(NON_STD_CHARGER_DET_TIME_MS)); + } else { + udc_writel(udc, (tmp & ~USB_CMD_RUN_STOP), USB_CMD_REG_OFFSET); + } return 0; } @@ -1827,10 +1822,6 @@ static void setup_received_irq(struct tegra_udc *udc, return; case USB_REQ_SET_ADDRESS: - if (udc->connect_type != CONNECT_TYPE_CDP) - udc->connect_type = CONNECT_TYPE_SDP; - tegra_vbus_draw(&udc->gadget, - USB_CHARGING_SDP_CURRENT_LIMIT_UA/1000); /* Status phase from udc */ if (setup->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) @@ -2254,14 +2245,7 @@ static void tegra_udc_set_current_limit_work(struct work_struct *work) { struct tegra_udc *udc = container_of(work, struct tegra_udc, current_work); - /* check udc regulator is available for drawing vbus current*/ - if (udc->vbus_reg) { - /* set the current limit in uA */ - dump_charger_type(udc, udc->current_limit * 1000); - regulator_set_current_limit( - udc->vbus_reg, 0, - udc->current_limit * 1000); - } + tegra_usb_set_charging_current(udc); } #ifdef CONFIG_TEGRA_GADGET_BOOST_CPU_FREQ @@ -2297,7 +2281,7 @@ static void tegra_udc_irq_work(struct work_struct *irq_work) /* * 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. + * is an non-DCP charger. */ static void tegra_udc_non_std_charger_detect_work(struct work_struct *work) { @@ -2305,7 +2289,10 @@ static void tegra_udc_non_std_charger_detect_work(struct work_struct *work) non_std_charger_work.work); DBG("%s(%d) BEGIN\n", __func__, __LINE__); + dr_controller_stop(udc); + 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 954a0ca5f8c8..6659ed0e0f5f 100644 --- a/drivers/usb/gadget/tegra_udc.h +++ b/drivers/usb/gadget/tegra_udc.h @@ -38,14 +38,14 @@ #define USB_MAX_CTRL_PAYLOAD 64 /* Charger current limit=1800mA, as per the USB charger spec */ -#define USB_CHARGING_DCP_CURRENT_LIMIT_UA 1800000 -#define USB_CHARGING_CDP_CURRENT_LIMIT_UA 1500000 -#define USB_CHARGING_SDP_CURRENT_LIMIT_UA 100000 -#define USB_CHARGING_NV_CHARGER_CURRENT_LIMIT_UA 2000000 -#define USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA 500000 - - /* 4 sec wait time for charger detection after vbus is detected */ -#define USB_CHARGER_DETECTION_WAIT_TIME_MS 4000 +#define USB_CHARGING_DCP_CURRENT_LIMIT_UA 1800000u +#define USB_CHARGING_CDP_CURRENT_LIMIT_UA 1500000u +#define USB_CHARGING_SDP_CURRENT_LIMIT_UA 500000u +#define USB_CHARGING_NV_CHARGER_CURRENT_LIMIT_UA 2000000u +#define USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA 500000u + + /* 4 sec wait time for non-std charger detection after vbus is detected */ +#define NON_STD_CHARGER_DET_TIME_MS 4000 #define BOOST_TRIGGER_SIZE 4096 #define UDC_RESET_TIMEOUT_MS 1000 |