From: Hao Wu <hao.wu@xxxxxxxxx> This patch enables penwell USB OTG Transceiver driver USB Charger Detection support. It can detect different types of USB charger based on MSIC. SDP (Standard Downstream Port - USB Host port charger), DCP (Dedicated Charging Port - USB Wall charger), CDP (Charging Downstream Port - Special USB Host port charger). Signed-off-by: Hao Wu <hao.wu@xxxxxxxxx> --- drivers/usb/otg/penwell_otg.c | 255 ++++++++++++++++++++++++++++++++++++--- include/linux/usb/penwell_otg.h | 68 ++++++++--- 2 files changed, 291 insertions(+), 32 deletions(-) diff --git a/drivers/usb/otg/penwell_otg.c b/drivers/usb/otg/penwell_otg.c index 52636c7..8266ad0 100644 --- a/drivers/usb/otg/penwell_otg.c +++ b/drivers/usb/otg/penwell_otg.c @@ -61,6 +61,7 @@ static int penwell_otg_set_host(struct otg_transceiver *otg, static int penwell_otg_set_peripheral(struct otg_transceiver *otg, struct usb_gadget *gadget); static int penwell_otg_start_srp(struct otg_transceiver *otg); +static int penwell_otg_msic_write(u16 addr, u8 data); static DEFINE_PCI_DEVICE_TABLE(pci_ids) = {{ .class = ((PCI_CLASS_SERIAL_USB << 8) | 0x20), @@ -155,19 +156,22 @@ static int penwell_otg_set_power(struct otg_transceiver *otg, static void penwell_otg_phy_enable(int on) { struct penwell_otg *pnw = the_transceiver; - u16 addr; u8 data; dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off"); - addr = MSIC_VUSB330CNT; data = on ? 0x37 : 0x24; - if (intel_scu_ipc_iowrite8(addr, data)) { - dev_err(pnw->dev, "Fail to access register for" - " OTG PHY power - write reg 0x%x failed.\n", addr); + mutex_lock(&pnw->msic_mutex); + + if (penwell_otg_msic_write(MSIC_VUSB330CNT, data)) { + mutex_unlock(&pnw->msic_mutex); + dev_err(pnw->dev, "Fail to enable PHY power\n"); return; } + + mutex_unlock(&pnw->msic_mutex); + dev_dbg(pnw->dev, "%s <---\n", __func__); } @@ -175,25 +179,25 @@ static void penwell_otg_phy_enable(int on) static int penwell_otg_set_vbus(struct otg_transceiver *otg, bool enabled) { struct penwell_otg *pnw = the_transceiver; - u16 addr; - u8 data, mask; + u8 data; + int retval; dev_dbg(pnw->dev, "%s ---> %s\n", __func__, enabled ? "on" : "off"); - addr = MSIC_VOTGCNT; data = enabled ? VOTGEN : 0; - mask = VOTGEN; - if (intel_scu_ipc_update_register(addr, data, mask)) { - dev_err(pnw->dev, "Fail to drive power on OTG Port - " - "update register 0x%x failed.\n", addr); - return -EBUSY; - } + mutex_lock(&pnw->msic_mutex); + + retval = intel_scu_ipc_update_register(MSIC_VOTGCNT, data, VOTGEN); + + if (retval) + dev_err(pnw->dev, "Fail to set power on OTG Port\n"); + + mutex_unlock(&pnw->msic_mutex); - dev_dbg(pnw->dev, "VOTGCNT val = 0x%x", data); dev_dbg(pnw->dev, "%s <---\n", __func__); - return 0; + return retval; } static int penwell_otg_ulpi_run(void) @@ -416,6 +420,193 @@ static void penwell_otg_HABA(int on) pnw->iotg.base + CI_OTGSC); } +/* write 8bit msic register */ +static int penwell_otg_msic_write(u16 addr, u8 data) +{ + struct penwell_otg *pnw = the_transceiver; + int retval = 0; + + retval = intel_scu_ipc_iowrite8(addr, data); + if (retval) { + dev_warn(pnw->dev, "Failed to write MSIC register %x\n", addr); + return retval; + } + + return retval; +} + +/* USB related register in MSIC can be access via SPI address and ulpi address + * Access the control register to switch */ +static void penwell_otg_msic_spi_access(bool enabled) +{ + struct penwell_otg *pnw = the_transceiver; + u8 data; + + dev_dbg(pnw->dev, "%s --->\n", __func__); + + /* Set ULPI ACCESS MODE */ + data = enabled ? SPIMODE : 0; + + penwell_otg_msic_write(MSIC_ULPIACCESSMODE, data); + + dev_dbg(pnw->dev, "%s <---\n", __func__); +} + +/* USB Battery Charger detection related functions */ +/* Data contact detection is the first step for charger detection */ +static int penwell_otg_data_contact_detect(void) +{ + struct penwell_otg *pnw = the_transceiver; + u8 data; + int count = 10; + int retval = 0; + + dev_dbg(pnw->dev, "%s --->\n", __func__); + + /* Enable SPI access */ + penwell_otg_msic_spi_access(true); + + /* Set POWER_CTRL_CLR */ + retval = penwell_otg_msic_write(MSIC_PWRCTRLCLR, DPVSRCEN); + if (retval) + return retval; + + /* Set FUNC_CTRL_SET */ + retval = penwell_otg_msic_write(MSIC_FUNCTRLSET, OPMODE0); + if (retval) + return retval; + + /* Set FUNC_CTRL_CLR */ + retval = penwell_otg_msic_write(MSIC_FUNCTRLCLR, OPMODE1); + if (retval) + return retval; + + /* Set OTG_CTRL_CLR */ + retval = penwell_otg_msic_write(MSIC_OTGCTRLCLR, + DMPULLDOWN | DPPULLDOWN); + if (retval) + return retval; + + /* Set POWER_CTRL_CLR */ + retval = penwell_otg_msic_write(MSIC_PWRCTRLCLR, SWCNTRL); + if (retval) + return retval; + + retval = penwell_otg_msic_write(MSIC_VS3SET, DATACONEN | SWUSBDET); + if (retval) + return retval; + + dev_dbg(pnw->dev, "Start Polling for Data contact detection!\n"); + + while (count) { + retval = intel_scu_ipc_ioread8(MSIC_PWRCTRL, &data); + if (retval) { + dev_warn(pnw->dev, "Failed to read MSIC register\n"); + return retval; + } + + if (data & DPVSRCEN) { + dev_dbg(pnw->dev, "Data contact detected!\n"); + return 0; + } + count--; + /* Interval is 50ms */ + msleep(50); + } + + dev_dbg(pnw->dev, "Data contact Timeout\n"); + + retval = penwell_otg_msic_write(MSIC_VS3CLR, DATACONEN | SWUSBDET); + if (retval) + return retval; + + udelay(100); + + retval = penwell_otg_msic_write(MSIC_VS3SET, SWUSBDET); + if (retval) + return retval; + + dev_dbg(pnw->dev, "%s <---\n", __func__); + return 0; +} + +static int penwell_otg_charger_detect(void) +{ + struct penwell_otg *pnw = the_transceiver; + + dev_dbg(pnw->dev, "%s --->\n", __func__); + + msleep(125); + + dev_dbg(pnw->dev, "%s <---\n", __func__); + + return 0; +} + +static int penwell_otg_charger_type_detect(void) +{ + struct penwell_otg *pnw = the_transceiver; + enum usb_charger_type charger; + u8 data; + int retval; + + dev_dbg(pnw->dev, "%s --->\n", __func__); + + retval = penwell_otg_msic_write(MSIC_VS3CLR, DATACONEN); + if (retval) + return retval; + + retval = penwell_otg_msic_write(MSIC_PWRCTRLSET, DPWKPUEN | SWCNTRL); + if (retval) + return retval; + + retval = penwell_otg_msic_write(MSIC_PWRCTRLCLR, DPVSRCEN); + if (retval) + return retval; + + retval = penwell_otg_msic_write(MSIC_OTGCTRLCLR, + DMPULLDOWN | DPPULLDOWN); + if (retval) + return retval; + + msleep(55); + + retval = penwell_otg_msic_write(MSIC_PWRCTRLCLR, + SWCNTRL | DPWKPUEN | HWDET); + if (retval) + return retval; + + usleep_range(800, 1000); + + /* Enable ULPI mode */ + penwell_otg_msic_spi_access(false); + + retval = intel_scu_ipc_ioread8(MSIC_SPWRSRINT1, &data); + if (retval) { + dev_warn(pnw->dev, "Failed to read MSIC register\n"); + return retval; + } + + switch (data & MSIC_SPWRSRINT1_MASK) { + case SPWRSRINT1_SDP: + charger = CHRG_SDP; + break; + case SPWRSRINT1_DCP: + charger = CHRG_DCP; + break; + case SPWRSRINT1_CDP: + charger = CHRG_CDP; + break; + default: + charger = CHRG_UNKNOWN; + break; + } + + dev_dbg(pnw->dev, "%s <---\n", __func__); + + return charger; +} + void penwell_otg_nsf_msg(unsigned long indicator) { switch (indicator) { @@ -851,6 +1042,8 @@ static void penwell_otg_work(struct work_struct *work) struct penwell_otg, work); struct intel_mid_otg_xceiv *iotg = &pnw->iotg; struct otg_hsm *hsm = &iotg->hsm; + enum usb_charger_type charger_type; + int retval; dev_dbg(pnw->dev, "old state = %s\n", state_string(iotg->otg.state)); @@ -913,6 +1106,34 @@ static void penwell_otg_work(struct work_struct *work) hsm->b_sess_end = 0; hsm->a_bus_suspend = 0; + /* Start USB Battery charger detection flow */ + + mutex_lock(&pnw->msic_mutex); + /* Enable data contact detection */ + penwell_otg_data_contact_detect(); + /* Enable charger detection functionality */ + penwell_otg_charger_detect(); + retval = penwell_otg_charger_type_detect(); + mutex_unlock(&pnw->msic_mutex); + if (retval < 0) { + dev_warn(pnw->dev, "Charger detect failure\n"); + break; + } else + charger_type = retval; + + if (charger_type == CHRG_DCP) { + dev_info(pnw->dev, "DCP detected\n"); + penwell_otg_phy_low_power(1); + iotg->otg.set_power(&iotg->otg, CHRG_CURR_DCP); + break; + } else if (charger_type == CHRG_CDP) { + dev_info(pnw->dev, "CDP detected\n"); + iotg->otg.set_power(&iotg->otg, CHRG_CURR_CDP); + } else if (charger_type == CHRG_SDP) + dev_info(pnw->dev, "SDP detected\n"); + else if (charger_type == CHRG_UNKNOWN) + dev_info(pnw->dev, "Unknown Charger Found\n"); + if (iotg->start_peripheral) { iotg->start_peripheral(iotg); iotg->otg.state = OTG_STATE_B_PERIPHERAL; @@ -920,7 +1141,6 @@ static void penwell_otg_work(struct work_struct *work) dev_dbg(pnw->dev, "client driver not loaded\n"); break; } - } else if ((hsm->b_bus_req || hsm->power_up || hsm->adp_change) && !hsm->b_srp_fail_tmr) { if ((hsm->b_ssend_srp && hsm->b_se0_srp) || @@ -2056,6 +2276,7 @@ static int penwell_otg_probe(struct pci_dev *pdev, goto err; } + mutex_init(&pnw->msic_mutex); pnw->msic = penwell_otg_check_msic(); penwell_otg_phy_enable(1); diff --git a/include/linux/usb/penwell_otg.h b/include/linux/usb/penwell_otg.h index 014eac9..d5cefcf 100644 --- a/include/linux/usb/penwell_otg.h +++ b/include/linux/usb/penwell_otg.h @@ -112,31 +112,33 @@ # define SUSBCHPDET BIT(6) # define SUSBDCDET BIT(2) # define MSIC_SPWRSRINT1_MASK (BIT(6) | BIT(2)) -# define SPWRSRINT1_CHRG_PORT BIT(6) -# define SPWRSRINT1_HOST_PORT 0 -# define SPWRSRINT1_DEDT_CHRG (BIT(6) | BIT(2)) +# define SPWRSRINT1_CDP BIT(6) +# define SPWRSRINT1_SDP 0 +# define SPWRSRINT1_DCP BIT(2) #define MSIC_IS4SET 0x2c8 /* Intel Specific */ # define IS4_CHGDSERXDPINV BIT(5) +#define MSIC_OTGCTRL 0x39c #define MSIC_OTGCTRLSET 0x340 #define MSIC_OTGCTRLCLR 0x341 -# define DMPULLDOWNCLR BIT(2) -# define DPPULLDOWNCLR BIT(1) +# define DMPULLDOWN BIT(2) +# define DPPULLDOWN BIT(1) +#define MSIC_PWRCTRL 0x3b5 #define MSIC_PWRCTRLSET 0x342 -# define DPWKPUENSET BIT(4) -# define SWCNTRLSET BIT(0) #define MSIC_PWRCTRLCLR 0x343 -# define DPVSRCENCLR BIT(6) -# define SWCNTRLCLR BIT(0) +# define HWDET BIT(7) +# define DPVSRCEN BIT(6) +# define DPWKPUEN BIT(4) +# define SWCNTRL BIT(0) +#define MSIC_FUNCTRL 0x398 #define MSIC_FUNCTRLSET 0x344 -# define OPMODESET0 BIT(3) #define MSIC_FUNCTRLCLR 0x345 -# define OPMODECLR1 BIT(4) +# define OPMODE1 BIT(4) +# define OPMODE0 BIT(3) +#define MSIC_VS3 0x3b9 #define MSIC_VS3SET 0x346 /* Vendor Specific */ -# define SWUSBDETSET BIT(4) -# define DATACONENSET BIT(3) #define MSIC_VS3CLR 0x347 -# define SWUSBDETCLR BIT(4) -# define DATACONENCLR BIT(3) +# define SWUSBDET BIT(4) +# define DATACONEN BIT(3) #define MSIC_ULPIACCESSMODE 0x348 # define SPIMODE BIT(0) @@ -210,6 +212,10 @@ #define FS_ADPI_MASK (ADPIS_ADPRAMPI | ADPIS_SNSMISSI | ADPIS_PRBTRGI) +/* define Data connect checking timeout and polling interval */ +#define DATACON_TIMEOUT 1000 +#define DATACON_INTERVAL 50 + enum penwell_otg_timer_type { TA_WAIT_VRISE_TMR, TA_WAIT_BCON_TMR, @@ -242,11 +248,42 @@ enum msic_vendor { MSIC_VD_UNKNOWN }; +/* charger defined in BC 1.1 */ +enum usb_charger_type { + CHRG_UNKNOWN, + CHRG_SDP, /* Standard Downstream Port */ + CHRG_CDP, /* Charging Downstream Port */ + CHRG_DCP, /* Dedicated Charging Port */ + CHRG_ACA /* Accessory Charger Adapter */ +}; + struct adp_status { struct completion adp_comp; u8 t_adp_rise; }; +/* OTG Battery Charging capability is used in charger capability detection */ +struct otg_bc_cap { + enum usb_charger_type chrg_type; + unsigned int mA; +#define CHRG_CURR_UNKNOWN 0 +#define CHRG_CURR_DISCONN 0 +#define CHRG_CURR_SDP_SUSP 2 +#define CHRG_CURR_SDP_LOW 100 +#define CHRG_CURR_SDP_HIGH 500 +#define CHRG_CURR_CDP 500 +#define CHRG_CURR_CDP_HS 950 +#define CHRG_CURR_DCP 1500 +#define CHRG_CURR_ACA 1500 +}; + +/* define event ids to notify battery driver */ +#define USBCHRG_EVENT_CONNECT 1 +#define USBCHRG_EVENT_DISCONN 2 +#define USBCHRG_EVENT_SUSPEND 3 +#define USBCHRG_EVENT_RESUME 4 +#define USBCHRG_EVENT_UPDATE 5 + struct penwell_otg { struct intel_mid_otg_xceiv iotg; struct device *dev; @@ -261,6 +298,7 @@ struct penwell_otg { struct timer_list hsm_timer; struct timer_list hnp_poll_timer; + struct mutex msic_mutex; enum msic_vendor msic; struct notifier_block iotg_notifier; -- 1.6.0.6 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html