USB: OTG: msm: Implement charger detection

Implement good battery algorithm defined in the battery charging V1.2 spec
for detecting different charging ports.  USB hardware is put into low power
mode when connected to a dedicated charging port.  vbus_draw and set_power
methods are implemented for determining the allowed current from Host in
different states (un-configured/suspend/configured).

The charger block is implemented using vendor specific registers and the
PHY used in MSM8960(28nm PHY) different from older targets like MSM8x60
and MSM7x30(45nm PHY).  The PHY vendor and product id registers are not
implemented in the above chipsets.  Hence PHY type is passed via platform
data.

Signed-off-by: Pavankumar Kondeti <pkondeti@codeaurora.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Pavankumar Kondeti 2011-05-04 10:19:47 +05:30 committed by Greg Kroah-Hartman
parent 0f73cac8e4
commit d860852e08
3 changed files with 457 additions and 5 deletions

View File

@ -2506,6 +2506,15 @@ out:
return ret; return ret;
} }
static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned mA)
{
struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget);
if (udc->transceiver)
return otg_set_power(udc->transceiver, mA);
return -ENOTSUPP;
}
/** /**
* Device operations part of the API to the USB controller hardware, * Device operations part of the API to the USB controller hardware,
* which don't involve endpoints (or i/o) * which don't involve endpoints (or i/o)
@ -2514,6 +2523,7 @@ out:
static const struct usb_gadget_ops usb_gadget_ops = { static const struct usb_gadget_ops usb_gadget_ops = {
.vbus_session = ci13xxx_vbus_session, .vbus_session = ci13xxx_vbus_session,
.wakeup = ci13xxx_wakeup, .wakeup = ci13xxx_wakeup,
.vbus_draw = ci13xxx_vbus_draw,
}; };
/** /**

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. /* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and * it under the terms of the GNU General Public License version 2 and
@ -409,6 +409,33 @@ skip_phy_resume:
} }
#endif #endif
static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA)
{
if (motg->cur_power == mA)
return;
/* TODO: Notify PMIC about available current */
dev_info(motg->otg.dev, "Avail curr from USB = %u\n", mA);
motg->cur_power = mA;
}
static int msm_otg_set_power(struct otg_transceiver *otg, unsigned mA)
{
struct msm_otg *motg = container_of(otg, struct msm_otg, otg);
/*
* Gadget driver uses set_power method to notify about the
* available current based on suspend/configured states.
*
* IDEV_CHG can be drawn irrespective of suspend/un-configured
* states when CDP/ACA is connected.
*/
if (motg->chg_type == USB_SDP_CHARGER)
msm_otg_notify_charger(motg, mA);
return 0;
}
static void msm_otg_start_host(struct otg_transceiver *otg, int on) static void msm_otg_start_host(struct otg_transceiver *otg, int on)
{ {
struct msm_otg *motg = container_of(otg, struct msm_otg, otg); struct msm_otg *motg = container_of(otg, struct msm_otg, otg);
@ -563,6 +590,306 @@ static int msm_otg_set_peripheral(struct otg_transceiver *otg,
return 0; return 0;
} }
static bool msm_chg_check_secondary_det(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 chg_det;
bool ret = false;
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x34);
ret = chg_det & (1 << 4);
break;
case SNPS_28NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x87);
ret = chg_det & 1;
break;
default:
break;
}
return ret;
}
static void msm_chg_enable_secondary_det(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 chg_det;
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x34);
/* Turn off charger block */
chg_det |= ~(1 << 1);
ulpi_write(otg, chg_det, 0x34);
udelay(20);
/* control chg block via ULPI */
chg_det &= ~(1 << 3);
ulpi_write(otg, chg_det, 0x34);
/* put it in host mode for enabling D- source */
chg_det &= ~(1 << 2);
ulpi_write(otg, chg_det, 0x34);
/* Turn on chg detect block */
chg_det &= ~(1 << 1);
ulpi_write(otg, chg_det, 0x34);
udelay(20);
/* enable chg detection */
chg_det &= ~(1 << 0);
ulpi_write(otg, chg_det, 0x34);
break;
case SNPS_28NM_INTEGRATED_PHY:
/*
* Configure DM as current source, DP as current sink
* and enable battery charging comparators.
*/
ulpi_write(otg, 0x8, 0x85);
ulpi_write(otg, 0x2, 0x85);
ulpi_write(otg, 0x1, 0x85);
break;
default:
break;
}
}
static bool msm_chg_check_primary_det(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 chg_det;
bool ret = false;
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x34);
ret = chg_det & (1 << 4);
break;
case SNPS_28NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x87);
ret = chg_det & 1;
break;
default:
break;
}
return ret;
}
static void msm_chg_enable_primary_det(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 chg_det;
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x34);
/* enable chg detection */
chg_det &= ~(1 << 0);
ulpi_write(otg, chg_det, 0x34);
break;
case SNPS_28NM_INTEGRATED_PHY:
/*
* Configure DP as current source, DM as current sink
* and enable battery charging comparators.
*/
ulpi_write(otg, 0x2, 0x85);
ulpi_write(otg, 0x1, 0x85);
break;
default:
break;
}
}
static bool msm_chg_check_dcd(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 line_state;
bool ret = false;
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
line_state = ulpi_read(otg, 0x15);
ret = !(line_state & 1);
break;
case SNPS_28NM_INTEGRATED_PHY:
line_state = ulpi_read(otg, 0x87);
ret = line_state & 2;
break;
default:
break;
}
return ret;
}
static void msm_chg_disable_dcd(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 chg_det;
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x34);
chg_det &= ~(1 << 5);
ulpi_write(otg, chg_det, 0x34);
break;
case SNPS_28NM_INTEGRATED_PHY:
ulpi_write(otg, 0x10, 0x86);
break;
default:
break;
}
}
static void msm_chg_enable_dcd(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 chg_det;
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x34);
/* Turn on D+ current source */
chg_det |= (1 << 5);
ulpi_write(otg, chg_det, 0x34);
break;
case SNPS_28NM_INTEGRATED_PHY:
/* Data contact detection enable */
ulpi_write(otg, 0x10, 0x85);
break;
default:
break;
}
}
static void msm_chg_block_on(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 func_ctrl, chg_det;
/* put the controller in non-driving mode */
func_ctrl = ulpi_read(otg, ULPI_FUNC_CTRL);
func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK;
func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING;
ulpi_write(otg, func_ctrl, ULPI_FUNC_CTRL);
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x34);
/* control chg block via ULPI */
chg_det &= ~(1 << 3);
ulpi_write(otg, chg_det, 0x34);
/* Turn on chg detect block */
chg_det &= ~(1 << 1);
ulpi_write(otg, chg_det, 0x34);
udelay(20);
break;
case SNPS_28NM_INTEGRATED_PHY:
/* Clear charger detecting control bits */
ulpi_write(otg, 0x3F, 0x86);
/* Clear alt interrupt latch and enable bits */
ulpi_write(otg, 0x1F, 0x92);
ulpi_write(otg, 0x1F, 0x95);
udelay(100);
break;
default:
break;
}
}
static void msm_chg_block_off(struct msm_otg *motg)
{
struct otg_transceiver *otg = &motg->otg;
u32 func_ctrl, chg_det;
switch (motg->pdata->phy_type) {
case CI_45NM_INTEGRATED_PHY:
chg_det = ulpi_read(otg, 0x34);
/* Turn off charger block */
chg_det |= ~(1 << 1);
ulpi_write(otg, chg_det, 0x34);
break;
case SNPS_28NM_INTEGRATED_PHY:
/* Clear charger detecting control bits */
ulpi_write(otg, 0x3F, 0x86);
/* Clear alt interrupt latch and enable bits */
ulpi_write(otg, 0x1F, 0x92);
ulpi_write(otg, 0x1F, 0x95);
break;
default:
break;
}
/* put the controller in normal mode */
func_ctrl = ulpi_read(otg, ULPI_FUNC_CTRL);
func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK;
func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL;
ulpi_write(otg, func_ctrl, ULPI_FUNC_CTRL);
}
#define MSM_CHG_DCD_POLL_TIME (100 * HZ/1000) /* 100 msec */
#define MSM_CHG_DCD_MAX_RETRIES 6 /* Tdcd_tmout = 6 * 100 msec */
#define MSM_CHG_PRIMARY_DET_TIME (40 * HZ/1000) /* TVDPSRC_ON */
#define MSM_CHG_SECONDARY_DET_TIME (40 * HZ/1000) /* TVDMSRC_ON */
static void msm_chg_detect_work(struct work_struct *w)
{
struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work);
struct otg_transceiver *otg = &motg->otg;
bool is_dcd, tmout, vout;
unsigned long delay;
dev_dbg(otg->dev, "chg detection work\n");
switch (motg->chg_state) {
case USB_CHG_STATE_UNDEFINED:
pm_runtime_get_sync(otg->dev);
msm_chg_block_on(motg);
msm_chg_enable_dcd(motg);
motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
motg->dcd_retries = 0;
delay = MSM_CHG_DCD_POLL_TIME;
break;
case USB_CHG_STATE_WAIT_FOR_DCD:
is_dcd = msm_chg_check_dcd(motg);
tmout = ++motg->dcd_retries == MSM_CHG_DCD_MAX_RETRIES;
if (is_dcd || tmout) {
msm_chg_disable_dcd(motg);
msm_chg_enable_primary_det(motg);
delay = MSM_CHG_PRIMARY_DET_TIME;
motg->chg_state = USB_CHG_STATE_DCD_DONE;
} else {
delay = MSM_CHG_DCD_POLL_TIME;
}
break;
case USB_CHG_STATE_DCD_DONE:
vout = msm_chg_check_primary_det(motg);
if (vout) {
msm_chg_enable_secondary_det(motg);
delay = MSM_CHG_SECONDARY_DET_TIME;
motg->chg_state = USB_CHG_STATE_PRIMARY_DONE;
} else {
motg->chg_type = USB_SDP_CHARGER;
motg->chg_state = USB_CHG_STATE_DETECTED;
delay = 0;
}
break;
case USB_CHG_STATE_PRIMARY_DONE:
vout = msm_chg_check_secondary_det(motg);
if (vout)
motg->chg_type = USB_DCP_CHARGER;
else
motg->chg_type = USB_CDP_CHARGER;
motg->chg_state = USB_CHG_STATE_SECONDARY_DONE;
/* fall through */
case USB_CHG_STATE_SECONDARY_DONE:
motg->chg_state = USB_CHG_STATE_DETECTED;
case USB_CHG_STATE_DETECTED:
msm_chg_block_off(motg);
dev_dbg(otg->dev, "charger = %d\n", motg->chg_type);
schedule_work(&motg->sm_work);
return;
default:
return;
}
schedule_delayed_work(&motg->chg_work, delay);
}
/* /*
* We support OTG, Peripheral only and Host only configurations. In case * We support OTG, Peripheral only and Host only configurations. In case
* of OTG, mode switch (host-->peripheral/peripheral-->host) can happen * of OTG, mode switch (host-->peripheral/peripheral-->host) can happen
@ -633,9 +960,48 @@ static void msm_otg_sm_work(struct work_struct *w)
writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC);
msm_otg_start_host(otg, 1); msm_otg_start_host(otg, 1);
otg->state = OTG_STATE_A_HOST; otg->state = OTG_STATE_A_HOST;
} else if (test_bit(B_SESS_VLD, &motg->inputs) && otg->gadget) { } else if (test_bit(B_SESS_VLD, &motg->inputs)) {
msm_otg_start_peripheral(otg, 1); switch (motg->chg_state) {
otg->state = OTG_STATE_B_PERIPHERAL; case USB_CHG_STATE_UNDEFINED:
msm_chg_detect_work(&motg->chg_work.work);
break;
case USB_CHG_STATE_DETECTED:
switch (motg->chg_type) {
case USB_DCP_CHARGER:
msm_otg_notify_charger(motg,
IDEV_CHG_MAX);
break;
case USB_CDP_CHARGER:
msm_otg_notify_charger(motg,
IDEV_CHG_MAX);
msm_otg_start_peripheral(otg, 1);
otg->state = OTG_STATE_B_PERIPHERAL;
break;
case USB_SDP_CHARGER:
msm_otg_notify_charger(motg, IUNIT);
msm_otg_start_peripheral(otg, 1);
otg->state = OTG_STATE_B_PERIPHERAL;
break;
default:
break;
}
break;
default:
break;
}
} else {
/*
* If charger detection work is pending, decrement
* the pm usage counter to balance with the one that
* is incremented in charger detection work.
*/
if (cancel_delayed_work_sync(&motg->chg_work)) {
pm_runtime_put_sync(otg->dev);
msm_otg_reset(otg);
}
msm_otg_notify_charger(motg, 0);
motg->chg_state = USB_CHG_STATE_UNDEFINED;
motg->chg_type = USB_INVALID_CHARGER;
} }
pm_runtime_put_sync(otg->dev); pm_runtime_put_sync(otg->dev);
break; break;
@ -643,7 +1009,10 @@ static void msm_otg_sm_work(struct work_struct *w)
dev_dbg(otg->dev, "OTG_STATE_B_PERIPHERAL state\n"); dev_dbg(otg->dev, "OTG_STATE_B_PERIPHERAL state\n");
if (!test_bit(B_SESS_VLD, &motg->inputs) || if (!test_bit(B_SESS_VLD, &motg->inputs) ||
!test_bit(ID, &motg->inputs)) { !test_bit(ID, &motg->inputs)) {
msm_otg_notify_charger(motg, 0);
msm_otg_start_peripheral(otg, 0); msm_otg_start_peripheral(otg, 0);
motg->chg_state = USB_CHG_STATE_UNDEFINED;
motg->chg_type = USB_INVALID_CHARGER;
otg->state = OTG_STATE_B_IDLE; otg->state = OTG_STATE_B_IDLE;
msm_otg_reset(otg); msm_otg_reset(otg);
schedule_work(w); schedule_work(w);
@ -935,6 +1304,7 @@ static int __init msm_otg_probe(struct platform_device *pdev)
writel(0, USB_OTGSC); writel(0, USB_OTGSC);
INIT_WORK(&motg->sm_work, msm_otg_sm_work); INIT_WORK(&motg->sm_work, msm_otg_sm_work);
INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work);
ret = request_irq(motg->irq, msm_otg_irq, IRQF_SHARED, ret = request_irq(motg->irq, msm_otg_irq, IRQF_SHARED,
"msm_otg", motg); "msm_otg", motg);
if (ret) { if (ret) {
@ -945,6 +1315,7 @@ static int __init msm_otg_probe(struct platform_device *pdev)
otg->init = msm_otg_reset; otg->init = msm_otg_reset;
otg->set_host = msm_otg_set_host; otg->set_host = msm_otg_set_host;
otg->set_peripheral = msm_otg_set_peripheral; otg->set_peripheral = msm_otg_set_peripheral;
otg->set_power = msm_otg_set_power;
otg->io_ops = &msm_otg_io_ops; otg->io_ops = &msm_otg_io_ops;
@ -1004,6 +1375,7 @@ static int __devexit msm_otg_remove(struct platform_device *pdev)
return -EBUSY; return -EBUSY;
msm_otg_debugfs_cleanup(); msm_otg_debugfs_cleanup();
cancel_delayed_work_sync(&motg->chg_work);
cancel_work_sync(&motg->sm_work); cancel_work_sync(&motg->sm_work);
pm_runtime_resume(&pdev->dev); pm_runtime_resume(&pdev->dev);

View File

@ -2,7 +2,7 @@
* *
* Copyright (C) 2008 Google, Inc. * Copyright (C) 2008 Google, Inc.
* Author: Brian Swetland <swetland@google.com> * Author: Brian Swetland <swetland@google.com>
* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
* *
* This software is licensed under the terms of the GNU General Public * This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and * License version 2, as published by the Free Software Foundation, and
@ -53,6 +53,64 @@ enum otg_control_type {
OTG_USER_CONTROL, OTG_USER_CONTROL,
}; };
/**
* PHY used in
*
* INVALID_PHY Unsupported PHY
* CI_45NM_INTEGRATED_PHY Chipidea 45nm integrated PHY
* SNPS_28NM_INTEGRATED_PHY Synopsis 28nm integrated PHY
*
*/
enum msm_usb_phy_type {
INVALID_PHY = 0,
CI_45NM_INTEGRATED_PHY,
SNPS_28NM_INTEGRATED_PHY,
};
#define IDEV_CHG_MAX 1500
#define IUNIT 100
/**
* Different states involved in USB charger detection.
*
* USB_CHG_STATE_UNDEFINED USB charger is not connected or detection
* process is not yet started.
* USB_CHG_STATE_WAIT_FOR_DCD Waiting for Data pins contact.
* USB_CHG_STATE_DCD_DONE Data pin contact is detected.
* USB_CHG_STATE_PRIMARY_DONE Primary detection is completed (Detects
* between SDP and DCP/CDP).
* USB_CHG_STATE_SECONDARY_DONE Secondary detection is completed (Detects
* between DCP and CDP).
* USB_CHG_STATE_DETECTED USB charger type is determined.
*
*/
enum usb_chg_state {
USB_CHG_STATE_UNDEFINED = 0,
USB_CHG_STATE_WAIT_FOR_DCD,
USB_CHG_STATE_DCD_DONE,
USB_CHG_STATE_PRIMARY_DONE,
USB_CHG_STATE_SECONDARY_DONE,
USB_CHG_STATE_DETECTED,
};
/**
* USB charger types
*
* USB_INVALID_CHARGER Invalid USB charger.
* USB_SDP_CHARGER Standard downstream port. Refers to a downstream port
* on USB2.0 compliant host/hub.
* USB_DCP_CHARGER Dedicated charger port (AC charger/ Wall charger).
* USB_CDP_CHARGER Charging downstream port. Enumeration can happen and
* IDEV_CHG_MAX can be drawn irrespective of USB state.
*
*/
enum usb_chg_type {
USB_INVALID_CHARGER = 0,
USB_SDP_CHARGER,
USB_DCP_CHARGER,
USB_CDP_CHARGER,
};
/** /**
* struct msm_otg_platform_data - platform device data * struct msm_otg_platform_data - platform device data
* for msm_otg driver. * for msm_otg driver.
@ -74,6 +132,7 @@ struct msm_otg_platform_data {
enum usb_mode_type mode; enum usb_mode_type mode;
enum otg_control_type otg_control; enum otg_control_type otg_control;
enum usb_mode_type default_mode; enum usb_mode_type default_mode;
enum msm_usb_phy_type phy_type;
void (*setup_gpio)(enum usb_otg_state state); void (*setup_gpio)(enum usb_otg_state state);
char *pclk_src_name; char *pclk_src_name;
}; };
@ -93,6 +152,12 @@ struct msm_otg_platform_data {
* @sm_work: OTG state machine work. * @sm_work: OTG state machine work.
* @in_lpm: indicates low power mode (LPM) state. * @in_lpm: indicates low power mode (LPM) state.
* @async_int: Async interrupt arrived. * @async_int: Async interrupt arrived.
* @cur_power: The amount of mA available from downstream port.
* @chg_work: Charger detection work.
* @chg_state: The state of charger detection process.
* @chg_type: The type of charger attached.
* @dcd_retires: The retry count used to track Data contact
* detection process.
*/ */
struct msm_otg { struct msm_otg {
struct otg_transceiver otg; struct otg_transceiver otg;
@ -110,6 +175,11 @@ struct msm_otg {
struct work_struct sm_work; struct work_struct sm_work;
atomic_t in_lpm; atomic_t in_lpm;
int async_int; int async_int;
unsigned cur_power;
struct delayed_work chg_work;
enum usb_chg_state chg_state;
enum usb_chg_type chg_type;
u8 dcd_retries;
}; };
#endif #endif