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),
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__);
}
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)
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) {
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));
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;
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) ||
goto err;
}
+ mutex_init(&pnw->msic_mutex);
pnw->msic = penwell_otg_check_msic();
penwell_otg_phy_enable(1);
# 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,
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;
struct timer_list hsm_timer;
struct timer_list hnp_poll_timer;
+ struct mutex msic_mutex;
enum msic_vendor msic;
struct notifier_block iotg_notifier;