usb: penwell_otg: add USB charger detection
authorHao Wu <hao.wu@intel.com>
Thu, 25 Aug 2011 11:20:35 +0000 (12:20 +0100)
committermgross <mark.gross@intel.com>
Wed, 9 Nov 2011 20:38:24 +0000 (12:38 -0800)
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).

Change-Id: I34009bb775bba53357f91a23c9190317d69d3654
Signed-off-by: Hao Wu <hao.wu@intel.com>
Signed-off-by: Dirk Brandewie <dirk.brandewie@gmail.com>
drivers/usb/otg/penwell_otg.c
include/linux/usb/penwell_otg.h

index 7eca376..52d8528 100644 (file)
@@ -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 const struct pci_device_id 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;
+
+       msleep(1);
+
+       /* 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));
@@ -911,6 +1104,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;
@@ -918,7 +1139,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) ||
@@ -2054,6 +2274,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);
index 014eac9..d5cefcf 100644 (file)
 #      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)
 
 
 #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;