>From 98d21b9084d9f943044402949438d8189151c82f Mon Sep 17 00:00:00 2001
From: Hao Wu <[email protected]>
Date: Thu, 21 Oct 2010 11:27:31 +0800
Subject: [PATCH] usb: penwell_otg: add USB charger detection.

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).

*CDP case is not tested on real hw yet due to no such device available.

Signed-off-by: Hao Wu <[email protected]>
---
 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 7eca376..52d8528 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 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);
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

Attachment: 0001-usb-penwell_otg-add-USB-charger-detection.patch
Description: 0001-usb-penwell_otg-add-USB-charger-detection.patch

_______________________________________________
Meego-kernel mailing list
[email protected]
http://lists.meego.com/listinfo/meego-kernel

Reply via email to