android_kernel_lenovo_1050f/drivers/usb/phy/penwell_otg.c

5804 lines
143 KiB
C

/*
* Intel Penwell USB OTG transceiver driver
* Copyright (C) 2009 - 2010, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
/* This driver helps to switch Penwell OTG controller function between host
* and peripheral. It works with EHCI driver and Penwell client controller
* driver together.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/moduleparam.h>
#include <linux/gpio.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/usb/otg.h>
#include <linux/notifier.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <linux/wakelock.h>
#include <asm/intel_scu_pmic.h>
#include <asm/intel_scu_ipc.h>
#include <asm/intel-mid.h>
#include "../core/usb.h"
#include <linux/intel_mid_pm.h>
#if defined(CONFIG_ME372CG_BATTERY_SMB345)
extern int setSMB345Charger(int usb_state);
#endif
#include <linux/usb/penwell_otg.h>
#define DRIVER_DESC "Intel Penwell USB OTG transceiver driver"
#define DRIVER_VERSION "0.8"
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR("Henry Yuan <hang.yuan@intel.com>, Hao Wu <hao.wu@intel.com>");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
static const char driver_name[] = "penwell_otg";
static void penwell_otg_remove(struct pci_dev *pdev);
static int penwell_otg_set_host(struct usb_otg *otg, struct usb_bus *host);
static int penwell_otg_set_peripheral(struct usb_otg *otg,
struct usb_gadget *gadget);
static int penwell_otg_start_srp(struct usb_otg *otg);
static void penwell_otg_mon_bus(void);
static int penwell_otg_msic_write(u16 addr, u8 data);
static void penwell_otg_phy_low_power(int on);
static int penwell_otg_ulpi_read(struct intel_mid_otg_xceiv *iotg,
u8 reg, u8 *val);
static int penwell_otg_ulpi_write(struct intel_mid_otg_xceiv *iotg,
u8 reg, u8 val);
static void penwell_spi_reset_phy(void);
static int penwell_otg_charger_hwdet(bool enable);
static void update_hsm(void);
static void set_client_mode(void);
#ifdef CONFIG_DEBUG_FS
unsigned int *pm_sss0_base;
int check_pm_otg(void)
{
/* check whether bit 12 and 13 are 0 */
/* printk(">>>>leon, pm_sss0_base:0x%x\n", *(pm_sss0_base)); */
if (pm_sss0_base)
return (*pm_sss0_base) & 0x3000;
else
return 0;
}
#ifdef readl
#undef readl
#endif
#ifdef writel
#undef writel
#endif
#define readl(addr) ({ if (check_pm_otg()) { \
panic("usb otg, read reg:%p, pm_sss0_base:0x%x", \
addr, *(pm_sss0_base)); }; __le32_to_cpu(__raw_readl(addr)); })
#define writel(b, addr) ({ if (check_pm_otg()) { \
panic("usb otg, write reg:%p, pm_sss0_base:0x%x", \
addr, *(pm_sss0_base)); }; __raw_writel(__cpu_to_le32(b), addr); })
#endif
#ifdef CONFIG_PM_SLEEP
#include <linux/suspend.h>
DECLARE_WAIT_QUEUE_HEAD(stop_host_wait);
atomic_t pnw_sys_suspended;
static int pnw_sleep_pm_callback(struct notifier_block *nfb,
unsigned long action, void *ignored)
{
switch (action) {
case PM_SUSPEND_PREPARE:
atomic_set(&pnw_sys_suspended, 1);
return NOTIFY_OK;
case PM_POST_SUSPEND:
atomic_set(&pnw_sys_suspended, 0);
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
static struct notifier_block pnw_sleep_pm_notifier = {
.notifier_call = pnw_sleep_pm_callback,
.priority = 0
};
/* the root hub will call this callback when device added/removed */
static int otg_notify(struct notifier_block *nb, unsigned long action,
struct usb_device *udev)
{
struct usb_phy *otg;
struct intel_mid_otg_xceiv *iotg;
/* skip bus add/remove notification, else access udev->parent could
* panic if bus register and unregister quickly(setup failed). And we
* do not care bus event.
*/
if (action == USB_BUS_ADD || action == USB_BUS_REMOVE)
return NOTIFY_DONE;
/* Ignore root hub add/remove event */
if (!udev->parent) {
pr_debug("%s Ignore root hub otg_notify\n", __func__);
return NOTIFY_DONE;
}
/* Ignore USB devices on external hub */
if (udev->parent && udev->parent->parent) {
pr_debug("%s Ignore USB devices on external hub\n", __func__);
return NOTIFY_DONE;
}
otg = usb_get_phy(USB_PHY_TYPE_USB2);
if (otg == NULL) {
pr_err("%s: failed to get otg transceiver\n", __func__);
return NOTIFY_BAD;
}
iotg = otg_to_mid_xceiv(otg);
switch (action) {
case USB_DEVICE_ADD:
pr_debug("Notify OTG HNP add device\n");
atomic_notifier_call_chain(&iotg->iotg_notifier,
MID_OTG_NOTIFY_CONNECT, iotg);
break;
case USB_DEVICE_REMOVE:
pr_debug("Notify OTG HNP delete device\n");
atomic_notifier_call_chain(&iotg->iotg_notifier,
MID_OTG_NOTIFY_DISCONN, iotg);
break;
case USB_OTG_TESTDEV:
pr_debug("Notify OTG test device\n");
atomic_notifier_call_chain(&iotg->iotg_notifier,
MID_OTG_NOTIFY_TEST, iotg);
break;
case USB_OTG_TESTDEV_VBUSOFF:
pr_debug("Notify OTG test device, Vbusoff mode\n");
atomic_notifier_call_chain(&iotg->iotg_notifier,
MID_OTG_NOTIFY_TEST_VBUS_OFF, iotg);
break;
default:
usb_put_phy(otg);
return NOTIFY_DONE;
}
usb_put_phy(otg);
return NOTIFY_OK;
}
static struct notifier_block otg_nb = {
.notifier_call = otg_notify,
};
#define PNW_PM_RESUME_WAIT(a) do { \
while (atomic_read(&pnw_sys_suspended)) { \
wait_event_timeout(a, false, HZ/100); \
} \
} while (0)
#else
#define PNW_PM_RESUME_WAIT(a)
#endif
#define PNW_STOP_HOST(pnw) do { \
if ((pnw)->iotg.stop_host) { \
PNW_PM_RESUME_WAIT(stop_host_wait); \
(pnw)->iotg.stop_host(&(pnw)->iotg); \
} \
} while (0)
inline int is_clovertrail(struct pci_dev *pdev)
{
return (pdev->vendor == 0x8086 && pdev->device == 0xE006);
}
EXPORT_SYMBOL_GPL(is_clovertrail);
static const char *state_string(enum usb_otg_state state)
{
switch (state) {
case OTG_STATE_A_IDLE:
return "a_idle";
case OTG_STATE_A_WAIT_VRISE:
return "a_wait_vrise";
case OTG_STATE_A_WAIT_BCON:
return "a_wait_bcon";
case OTG_STATE_A_HOST:
return "a_host";
case OTG_STATE_A_SUSPEND:
return "a_suspend";
case OTG_STATE_A_PERIPHERAL:
return "a_peripheral";
case OTG_STATE_A_WAIT_VFALL:
return "a_wait_vfall";
case OTG_STATE_A_VBUS_ERR:
return "a_vbus_err";
case OTG_STATE_B_IDLE:
return "b_idle";
case OTG_STATE_B_PERIPHERAL:
return "b_peripheral";
case OTG_STATE_B_WAIT_ACON:
return "b_wait_acon";
case OTG_STATE_B_HOST:
return "b_host";
default:
return "UNDEFINED";
}
}
static const char *charger_string(enum usb_charger_type charger)
{
switch (charger) {
case CHRG_SDP:
return "Standard Downstream Port";
case CHRG_SDP_INVAL:
return "Invalid Standard Downstream Port";
case CHRG_CDP:
return "Charging Downstream Port";
case CHRG_DCP:
return "Dedicated Charging Port";
case CHRG_ACA:
return "Accessory Charger Adaptor";
case CHRG_SE1:
return "SE1 Charger";
case CHRG_UNKNOWN:
return "Unknown";
default:
return "Undefined";
}
}
static const char *psc_string(enum power_supply_charger_cable_type charger)
{
switch (charger) {
case POWER_SUPPLY_CHARGER_TYPE_USB_SDP:
return "Standard Downstream Port";
case POWER_SUPPLY_CHARGER_TYPE_USB_CDP:
return "Charging Downstream Port";
case POWER_SUPPLY_CHARGER_TYPE_USB_DCP:
return "Dedicated Charging Port";
case POWER_SUPPLY_CHARGER_TYPE_USB_ACA:
return "Accessory Charger Adaptor";
case POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK:
return "Accessory Charger Adaptor Dock";
case POWER_SUPPLY_CHARGER_TYPE_ACA_A:
return "Accessory Charger Adaptor Type A";
case POWER_SUPPLY_CHARGER_TYPE_ACA_B:
return "Accessory Charger Adaptor Type B";
case POWER_SUPPLY_CHARGER_TYPE_ACA_C:
return "Accessory Charger Adaptor Type C";
case POWER_SUPPLY_CHARGER_TYPE_SE1:
return "SE1 Charger";
case POWER_SUPPLY_CHARGER_TYPE_NONE:
return "Unknown";
default:
return "Undefined";
}
}
static struct penwell_otg *the_transceiver;
void penwell_update_transceiver(void)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long flags;
if (!pnw->qwork) {
dev_warn(pnw->dev, "no workqueue for state machine\n");
return;
}
spin_lock_irqsave(&pnw->lock, flags);
if (!pnw->queue_stop) {
queue_work(pnw->qwork, &pnw->work);
dev_dbg(pnw->dev, "transceiver is updated\n");
}
spin_unlock_irqrestore(&pnw->lock, flags);
}
static int penwell_otg_set_host(struct usb_otg *otg, struct usb_bus *host)
{
otg->host = host;
return 0;
}
static int penwell_otg_set_peripheral(struct usb_otg *otg,
struct usb_gadget *gadget)
{
otg->gadget = gadget;
return 0;
}
static void penwell_otg_set_charger(enum usb_charger_type charger)
{
struct penwell_otg *pnw = the_transceiver;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__,
charger_string(charger));
switch (charger) {
case CHRG_SDP:
case CHRG_DCP:
case CHRG_CDP:
case CHRG_ACA:
case CHRG_SDP_INVAL:
case CHRG_SE1:
case CHRG_UNKNOWN:
pnw->charging_cap.chrg_type = charger;
break;
default:
dev_warn(pnw->dev, "undefined charger type\n");
break;
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
static void _penwell_otg_update_chrg_cap(enum usb_charger_type charger,
unsigned ma)
{
struct penwell_otg *pnw = the_transceiver;
int flag = 0;
int event, retval;
dev_dbg(pnw->dev, "%s = %s, %d --->\n", __func__,
charger_string(charger), ma);
/* Check charger type information */
if (pnw->charging_cap.chrg_type != charger) {
if (pnw->charging_cap.chrg_type == CHRG_UNKNOWN ||
charger == CHRG_UNKNOWN) {
penwell_otg_set_charger(charger);
} else if (pnw->charging_cap.chrg_type == CHRG_SDP &&
charger == CHRG_SDP_INVAL) {
penwell_otg_set_charger(charger);
} else
return;
} else {
/* Do nothing if no update for current */
if (pnw->charging_cap.ma == ma)
return;
}
/* set current */
switch (pnw->charging_cap.chrg_type) {
case CHRG_SDP:
if (pnw->charging_cap.ma == CHRG_CURR_DISCONN
&& (ma == CHRG_CURR_SDP_LOW
|| ma == CHRG_CURR_SDP_HIGH)) {
/* SDP event: charger connect */
event = USBCHRG_EVENT_CONNECT;
flag = 1;
} else if (pnw->charging_cap.ma == CHRG_CURR_SDP_LOW
&& ma == CHRG_CURR_SDP_HIGH) {
/* SDP event: configuration update */
event = USBCHRG_EVENT_UPDATE;
flag = 1;
} else if (pnw->charging_cap.ma == CHRG_CURR_SDP_HIGH
&& ma == CHRG_CURR_SDP_LOW) {
/* SDP event: configuration update */
event = USBCHRG_EVENT_UPDATE;
flag = 1;
} else if (pnw->charging_cap.ma == CHRG_CURR_SDP_SUSP
&& (ma == CHRG_CURR_SDP_LOW
|| ma == CHRG_CURR_SDP_HIGH)) {
/* SDP event: resume from suspend state */
event = USBCHRG_EVENT_RESUME;
flag = 1;
} else if ((pnw->charging_cap.ma == CHRG_CURR_SDP_LOW
|| pnw->charging_cap.ma == CHRG_CURR_SDP_HIGH)
&& ma == CHRG_CURR_SDP_SUSP) {
/* SDP event: enter suspend state */
event = USBCHRG_EVENT_SUSPEND;
flag = 1;
} else if (ma == 0) {
event = USBCHRG_EVENT_DISCONN;
flag = 1;
} else
dev_dbg(pnw->dev, "SDP: no need to update EM\n");
break;
case CHRG_DCP:
if (ma == CHRG_CURR_DCP) {
/* DCP event: charger connect */
event = USBCHRG_EVENT_CONNECT;
flag = 1;
} else
dev_dbg(pnw->dev, "DCP: no need to update EM\n");
break;
case CHRG_SE1:
if (ma == CHRG_CURR_SE1) {
/* SE1 event: charger connect */
event = USBCHRG_EVENT_CONNECT;
flag = 1;
} else
dev_dbg(pnw->dev, "SE1: no need to update EM\n");
break;
case CHRG_CDP:
if (pnw->charging_cap.ma == CHRG_CURR_DISCONN
&& ma == CHRG_CURR_CDP) {
/* CDP event: charger connect */
event = USBCHRG_EVENT_CONNECT;
flag = 1;
} else
dev_dbg(pnw->dev, "CDP: no need to update EM\n");
break;
case CHRG_UNKNOWN:
if (ma == CHRG_CURR_DISCONN) {
/* event: chargers disconnect */
event = USBCHRG_EVENT_DISCONN;
flag = 1;
} else
dev_dbg(pnw->dev, "UNKNOWN: no need to update EM\n");
break;
case CHRG_SDP_INVAL:
if (ma == CHRG_CURR_SDP_INVAL) {
event = USBCHRG_EVENT_UPDATE;
flag = 1;
} else
dev_dbg(pnw->dev, "SDP_INVAL: no need to update EM\n");
default:
break;
}
if (flag) {
pnw->charging_cap.ma = ma;
pnw->charging_cap.current_event = event;
/* Notify EM the charging current update */
dev_dbg(pnw->dev, "Notify EM charging capability change\n");
dev_dbg(pnw->dev, "%s event = %d ma = %d\n",
charger_string(pnw->charging_cap.chrg_type), event, ma);
if (pnw->bc_callback) {
retval = pnw->bc_callback(pnw->bc_arg, event,
&pnw->charging_cap);
if (retval)
dev_dbg(pnw->dev,
"bc callback return %d\n", retval);
}
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
static enum power_supply_charger_cable_type usb_chrg_to_power_supply_chrg(
enum usb_charger_type chrg_type)
{
switch (chrg_type) {
case CHRG_UNKNOWN: return POWER_SUPPLY_CHARGER_TYPE_NONE;
case CHRG_SDP: return POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
case CHRG_CDP: return POWER_SUPPLY_CHARGER_TYPE_USB_CDP;
case CHRG_SDP_INVAL: return POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
case CHRG_DCP: return POWER_SUPPLY_CHARGER_TYPE_USB_DCP;
case CHRG_ACA: return POWER_SUPPLY_CHARGER_TYPE_USB_ACA;
case CHRG_ACA_DOCK: return POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK;
case CHRG_ACA_A: return POWER_SUPPLY_CHARGER_TYPE_ACA_A;
case CHRG_ACA_B: return POWER_SUPPLY_CHARGER_TYPE_ACA_B;
case CHRG_ACA_C: return POWER_SUPPLY_CHARGER_TYPE_ACA_C;
case CHRG_SE1: return POWER_SUPPLY_CHARGER_TYPE_SE1;
case CHRG_MHL: return POWER_SUPPLY_CHARGER_TYPE_MHL;
default: return POWER_SUPPLY_CHARGER_TYPE_NONE;
}
}
static enum power_supply_charger_event check_psc_event(
struct power_supply_cable_props old,
struct power_supply_cable_props new)
{
struct penwell_otg *pnw = the_transceiver;
dev_dbg(pnw->dev, "%s --->\n", __func__);
/* Check charger type information */
if (old.chrg_type != new.chrg_type) {
if (old.chrg_type == POWER_SUPPLY_CHARGER_TYPE_NONE
&& new.ma != 0)
return POWER_SUPPLY_CHARGER_EVENT_CONNECT;
else if (new.chrg_type == POWER_SUPPLY_CHARGER_TYPE_NONE)
return POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
else {
dev_dbg(pnw->dev, "not a valid event\n");
return -1;
}
}
/* Check the charging current limit */
if (old.ma == new.ma) {
dev_dbg(pnw->dev, "not a valid event\n");
return -1;
}
switch (new.chrg_type) {
case POWER_SUPPLY_CHARGER_TYPE_USB_SDP:
if (old.ma == CHRG_CURR_DISCONN &&
(new.ma == CHRG_CURR_SDP_LOW ||
new.ma == CHRG_CURR_SDP_HIGH)) {
/* SDP event: charger connect */
return POWER_SUPPLY_CHARGER_EVENT_CONNECT;
} else if (old.ma == CHRG_CURR_SDP_LOW &&
new.ma == CHRG_CURR_SDP_HIGH) {
/* SDP event: configuration update */
return POWER_SUPPLY_CHARGER_EVENT_UPDATE;
} else if (old.ma == CHRG_CURR_SDP_HIGH &&
new.ma == CHRG_CURR_SDP_LOW) {
/* SDP event: configuration update */
return POWER_SUPPLY_CHARGER_EVENT_UPDATE;
} else if (old.ma == CHRG_CURR_SDP_SUSP &&
(new.ma == CHRG_CURR_SDP_LOW ||
new.ma == CHRG_CURR_SDP_HIGH)) {
/* SDP event: resume from suspend state */
return POWER_SUPPLY_CHARGER_EVENT_RESUME;
} else if ((old.ma == CHRG_CURR_SDP_LOW ||
old.ma == CHRG_CURR_SDP_HIGH) &&
new.ma == CHRG_CURR_SDP_SUSP) {
/* SDP event: enter suspend state */
return POWER_SUPPLY_CHARGER_EVENT_SUSPEND;
} else
dev_dbg(pnw->dev, "SDP: no need to update EM\n");
break;
case POWER_SUPPLY_CHARGER_TYPE_USB_DCP:
if (new.ma == CHRG_CURR_DCP) {
/* DCP event: charger connect */
return POWER_SUPPLY_CHARGER_EVENT_CONNECT;
} else
dev_dbg(pnw->dev, "DCP: no need to update EM\n");
break;
case POWER_SUPPLY_CHARGER_TYPE_USB_CDP:
if (pnw->charging_cap.ma == CHRG_CURR_DISCONN &&
new.ma == CHRG_CURR_CDP) {
/* CDP event: charger connect */
return POWER_SUPPLY_CHARGER_EVENT_CONNECT;
} else
dev_dbg(pnw->dev, "CDP: no need to update EM\n");
break;
case POWER_SUPPLY_CHARGER_TYPE_NONE:
if (new.ma == CHRG_CURR_DISCONN) {
/* event: chargers disconnect */
return POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
} else
dev_dbg(pnw->dev, "UNKNOWN: no need to update EM\n");
break;
default:
break;
}
return -1;
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
static void penwell_otg_update_chrg_cap(enum usb_charger_type charger,
unsigned ma)
{
struct penwell_otg *pnw = the_transceiver;
struct pci_dev *pdev;
unsigned long flags;
struct otg_bc_event *event;
pdev = to_pci_dev(pnw->dev);
dev_dbg(pnw->dev, "%s --->\n", __func__);
if (!is_clovertrail(pdev)) {
spin_lock_irqsave(&pnw->charger_lock, flags);
_penwell_otg_update_chrg_cap(charger, ma);
spin_unlock_irqrestore(&pnw->charger_lock, flags);
} else {
dev_dbg(pnw->dev, "clv cable_props_update\n");
event = kzalloc(sizeof(*event), GFP_ATOMIC);
if (!event) {
dev_err(pnw->dev, "no memory for charging event");
return;
}
event->cap.chrg_type = usb_chrg_to_power_supply_chrg(charger);
event->cap.ma = ma;
INIT_LIST_HEAD(&event->node);
spin_lock_irqsave(&pnw->charger_lock, flags);
list_add_tail(&event->node, &pnw->chrg_evt_queue);
spin_unlock_irqrestore(&pnw->charger_lock, flags);
queue_work(pnw->chrg_qwork, &pnw->psc_notify);
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
static int penwell_otg_set_power(struct usb_phy *otg, unsigned ma)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long flags;
struct pci_dev *pdev;
struct otg_bc_event *event;
dev_dbg(pnw->dev, "%s --->\n", __func__);
pdev = to_pci_dev(pnw->dev);
if (!is_clovertrail(pdev)) {
spin_lock_irqsave(&pnw->charger_lock, flags);
if (pnw->charging_cap.chrg_type != CHRG_SDP) {
spin_unlock_irqrestore(&pnw->charger_lock, flags);
return 0;
}
if (!pnw->otg_pdata->charging_compliance)
ma = CHRG_CURR_SDP_HIGH;
_penwell_otg_update_chrg_cap(CHRG_SDP, ma);
spin_unlock_irqrestore(&pnw->charger_lock, flags);
} else {
dev_dbg(pnw->dev, "clv charger_set_power\n");
if (pnw->psc_cap.chrg_type != POWER_SUPPLY_CHARGER_TYPE_USB_SDP)
return 0;
if (!pnw->otg_pdata->charging_compliance)
ma = CHRG_CURR_SDP_HIGH;
event = kzalloc(sizeof(*event), GFP_ATOMIC);
if (!event) {
dev_err(pnw->dev, "no memory for charging event");
return -ENOMEM;
}
event->cap.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
event->cap.ma = ma;
INIT_LIST_HEAD(&event->node);
spin_lock_irqsave(&pnw->charger_lock, flags);
list_add_tail(&event->node, &pnw->chrg_evt_queue);
spin_unlock_irqrestore(&pnw->charger_lock, flags);
queue_work(pnw->chrg_qwork, &pnw->psc_notify);
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
return 0;
}
int penwell_otg_get_chrg_status(struct usb_phy *x, void *data)
{
unsigned long flags;
struct power_supply_cable_props *cap =
(struct power_supply_cable_props *)data;
struct penwell_otg *pnw = the_transceiver;
if (pnw == NULL)
return -ENODEV;
dev_dbg(pnw->dev, "%s --->\n", __func__);
if (data == NULL)
return -EINVAL;
spin_lock_irqsave(&pnw->cap_lock, flags);
cap->chrg_evt = pnw->psc_cap.chrg_evt;
cap->chrg_type = pnw->psc_cap.chrg_type;
cap->ma = pnw->psc_cap.ma;
spin_unlock_irqrestore(&pnw->cap_lock, flags);
dev_dbg(pnw->dev, "%s <---\n", __func__);
return 0;
}
int penwell_otg_query_charging_cap(struct otg_bc_cap *cap)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long flags;
if (pnw == NULL)
return -ENODEV;
dev_dbg(pnw->dev, "%s --->\n", __func__);
if (cap == NULL)
return -EINVAL;
if (is_clovertrail(to_pci_dev(pnw->dev)))
return -ENODEV;
spin_lock_irqsave(&pnw->charger_lock, flags);
cap->chrg_type = pnw->charging_cap.chrg_type;
cap->ma = pnw->charging_cap.ma;
cap->current_event = pnw->charging_cap.current_event;
spin_unlock_irqrestore(&pnw->charger_lock, flags);
dev_dbg(pnw->dev, "%s <---\n", __func__);
return 0;
}
EXPORT_SYMBOL_GPL(penwell_otg_query_charging_cap);
/* Register/unregister battery driver callback */
void *penwell_otg_register_bc_callback(
int (*cb)(void *, int, struct otg_bc_cap *), void *arg)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long flags;
if (pnw == NULL)
return pnw;
if (is_clovertrail(to_pci_dev(pnw->dev)))
return NULL;
spin_lock_irqsave(&pnw->charger_lock, flags);
if (pnw->bc_callback != NULL)
dev_dbg(pnw->dev, "callback has already registered\n");
pnw->bc_callback = cb;
pnw->bc_arg = arg;
spin_unlock_irqrestore(&pnw->charger_lock, flags);
return pnw;
}
EXPORT_SYMBOL_GPL(penwell_otg_register_bc_callback);
int penwell_otg_unregister_bc_callback(void *handler)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long flags;
if (pnw == NULL)
return -ENODEV;
if (pnw != handler)
return -EINVAL;
if (is_clovertrail(to_pci_dev(pnw->dev)))
return -ENODEV;
spin_lock_irqsave(&pnw->charger_lock, flags);
pnw->bc_callback = NULL;
pnw->bc_arg = NULL;
spin_unlock_irqrestore(&pnw->charger_lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(penwell_otg_unregister_bc_callback);
/* After probe, it should enable the power of USB PHY */
static void penwell_otg_phy_enable(int on)
{
struct penwell_otg *pnw = the_transceiver;
u8 data;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off");
data = on ? 0x37 : 0x24;
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__);
}
/* A-device drives vbus, controlled through MSIC register */
static int penwell_otg_set_vbus(struct usb_otg *otg, bool enabled)
{
struct penwell_otg *pnw = the_transceiver;
u8 data;
unsigned long flags;
int retval = 0;
struct otg_bc_event *evt;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__, enabled ? "on" : "off");
/*
* For Clovertrail, VBUS is driven by TPS2052 power switch chip.
* But TPS2052 is controlled by ULPI PHY.
*/
if (is_clovertrail(to_pci_dev(pnw->dev))) {
penwell_otg_phy_low_power(0);
if (enabled)
penwell_otg_ulpi_write(&pnw->iotg,
ULPI_OTGCTRLSET, DRVVBUS | DRVVBUS_EXTERNAL);
else
penwell_otg_ulpi_write(&pnw->iotg,
ULPI_OTGCTRLCLR, DRVVBUS | DRVVBUS_EXTERNAL);
evt = kzalloc(sizeof(*evt), GFP_KERNEL);
if (!evt) {
dev_err(pnw->dev, "no memory for charging event");
return -ENOMEM;
}
evt->cap.chrg_type = POWER_SUPPLY_CHARGER_TYPE_NONE;
INIT_LIST_HEAD(&evt->node);
if ((!enabled) && (pnw->iotg.hsm.id == ID_ACA_A
|| pnw->iotg.hsm.id == ID_ACA_B
|| pnw->iotg.hsm.id == ID_ACA_C)) {
evt->cap.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_ACA;
evt->cap.ma = CHRG_CURR_ACA;
evt->cap.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
} else {
dev_info(pnw->dev, "notification: turn %s VBUS\n",
enabled ? "ON" : "OFF");
atomic_notifier_call_chain(&pnw->iotg.otg.notifier,
USB_EVENT_DRIVE_VBUS, &enabled);
#if defined(CONFIG_ME372CG_BATTERY_SMB345)
setSMB345Charger(enabled ? ENABLE_5V : DISABLE_5V);
#endif
kfree(evt);
goto done;
}
dev_dbg(pnw->dev, "set_vbus ma = %d, event = %d, type = %s\n",
evt->cap.ma, evt->cap.chrg_evt,
psc_string(evt->cap.chrg_type));
spin_lock_irqsave(&pnw->charger_lock, flags);
list_add_tail(&evt->node, &pnw->chrg_evt_queue);
spin_unlock_irqrestore(&pnw->charger_lock, flags);
queue_work(pnw->chrg_qwork, &pnw->psc_notify);
goto done;
}
if (pnw->otg_pdata->gpio_vbus) {
dev_info(pnw->dev, "Turn %s VBUS using GPIO pin %d\n",
enabled ? "on" : "off", pnw->otg_pdata->gpio_vbus);
gpio_direction_output(pnw->otg_pdata->gpio_vbus,
enabled ? 1 : 0);
goto done;
}
data = enabled ? VOTGEN : 0;
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);
done:
dev_dbg(pnw->dev, "%s <---\n", __func__);
return retval;
}
static int penwell_otg_ulpi_run(void)
{
struct penwell_otg *pnw = the_transceiver;
u32 val;
val = readl(pnw->iotg.base + CI_ULPIVP);
if (val & ULPI_RUN)
return 1;
dev_dbg(pnw->dev, "%s: ULPI command done\n", __func__);
return 0;
}
/* io_ops to access ulpi registers */
static int
penwell_otg_ulpi_read(struct intel_mid_otg_xceiv *iotg, u8 reg, u8 *val)
{
struct penwell_otg *pnw = the_transceiver;
u32 val32 = 0;
int count;
unsigned long flags;
dev_dbg(pnw->dev, "%s - addr 0x%x\n", __func__, reg);
spin_lock_irqsave(&pnw->lock, flags);
/* Port = 0 */
val32 = ULPI_RUN | reg << 16;
writel(val32, pnw->iotg.base + CI_ULPIVP);
/* Polling at least 2ms for read operation to complete*/
count = 400;
while (count) {
val32 = readl(pnw->iotg.base + CI_ULPIVP);
if (val32 & ULPI_RUN) {
count--;
udelay(5);
} else {
*val = (u8)((val32 & ULPI_DATRD) >> 8);
dev_dbg(pnw->dev,
"%s - done data 0x%x\n", __func__, *val);
spin_unlock_irqrestore(&pnw->lock, flags);
return 0;
}
}
dev_warn(pnw->dev, "%s - addr 0x%x timeout\n", __func__, reg);
spin_unlock_irqrestore(&pnw->lock, flags);
return -ETIMEDOUT;
}
static int
penwell_otg_ulpi_write(struct intel_mid_otg_xceiv *iotg, u8 reg, u8 val)
{
struct penwell_otg *pnw = the_transceiver;
u32 val32 = 0;
int count;
unsigned long flags;
dev_dbg(pnw->dev,
"%s - addr 0x%x - data 0x%x\n", __func__, reg, val);
spin_lock_irqsave(&pnw->lock, flags);
/* Port = 0 */
val32 = ULPI_RUN | ULPI_RW | reg << 16 | val;
writel(val32, pnw->iotg.base + CI_ULPIVP);
/* Polling at least 2ms for write operation to complete*/
count = 400;
while (count && penwell_otg_ulpi_run()) {
count--;
udelay(5);
}
dev_dbg(pnw->dev, "%s - addr 0x%x %s\n", __func__, reg,
count ? "complete" : "timeout");
spin_unlock_irqrestore(&pnw->lock, flags);
return count ? 0 : -ETIMEDOUT;
}
int pnw_otg_ulpi_write(u8 reg, u8 val)
{
return penwell_otg_ulpi_write(&the_transceiver->iotg,
reg, val);
}
EXPORT_SYMBOL_GPL(pnw_otg_ulpi_write);
static enum msic_vendor penwell_otg_check_msic(void)
{
/* Return MSIC_VD_TI directly */
return MSIC_VD_TI;
}
/* Monitor function check if SRP initial conditions. Use polling on current
* status for b_ssend_srp, b_se0_srp */
static void penwell_otg_mon_bus(void)
{
struct penwell_otg *pnw = the_transceiver;
int count = 6;
int interval = 300; /* ms */
u32 val = 0;
dev_dbg(pnw->dev, "%s --->\n", __func__);
pnw->iotg.hsm.b_ssend_srp = 0;
pnw->iotg.hsm.b_se0_srp = 0;
while (count) {
msleep(interval);
/* Check VBus status */
val = readl(pnw->iotg.base + CI_OTGSC);
if (!(val & OTGSC_BSE))
return;
val = readl(pnw->iotg.base + CI_PORTSC1);
if (val & PORTSC_LS)
return;
count--;
}
pnw->iotg.hsm.b_ssend_srp = 1;
pnw->iotg.hsm.b_se0_srp = 1;
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
/* HNP polling function */
/* The timeout callback function which polls the host request flag for HNP */
static void penwell_otg_hnp_poll_fn(unsigned long indicator)
{
struct penwell_otg *pnw = the_transceiver;
queue_work(pnw->qwork, &pnw->hnp_poll_work);
}
/* Start HNP polling */
/* Call this function with iotg->hnp_poll_lock held */
static int penwell_otg_add_hnp_poll_timer(struct intel_mid_otg_xceiv *iotg,
unsigned long delay)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long j = jiffies;
pnw->hnp_poll_timer.data = 1;
pnw->hnp_poll_timer.function = penwell_otg_hnp_poll_fn;
pnw->hnp_poll_timer.expires = j + msecs_to_jiffies(delay);
add_timer(&pnw->hnp_poll_timer);
return 0;
}
static int penwell_otg_start_hnp_poll(struct intel_mid_otg_xceiv *iotg)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long flags;
spin_lock_irqsave(&pnw->iotg.hnp_poll_lock, flags);
if (pnw->iotg.hsm.hnp_poll_enable) {
spin_unlock_irqrestore(&pnw->iotg.hnp_poll_lock, flags);
dev_dbg(pnw->dev, "HNP polling is already enabled\n");
return 0;
}
/* mark HNP polling enabled and start HNP polling in 50ms */
pnw->iotg.hsm.hnp_poll_enable = 1;
penwell_otg_add_hnp_poll_timer(&pnw->iotg, 50);
spin_unlock_irqrestore(&pnw->iotg.hnp_poll_lock, flags);
return 0;
}
static int penwell_otg_continue_hnp_poll(struct intel_mid_otg_xceiv *iotg)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long flags;
spin_lock_irqsave(&pnw->iotg.hnp_poll_lock, flags);
if (!pnw->iotg.hsm.hnp_poll_enable) {
spin_unlock_irqrestore(&pnw->iotg.hnp_poll_lock, flags);
dev_dbg(pnw->dev, "HNP polling is disabled, stop polling\n");
return 0;
}
penwell_otg_add_hnp_poll_timer(&pnw->iotg, THOS_REQ_POL);
spin_unlock_irqrestore(&pnw->iotg.hnp_poll_lock, flags);
return 0;
}
/* Stop HNP polling */
static int penwell_otg_stop_hnp_poll(struct intel_mid_otg_xceiv *iotg)
{
struct penwell_otg *pnw = the_transceiver;
unsigned long flags;
spin_lock_irqsave(&pnw->iotg.hnp_poll_lock, flags);
if (!pnw->iotg.hsm.hnp_poll_enable) {
spin_unlock_irqrestore(&pnw->iotg.hnp_poll_lock, flags);
dev_dbg(pnw->dev, "HNP polling is already disabled\n");
return 0;
}
pnw->iotg.hsm.hnp_poll_enable = 0;
del_timer_sync(&pnw->hnp_poll_timer);
spin_unlock_irqrestore(&pnw->iotg.hnp_poll_lock, flags);
return 0;
}
/* Start SRP function */
static int penwell_otg_start_srp(struct usb_otg *otg)
{
struct penwell_otg *pnw = the_transceiver;
u32 val;
dev_dbg(pnw->dev, "%s --->\n", __func__);
val = readl(pnw->iotg.base + CI_OTGSC);
writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HADP,
pnw->iotg.base + CI_OTGSC);
/* Check if the data plus is finished or not */
msleep(8);
val = readl(pnw->iotg.base + CI_OTGSC);
if (val & (OTGSC_HADP | OTGSC_DP))
dev_warn(pnw->dev, "DataLine SRP Error\n");
dev_dbg(pnw->dev, "%s <---\n", __func__);
return 0;
}
/* stop SOF via bus_suspend */
static void penwell_otg_loc_sof(int on)
{
struct penwell_otg *pnw = the_transceiver;
struct usb_hcd *hcd;
int err;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "resume" : "suspend");
hcd = bus_to_hcd(pnw->iotg.otg.otg->host);
if (on)
err = hcd->driver->bus_resume(hcd);
else
err = hcd->driver->bus_suspend(hcd);
if (err)
dev_warn(pnw->dev, "Fail to resume/suspend USB bus -%d\n", err);
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
static void penwell_otg_phy_low_power(int on)
{
struct penwell_otg *pnw = the_transceiver;
u32 val;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off");
val = readl(pnw->iotg.base + CI_USBMODE);
if (!(val & USBMODE_CM)) {
dev_err(pnw->dev,
"PHY can't enter low power mode "
"when UDC is in idle mode\n");
set_client_mode();
}
val = readl(pnw->iotg.base + CI_HOSTPC1);
dev_dbg(pnw->dev, "---> Register CI_HOSTPC1 = %x\n", val);
if (on) {
if (val & HOSTPC1_PHCD) {
dev_dbg(pnw->dev, "already in Low power mode\n");
return;
}
writel(val | HOSTPC1_PHCD, pnw->iotg.base + CI_HOSTPC1);
} else {
if (!(val & HOSTPC1_PHCD)) {
dev_dbg(pnw->dev, "already in Normal mode\n");
return;
}
writel(val & ~HOSTPC1_PHCD, pnw->iotg.base + CI_HOSTPC1);
}
val = readl(pnw->iotg.base + CI_HOSTPC1);
dev_dbg(pnw->dev, "<--- Register CI_HOSTPC1 = %x\n", val);
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
/*
* For Penwell, VBUS330 is the power rail to otg PHY inside MSIC, set it
* into low power mode or normal mode according to pm state.
* Call this function when spi access to MSIC registers is enabled.
*
* For Clovertrail, we don't have a controllable power rail to the PHY -
* it's always on.
*/
static int penwell_otg_vusb330_low_power(int on)
{
struct penwell_otg *pnw = the_transceiver;
u8 data;
int retval = 0;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off");
if (!is_clovertrail(to_pci_dev(pnw->dev))) {
if (on)
data = 0x5; /* Low power mode */
else
data = 0x7; /* Normal mode */
retval = penwell_otg_msic_write(MSIC_VUSB330CNT, data);
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
return retval;
}
/* Enable/Disable OTG interrupt */
static void penwell_otg_intr(int on)
{
struct penwell_otg *pnw = the_transceiver;
u32 val;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off");
val = readl(pnw->iotg.base + CI_OTGSC);
/* mask W/C bits to avoid clearing them when
* val is written back to OTGSC */
val &= ~OTGSC_INTSTS_MASK;
if (on) {
val = val | (OTGSC_INTEN_MASK);
writel(val, pnw->iotg.base + CI_OTGSC);
} else {
val = val & ~(OTGSC_INTEN_MASK);
writel(val, pnw->iotg.base + CI_OTGSC);
}
}
/* set HAAR: Hardware Assist Auto-Reset */
static void penwell_otg_HAAR(int on)
{
struct penwell_otg *pnw = the_transceiver;
u32 val;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off");
val = readl(pnw->iotg.base + CI_OTGSC);
if (on)
writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HAAR,
pnw->iotg.base + CI_OTGSC);
else
writel((val & ~OTGSC_INTSTS_MASK) & ~OTGSC_HAAR,
pnw->iotg.base + CI_OTGSC);
}
/* set HABA: Hardware Assist B-Disconnect to A-Connect */
static void penwell_otg_HABA(int on)
{
struct penwell_otg *pnw = the_transceiver;
u32 val;
dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off");
val = readl(pnw->iotg.base + CI_OTGSC);
if (on)
writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HABA,
pnw->iotg.base + CI_OTGSC);
else
writel((val & ~OTGSC_INTSTS_MASK) & ~OTGSC_HABA,
pnw->iotg.base + CI_OTGSC);
}
/* read 8bit msic register */
static int penwell_otg_msic_read(u16 addr, u8 *data)
{
struct penwell_otg *pnw = the_transceiver;
int retval = intel_scu_ipc_ioread8(addr, data);
if (retval)
dev_warn(pnw->dev, "Failed to read MSIC register %x\n", addr);
return retval;
}
/* 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;
}
/* 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 = 50;
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_USB_MISC, &data);
if (retval) {
dev_warn(pnw->dev, "Failed to read MSIC register\n");
return retval;
}
if (data & MISC_CHGDSERXDPINV) {
dev_dbg(pnw->dev, "Data contact detected!\n");
return 0;
}
count--;
/* Interval is 10 - 11ms */
usleep_range(10000, 11000);
}
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;
}
/* manual charger detection by ULPI access */
static int penwell_otg_manual_chrg_det(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg;
int retval;
u8 data, data1, data2;
unsigned long timeout, interval;
dev_dbg(pnw->dev, "%s --->\n", __func__);
iotg = &pnw->iotg;
dev_info(pnw->dev, "USB charger detection start...\n");
/* WA for invalid (SE1) charger: add DCD & SE1 detection over SPI
* instead of ULPI interface to avoid any ulpi read/write failures
* with SE1 charger */
penwell_otg_msic_spi_access(true);
retval = penwell_otg_msic_write(MSIC_FUNCTRLCLR,
OPMODE1 | TERMSELECT | XCVRSELECT1);
if (retval) {
penwell_otg_msic_spi_access(false);
return retval;
}
retval = penwell_otg_msic_write(MSIC_FUNCTRLSET, OPMODE0 | XCVRSELECT0);
if (retval) {
penwell_otg_msic_spi_access(false);
return retval;
}
retval = penwell_otg_msic_write(MSIC_PWRCTRLSET, SWCNTRL);
if (retval) {
penwell_otg_msic_spi_access(false);
return retval;
}
retval = penwell_otg_msic_write(MSIC_VS3SET, CHGD_IDP_SRC);
if (retval) {
penwell_otg_msic_spi_access(false);
return retval;
}
dev_info(pnw->dev, "charger detection DCD start...\n");
/* Check DCD result, use same polling parameter */
timeout = jiffies + msecs_to_jiffies(DATACON_TIMEOUT);
interval = DATACON_INTERVAL * 1000; /* us */
dev_info(pnw->dev, "DCD started!\n");
/* Delay TIDP_SRC_ON + TCHGD_SERX_DEB */
usleep_range(66500, 67000);
while (!time_after(jiffies, timeout)) {
retval = penwell_otg_msic_read(MSIC_VS4, &data);
if (retval) {
penwell_otg_msic_spi_access(false);
return retval;
}
if (!(data & CHRG_SERX_DP)) {
dev_info(pnw->dev, "Data contact detected!\n");
break;
}
/* Polling interval */
usleep_range(interval, interval + 2000);
}
retval = penwell_otg_msic_write(MSIC_VS3CLR, CHGD_IDP_SRC);
if (retval) {
penwell_otg_msic_spi_access(false);
return retval;
}
dev_info(pnw->dev, "DCD complete\n");
/* Check for SE1, Linestate = '11' */
retval = penwell_otg_msic_read(MSIC_DEBUG, &data);
if (retval) {
penwell_otg_msic_spi_access(false);
return retval;
}
dev_info(pnw->dev, "MSIC.DEBUG.LINESTATE.D[1:0] = 0x%02X\n", data);
data &= LINESTATE_MSK;
if (data == LINESTATE_SE1) {
dev_info(pnw->dev, "SE1 Detected\n");
penwell_otg_msic_spi_access(false);
return CHRG_SE1;
}
penwell_otg_msic_spi_access(false);
dev_info(pnw->dev, "Primary Detection start...\n");
/* Primary Dection config */
/* ulpi_write(0x0b, 0x06) */
retval = penwell_otg_ulpi_write(iotg, ULPI_OTGCTRLSET,
DMPULLDOWN | DPPULLDOWN);
if (retval)
return retval;
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, DPVSRCEN);
if (retval)
return retval;
msleep(125);
/* Check result SDP vs CDP/DCP */
retval = penwell_otg_ulpi_read(iotg, ULPI_PWRCTRL, &data1);
if (retval)
return retval;
data1 = data1 & VDATDET;
retval = penwell_otg_ulpi_read(iotg, ULPI_VS4, &data2);
if (retval)
return retval;
data2 = data2 & CHRG_SERX_DM;
if (!data1 || data2) {
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR,
DPVSRCEN);
if (retval)
return retval;
dev_info(pnw->dev, "USB Charger Detection done\n");
return CHRG_SDP;
}
/* start detection on CDP vs DCP */
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR, DPVSRCEN);
if (retval)
return retval;
/* sleep 1ms between Primary and Secondary detection */
usleep_range(1000, 1200);
retval = penwell_otg_ulpi_write(iotg, ULPI_VS1CLR, DATAPOLARITY);
if (retval)
return retval;
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, DPVSRCEN);
if (retval)
return retval;
msleep(85);
/* read result on CDP vs DCP */
retval = penwell_otg_ulpi_read(iotg, ULPI_PWRCTRL, &data);
if (retval)
return retval;
data = data & VDATDET;
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR, DPVSRCEN);
if (retval)
return retval;
retval = penwell_otg_ulpi_write(iotg, ULPI_VS1SET, DATAPOLARITY);
if (retval)
return retval;
dev_info(pnw->dev, "USB Charger Detection done\n");
if (data) {
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET,
DPVSRCEN);
if (retval)
return retval;
return CHRG_DCP;
} else
return CHRG_CDP;
dev_dbg(pnw->dev, "%s <---\n", __func__);
};
static int penwell_otg_charger_det_dcd_clt(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval;
unsigned long timeout, interval;
u8 data;
/* Change OPMODE to '01' Non-driving */
retval = penwell_otg_ulpi_write(iotg, ULPI_FUNCTRLSET,
OPMODE0 | XCVRSELECT0);
if (retval)
return retval;
retval = penwell_otg_ulpi_write(iotg, ULPI_FUNCTRLCLR,
OPMODE1 | XCVRSELECT1 | TERMSELECT);
if (retval)
return retval;
/* Enable SW control */
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, SWCNTRL);
if (retval)
return retval;
/* Clear HWDETECT result for safety */
penwell_otg_charger_hwdet(false);
/* Enable IDPSRC */
retval = penwell_otg_ulpi_write(iotg, ULPI_VS3SET, CHGD_IDP_SRC);
if (retval)
return retval;
/* Check DCD result, use same polling parameter */
timeout = jiffies + msecs_to_jiffies(DATACON_TIMEOUT);
interval = DATACON_INTERVAL * 1000; /* us */
dev_info(pnw->dev, "DCD started\n");
/* Delay TIDP_SRC_ON + TCHGD_SERX_DEB = 347.8us + 66.1ms max */
usleep_range(66500, 67000);
while (!time_after(jiffies, timeout)) {
retval = penwell_otg_ulpi_read(iotg, ULPI_VS4, &data);
if (retval) {
dev_warn(pnw->dev, "Failed to read ULPI register\n");
return retval;
}
dev_dbg(pnw->dev, "VS4 = 0x%02x.. DP = bit1\n", data);
if (!(data & CHRG_SERX_DP)) {
dev_info(pnw->dev, "Data contact detected!\n");
break;
}
/* Polling interval */
usleep_range(interval, interval + 2000);
}
/* ulpi_write(0x87, 0x40)*/
retval = penwell_otg_ulpi_write(iotg, ULPI_VS3CLR, CHGD_IDP_SRC);
if (retval)
return retval;
dev_info(pnw->dev, "DCD complete\n");
return 0;
}
static int penwell_otg_charger_det_aca_clt(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval;
u8 data;
u8 usb_vs2_sts = 0;
u8 usb_vs2_latch = 0;
u8 usb_vdat_det = 0;
u8 usb_vdm = 0;
retval = penwell_otg_ulpi_read(iotg, ULPI_VS2LATCH, &usb_vs2_latch);
dev_dbg(pnw->dev, "%s: usb_vs2_latch = 0x%x\n",
__func__, usb_vs2_latch);
if (retval) {
dev_warn(pnw->dev, "ULPI read failed, exit\n");
return retval;
}
retval = penwell_otg_ulpi_read(iotg, ULPI_VS2STS, &usb_vs2_sts);
dev_dbg(pnw->dev, "%s: usb_vs2_sts = 0x%x\n",
__func__, usb_vs2_sts);
if (retval) {
dev_warn(pnw->dev, "ULPI read failed, exit\n");
return retval;
}
if (usb_vs2_latch & IDRARBRC_MSK) {
switch (IDRARBRC_STS(usb_vs2_sts)) {
case IDRARBRC_A:
iotg->hsm.id = ID_ACA_A;
break;
case IDRARBRC_B:
iotg->hsm.id = ID_ACA_B;
dev_info(pnw->dev, "ACA-B detected\n");
break;
case IDRARBRC_C:
iotg->hsm.id = ID_ACA_C;
dev_info(pnw->dev, "ACA-C detected\n");
break;
default:
break;
}
}
if (iotg->hsm.id == ID_ACA_A) {
retval = penwell_otg_ulpi_write(iotg,
ULPI_PWRCTRLSET, DPVSRCEN);
if (retval)
return retval;
msleep(70);
retval = penwell_otg_ulpi_read(iotg, ULPI_PWRCTRL, &data);
usb_vdat_det = data & VDATDET;
if (retval) {
dev_warn(pnw->dev, "ULPI read failed, exit\n");
return retval;
}
retval = penwell_otg_ulpi_read(iotg, ULPI_VS4, &data);
usb_vdm = data & CHRG_SERX_DM;
if (retval) {
dev_warn(pnw->dev, "ULPI read failed, exit\n");
return retval;
}
retval = penwell_otg_ulpi_write(iotg,
ULPI_PWRCTRLCLR, DPVSRCEN);
if (retval)
return retval;
if (usb_vdat_det && !usb_vdm)
dev_info(pnw->dev, "ACA-Dock detected\n");
else if (!usb_vdat_det && usb_vdm)
dev_info(pnw->dev, "ACA-A detected\n");
}
if (iotg->hsm.id == ID_ACA_A || iotg->hsm.id == ID_ACA_B
|| iotg->hsm.id == ID_ACA_C) {
return CHRG_ACA;
}
return 0;
}
static int penwell_otg_charger_det_se1_clt(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval;
u8 data;
retval = penwell_otg_ulpi_read(iotg, ULPI_DEBUG, &data);
if (retval) {
dev_warn(pnw->dev, "ULPI read failed, exit\n");
return -EBUSY;
}
if ((data & CHRG_SERX_DP) && (data & CHRG_SERX_DM))
return CHRG_SE1;
return 0;
}
static int penwell_otg_charger_det_pri_clt(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval;
u8 vdat_det, serx_dm;
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, DPVSRCEN);
if (retval)
return retval;
msleep(110);
retval = penwell_otg_ulpi_read(iotg, ULPI_PWRCTRL, &vdat_det);
if (retval) {
dev_warn(pnw->dev, "ULPI read failed, exit\n");
return -EBUSY;
}
retval = penwell_otg_ulpi_read(iotg, ULPI_VS4, &serx_dm);
if (retval) {
dev_warn(pnw->dev, "ULPI read failed, exit\n");
return -EBUSY;
}
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR, DPVSRCEN);
if (retval)
return retval;
vdat_det &= VDATDET;
serx_dm &= CHRG_SERX_DM;
if ((!vdat_det) || serx_dm)
return CHRG_SDP;
return 0;
}
static int penwell_otg_charger_det_sec_clt(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval;
u8 vdat_det;
usleep_range(1000, 1500);
retval = penwell_otg_ulpi_write(iotg, ULPI_VS1CLR, DATAPOLARITY);
if (retval)
return retval;
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, DPVSRCEN);
if (retval)
return retval;
msleep(80);
retval = penwell_otg_ulpi_read(iotg, ULPI_PWRCTRL, &vdat_det);
if (retval) {
dev_warn(pnw->dev, "ULPI read failed, exit\n");
return retval;
}
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR, DPVSRCEN);
if (retval)
return retval;
retval = penwell_otg_ulpi_write(iotg, ULPI_VS1SET, DATAPOLARITY);
if (retval)
return retval;
vdat_det &= VDATDET;
if (vdat_det)
return CHRG_DCP;
else
return CHRG_CDP;
return 0;
}
static int penwell_otg_charger_det_clean_clt(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval;
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET,
DPVSRCEN | SWCNTRL);
if (retval)
return retval;
return 0;
}
/* As we use SW mode to do charger detection, need to notify HW
* the result SW get, charging port or not */
static int penwell_otg_charger_hwdet(bool enable)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval;
/* This is for CLV only */
if (!is_clovertrail(to_pci_dev(pnw->dev)))
return 0;
if (enable) {
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, HWDET);
if (retval)
return retval;
dev_dbg(pnw->dev, "set HWDETECT\n");
} else {
retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR, HWDET);
if (retval)
return retval;
dev_dbg(pnw->dev, "clear HWDETECT\n");
}
return 0;
}
static int penwell_otg_charger_det_clt(void)
{
struct penwell_otg *pnw = the_transceiver;
int retval;
dev_dbg(pnw->dev, "%s --->\n", __func__);
/* DCD */
retval = penwell_otg_charger_det_dcd_clt();
if (retval) {
dev_warn(pnw->dev, "DCD failed, exit\n");
return retval;
}
/* ACA Detection */
retval = penwell_otg_charger_det_aca_clt();
if (retval < 0) {
dev_warn(pnw->dev, "ACA Det failed, exit\n");
return retval;
} else if (retval == CHRG_ACA) {
dev_info(pnw->dev, "ACA detected\n");
penwell_otg_charger_hwdet(true);
return CHRG_ACA;
}
/* SE1 Detection */
retval = penwell_otg_charger_det_se1_clt();
if (retval < 0) {
dev_warn(pnw->dev, "SE1 Det failed, exit\n");
return retval;
} else if (retval == CHRG_SE1) {
dev_info(pnw->dev, "SE1 detected\n");
penwell_otg_charger_hwdet(true);
return CHRG_SE1;
}
/* Pri Det */
retval = penwell_otg_charger_det_pri_clt();
if (retval < 0) {
dev_warn(pnw->dev, "Pri Det failed, exit\n");
return retval;
} else if (retval == CHRG_SDP) {
dev_info(pnw->dev, "SDP detected\n");
return CHRG_SDP;
}
/* Sec Det */
retval = penwell_otg_charger_det_sec_clt();
if (retval < 0) {
dev_warn(pnw->dev, "Sec Det failed, exit\n");
return retval;
} else if (retval == CHRG_CDP) {
dev_info(pnw->dev, "CDP detected\n");
penwell_otg_charger_hwdet(true);
return CHRG_CDP;
} else if (retval == CHRG_DCP) {
dev_info(pnw->dev, "DCP detected\n");
penwell_otg_charger_det_clean_clt();
penwell_otg_charger_hwdet(true);
return CHRG_DCP;
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
return 0;
}
void penwell_otg_phy_vbus_wakeup(bool on)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
u8 flag = 0;
dev_dbg(pnw->dev, "%s --->\n", __func__);
penwell_otg_msic_spi_access(true);
flag = VBUSVLD | SESSVLD | SESSEND;
if (is_clovertrail(to_pci_dev(pnw->dev))) {
if (on) {
penwell_otg_ulpi_write(iotg, ULPI_USBINTEN_RISINGSET,
flag);
penwell_otg_ulpi_write(iotg, ULPI_USBINTEN_FALLINGSET,
flag);
} else {
penwell_otg_ulpi_write(iotg, ULPI_USBINTEN_RISINGCLR,
flag);
penwell_otg_ulpi_write(iotg, ULPI_USBINTEN_FALLINGCLR,
flag);
}
} else {
if (on) {
penwell_otg_msic_write(MSIC_USBINTEN_RISESET, flag);
penwell_otg_msic_write(MSIC_USBINTEN_FALLSET, flag);
} else {
penwell_otg_msic_write(MSIC_USBINTEN_RISECLR, flag);
penwell_otg_msic_write(MSIC_USBINTEN_FALLCLR, flag);
}
}
penwell_otg_msic_spi_access(false);
dev_dbg(pnw->dev, "%s --->\n", __func__);
}
void penwell_otg_phy_intr(bool on)
{
struct penwell_otg *pnw = the_transceiver;
u8 flag = 0;
int retval;
u8 data;
dev_dbg(pnw->dev, "%s --->\n", __func__);
penwell_otg_msic_spi_access(true);
flag = VBUSVLD | IDGND;
if (on) {
dev_info(pnw->dev, "enable VBUSVLD & IDGND\n");
penwell_otg_msic_write(MSIC_USBINTEN_RISESET, flag);
penwell_otg_msic_write(MSIC_USBINTEN_FALLSET, flag);
} else {
dev_info(pnw->dev, "disable VBUSVLD & IDGND\n");
penwell_otg_msic_write(MSIC_USBINTEN_RISECLR, flag);
penwell_otg_msic_write(MSIC_USBINTEN_FALLCLR, flag);
}
retval = intel_scu_ipc_ioread8(MSIC_USBINTEN_RISE, &data);
if (retval)
dev_warn(pnw->dev, "Failed to read MSIC register\n");
else
dev_info(pnw->dev, "MSIC_USBINTEN_RISE = 0x%x", data);
retval = intel_scu_ipc_ioread8(MSIC_USBINTEN_FALL, &data);
if (retval)
dev_warn(pnw->dev, "Failed to read MSIC register\n");
else
dev_info(pnw->dev, "MSIC_USBINTEN_FALL = 0x%x", data);
penwell_otg_msic_spi_access(false);
}
void penwell_otg_phy_power(int on)
{
struct penwell_otg *pnw = the_transceiver;
dev_dbg(pnw->dev, "%s --->\n", __func__);
if (is_clovertrail(to_pci_dev(pnw->dev))) {
dev_dbg(pnw->dev, "turn %s USB PHY by gpio_cs(%d)\n",
on ? "on" : "off",
pnw->otg_pdata->gpio_cs);
gpio_direction_output(pnw->otg_pdata->gpio_cs, on);
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
void penwell_otg_phy_reset(void)
{
struct penwell_otg *pnw = the_transceiver;
dev_dbg(pnw->dev, "%s --->\n", __func__);
if (is_clovertrail(to_pci_dev(pnw->dev))) {
gpio_direction_output(pnw->otg_pdata->gpio_reset, 0);
usleep_range(200, 500);
gpio_set_value(pnw->otg_pdata->gpio_reset, 1);
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
#if 0
knext_wa: usb_notify_warning should be implemented again instead of in hub.c
static void penwell_otg_notify_warning(int warning_code)
{
struct penwell_otg *pnw = the_transceiver;
dev_dbg(pnw->dev, "%s ---> %d\n", __func__, warning_code);
if (pnw && pnw->iotg.otg.otg->host && pnw->iotg.otg.otg->host->root_hub)
usb_notify_warning(pnw->iotg.otg.otg->host->root_hub,
warning_code);
else
dev_dbg(pnw->dev, "no valid device for notification\n");
dev_dbg(pnw->dev, "%s <--- %d\n", __func__, warning_code);
}
#else
#define penwell_otg_notify_warning(x)
#endif
void penwell_otg_nsf_msg(unsigned long indicator)
{
switch (indicator) {
case 2:
case 4:
case 6:
case 7:
dev_warn(the_transceiver->dev,
"NSF-%lu - device not responding\n", indicator);
break;
case 3:
dev_warn(the_transceiver->dev,
"NSF-%lu - device not supported\n", indicator);
break;
default:
dev_warn(the_transceiver->dev,
"Do not have this kind of NSF\n");
break;
}
}
/* The timeout callback function to set time out bit */
static void penwell_otg_timer_fn(unsigned long indicator)
{
struct penwell_otg *pnw = the_transceiver;
*(int *)indicator = 1;
dev_dbg(pnw->dev, "kernel timer - timeout\n");
penwell_update_transceiver();
}
/* kernel timer used for OTG timing */
static void penwell_otg_add_timer(enum penwell_otg_timer_type timers)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
unsigned long j = jiffies;
unsigned long data, time;
if (timer_pending(&pnw->hsm_timer))
return;
switch (timers) {
case TA_WAIT_VRISE_TMR:
iotg->hsm.a_wait_vrise_tmout = 0;
data = (unsigned long)&iotg->hsm.a_wait_vrise_tmout;
/* Charger HW limitation workaround for CLV */
time = is_clovertrail(to_pci_dev(pnw->dev)) ?
400 : TA_WAIT_VRISE;
dev_dbg(pnw->dev,
"Add timer TA_WAIT_VRISE = %lu\n", time);
break;
case TA_WAIT_BCON_TMR:
iotg->hsm.a_wait_bcon_tmout = 0;
data = (unsigned long)&iotg->hsm.a_wait_bcon_tmout;
time = TA_WAIT_BCON;
dev_dbg(pnw->dev,
"Add timer TA_WAIT_BCON = %d\n", TA_WAIT_BCON);
break;
case TA_AIDL_BDIS_TMR:
iotg->hsm.a_aidl_bdis_tmout = 0;
data = (unsigned long)&iotg->hsm.a_aidl_bdis_tmout;
time = TA_AIDL_BDIS;
dev_dbg(pnw->dev,
"Add timer TA_AIDL_BDIS = %d\n", TA_AIDL_BDIS);
break;
case TA_BIDL_ADIS_TMR:
iotg->hsm.a_bidl_adis_tmout = 0;
iotg->hsm.a_bidl_adis_tmr = 1;
data = (unsigned long)&iotg->hsm.a_bidl_adis_tmout;
time = TA_BIDL_ADIS;
dev_dbg(pnw->dev,
"Add timer TA_BIDL_ADIS = %d\n", TA_BIDL_ADIS);
break;
case TA_WAIT_VFALL_TMR:
iotg->hsm.a_wait_vfall_tmout = 0;
data = (unsigned long)&iotg->hsm.a_wait_vfall_tmout;
time = TA_WAIT_VFALL;
dev_dbg(pnw->dev,
"Add timer TA_WAIT_VFALL = %d\n", TA_WAIT_VFALL);
break;
case TB_ASE0_BRST_TMR:
iotg->hsm.b_ase0_brst_tmout = 0;
data = (unsigned long)&iotg->hsm.b_ase0_brst_tmout;
time = TB_ASE0_BRST;
dev_dbg(pnw->dev,
"Add timer TB_ASE0_BRST = %d\n", TB_ASE0_BRST);
break;
case TB_SRP_FAIL_TMR:
iotg->hsm.b_srp_fail_tmout = 0;
iotg->hsm.b_srp_fail_tmr = 1;
data = (unsigned long)&iotg->hsm.b_srp_fail_tmout;
time = TB_SRP_FAIL;
dev_dbg(pnw->dev,
"Add timer TB_SRP_FAIL = %d\n", TB_SRP_FAIL);
break;
/* support OTG test mode */
case TTST_MAINT_TMR:
iotg->hsm.tst_maint_tmout = 0;
data = (unsigned long)&iotg->hsm.tst_maint_tmout;
time = TTST_MAINT;
dev_dbg(pnw->dev,
"Add timer TTST_MAINT = %d\n", TTST_MAINT);
break;
case TTST_NOADP_TMR:
iotg->hsm.tst_noadp_tmout = 0;
data = (unsigned long)&iotg->hsm.tst_noadp_tmout;
time = TTST_NOADP;
dev_dbg(pnw->dev,
"Add timer TTST_NOADP = %d\n", TTST_NOADP);
break;
default:
dev_dbg(pnw->dev,
"unkown timer, can not enable such timer\n");
return;
}
pnw->hsm_timer.data = data;
pnw->hsm_timer.function = penwell_otg_timer_fn;
pnw->hsm_timer.expires = j + time * HZ / 1000; /* milliseconds */
add_timer(&pnw->hsm_timer);
}
static inline void penwell_otg_del_timer(enum penwell_otg_timer_type timers)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
switch (timers) {
case TA_BIDL_ADIS_TMR:
iotg->hsm.a_bidl_adis_tmr = 0;
break;
case TB_SRP_FAIL_TMR:
iotg->hsm.b_srp_fail_tmr = 0;
break;
case TA_WAIT_BCON_TMR:
iotg->hsm.a_wait_bcon_tmout = 0;
break;
case TTST_MAINT_TMR:
iotg->hsm.tst_maint_tmout = 0;
break;
case TTST_NOADP_TMR:
iotg->hsm.tst_noadp_tmout = 0;
break;
default:
break;
}
dev_dbg(pnw->dev, "state machine timer deleted\n");
del_timer_sync(&pnw->hsm_timer);
}
static void reset_otg(void)
{
struct penwell_otg *pnw = the_transceiver;
u32 val;
int delay_time = 1000;
dev_dbg(pnw->dev, "reseting OTG controller ...\n");
val = readl(pnw->iotg.base + CI_USBCMD);
writel(val | USBCMD_RST, pnw->iotg.base + CI_USBCMD);
do {
udelay(100);
if (!delay_time--)
dev_dbg(pnw->dev, "reset timeout\n");
val = readl(pnw->iotg.base + CI_USBCMD);
val &= USBCMD_RST;
} while (val != 0);
dev_dbg(pnw->dev, "reset done.\n");
}
static void pnw_phy_ctrl_rst(void)
{
struct penwell_otg *pnw = the_transceiver;
struct pci_dev *pdev;
pdev = to_pci_dev(pnw->dev);
/* mask id intr before reset and delay for 4 ms
* before unmasking id intr to avoid wrongly
* detecting ID_A which is a side-effect of reset
* PHY
*/
penwell_otg_intr(0);
synchronize_irq(pdev->irq);
penwell_otg_phy_reset();
reset_otg();
msleep(50);
penwell_otg_intr(1);
/* after reset, need to sync to OTGSC status bits to hsm */
update_hsm();
penwell_update_transceiver();
}
static void set_host_mode(void)
{
u32 val;
reset_otg();
val = readl(the_transceiver->iotg.base + CI_USBMODE);
val = (val & (~USBMODE_CM)) | USBMODE_HOST;
writel(val, the_transceiver->iotg.base + CI_USBMODE);
}
static void set_client_mode(void)
{
u32 val;
reset_otg();
val = readl(the_transceiver->iotg.base + CI_USBMODE);
val = (val & (~USBMODE_CM)) | USBMODE_DEVICE;
writel(val, the_transceiver->iotg.base + CI_USBMODE);
}
static void init_hsm(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
u32 val32;
/* read OTGSC after reset */
val32 = readl(iotg->base + CI_OTGSC);
dev_dbg(pnw->dev,
"%s: OTGSC init value = 0x%x\n", __func__, val32);
/* set init state */
if (val32 & OTGSC_ID) {
iotg->hsm.id = ID_B;
iotg->otg.otg->default_a = 0;
set_client_mode();
iotg->otg.state = OTG_STATE_B_IDLE;
} else {
iotg->hsm.id = ID_A;
iotg->otg.otg->default_a = 1;
set_host_mode();
iotg->otg.state = OTG_STATE_A_IDLE;
}
/* set session indicator */
if (val32 & OTGSC_BSE)
iotg->hsm.b_sess_end = 1;
if (val32 & OTGSC_BSV)
iotg->hsm.b_sess_vld = 1;
if (val32 & OTGSC_ASV)
iotg->hsm.a_sess_vld = 1;
if (val32 & OTGSC_AVV)
iotg->hsm.a_vbus_vld = 1;
/* default user is not request the bus */
iotg->hsm.a_bus_req = 1;
iotg->hsm.a_bus_drop = 0;
/* init hsm means power_up case */
iotg->hsm.power_up = 0;
/* defautly don't request bus as B device */
iotg->hsm.b_bus_req = 0;
/* no system error */
iotg->hsm.a_clr_err = 0;
if (iotg->otg.state == OTG_STATE_A_IDLE) {
wake_lock(&pnw->wake_lock);
pm_runtime_get(pnw->dev);
}
}
static void update_hsm(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
u32 val32;
/* read OTGSC */
val32 = readl(iotg->base + CI_OTGSC);
dev_dbg(pnw->dev,
"%s OTGSC current value = 0x%x\n", __func__, val32);
iotg->hsm.id = !!(val32 & OTGSC_ID) ? ID_B : ID_A;
iotg->hsm.b_sess_end = !!(val32 & OTGSC_BSE);
iotg->hsm.b_sess_vld = !!(val32 & OTGSC_BSV);
iotg->hsm.a_sess_vld = !!(val32 & OTGSC_ASV);
iotg->hsm.a_vbus_vld = !!(val32 & OTGSC_AVV);
}
static void penwell_otg_eye_diagram_optimize(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval;
u8 value = 0;
/* Check platform value as different value will be used*/
if (is_clovertrail(to_pci_dev(pnw->dev))) {
/* Set 0x7f for better quality in eye diagram
* It means ZHSDRV = 0b11 and IHSTX = 0b1111*/
value = 0x7f;
} else {
/* Set 0x77 for better quality in eye diagram
* It means ZHSDRV = 0b11 and IHSTX = 0b0111*/
value = 0x77;
}
retval = penwell_otg_ulpi_write(iotg, ULPI_VS1SET, value);
if (retval)
dev_warn(pnw->dev,
"eye diagram optimize failed with ulpi failure\n");
}
static irqreturn_t otg_dummy_irq(int irq, void *_dev)
{
struct penwell_otg *pnw = the_transceiver;
void __iomem *reg_base = _dev;
u32 val;
u32 int_mask = 0;
val = readl(reg_base + CI_USBMODE);
if ((val & USBMODE_CM) != USBMODE_DEVICE)
return IRQ_NONE;
val = readl(reg_base + CI_USBSTS);
int_mask = val & INTR_DUMMY_MASK;
if (int_mask == 0)
return IRQ_NONE;
/* clear hsm.b_conn here since host driver can't detect it
* otg_dummy_irq called means B-disconnect happened.
*/
if (pnw->iotg.hsm.b_conn) {
pnw->iotg.hsm.b_conn = 0;
penwell_update_transceiver();
}
/* Clear interrupts */
writel(int_mask, reg_base + CI_USBSTS);
return IRQ_HANDLED;
}
static irqreturn_t otg_irq_handle(struct penwell_otg *pnw,
struct intel_mid_otg_xceiv *iotg)
{
int id = 0;
int flag = 0;
u32 int_sts, int_en, int_mask = 0;
u8 usb_vs2_latch = 0;
u8 usb_vs2_sts = 0;
struct iotg_ulpi_access_ops *ops;
/* Check VBUS/SRP interrup */
int_sts = readl(pnw->iotg.base + CI_OTGSC);
int_en = (int_sts & OTGSC_INTEN_MASK) >> 8;
int_mask = int_sts & int_en;
if (int_mask == 0)
return IRQ_NONE;
ops = &iotg->ulpi_ops;
ops->read(iotg, ULPI_VS2LATCH, &usb_vs2_latch);
dev_dbg(pnw->dev, "usb_vs2_latch = 0x%x\n", usb_vs2_latch);
if (usb_vs2_latch & IDRARBRC_MSK) {
ops->read(iotg, ULPI_VS2STS, &usb_vs2_sts);
dev_dbg(pnw->dev, "%s: usb_vs2_sts = 0x%x\n",
__func__, usb_vs2_sts);
switch (IDRARBRC_STS(usb_vs2_sts)) {
case IDRARBRC_A:
id = ID_ACA_A;
dev_dbg(pnw->dev, "ACA-A interrupt detected\n");
break;
case IDRARBRC_B:
id = ID_ACA_B;
dev_dbg(pnw->dev, "ACA-B interrupt detected\n");
break;
case IDRARBRC_C:
id = ID_ACA_C;
dev_dbg(pnw->dev, "ACA-C interrupt detected\n");
break;
default:
break;
}
if (id) {
iotg->hsm.id = id;
flag = 1;
dev_dbg(pnw->dev, "%s: id change int = %d\n",
__func__, iotg->hsm.id);
} else {
iotg->hsm.id = (int_sts & OTGSC_ID) ?
ID_B : ID_A;
flag = 1;
dev_dbg(pnw->dev, "%s: id change int = %d\n",
__func__, iotg->hsm.id);
}
}
if (int_mask) {
dev_dbg(pnw->dev,
"OTGSC = 0x%x, mask =0x%x\n", int_sts, int_mask);
if (int_mask & OTGSC_IDIS) {
if (!id)
iotg->hsm.id = (int_sts & OTGSC_ID) ?
ID_B : ID_A;
flag = 1;
dev_dbg(pnw->dev, "%s: id change int = %d\n",
__func__, iotg->hsm.id);
/* Update a_vbus_valid once ID changed */
iotg->hsm.a_vbus_vld = (int_sts & OTGSC_AVV) ? 1 : 0;
}
if (int_mask & OTGSC_DPIS) {
iotg->hsm.a_srp_det = (int_sts & OTGSC_DPS) ? 1 : 0;
flag = 1;
dev_dbg(pnw->dev, "%s: data pulse int = %d\n",
__func__, iotg->hsm.a_srp_det);
}
if (int_mask & OTGSC_BSEIS) {
iotg->hsm.b_sess_end = (int_sts & OTGSC_BSE) ? 1 : 0;
flag = 1;
dev_dbg(pnw->dev, "%s: b sess end int = %d\n",
__func__, iotg->hsm.b_sess_end);
}
if (int_mask & OTGSC_BSVIS) {
iotg->hsm.b_sess_vld = (int_sts & OTGSC_BSV) ? 1 : 0;
flag = 1;
dev_dbg(pnw->dev, "%s: b sess valid int = %d\n",
__func__, iotg->hsm.b_sess_vld);
}
if (int_mask & OTGSC_ASVIS) {
iotg->hsm.a_sess_vld = (int_sts & OTGSC_ASV) ? 1 : 0;
flag = 1;
dev_dbg(pnw->dev, "%s: a sess valid int = %d\n",
__func__, iotg->hsm.a_sess_vld);
}
if (int_mask & OTGSC_AVVIS) {
iotg->hsm.a_vbus_vld = (int_sts & OTGSC_AVV) ? 1 : 0;
flag = 1;
dev_dbg(pnw->dev, "%s: a vbus valid int = %d\n",
__func__, iotg->hsm.a_vbus_vld);
}
writel((int_sts & ~OTGSC_INTSTS_MASK) | int_mask,
pnw->iotg.base + CI_OTGSC);
}
if (flag)
penwell_update_transceiver();
return IRQ_HANDLED;
}
static irqreturn_t otg_irq(int irq, void *_dev)
{
struct penwell_otg *pnw = _dev;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
unsigned long flags;
#ifdef CONFIG_PM_RUNTIME
if (pnw->rt_resuming)
return IRQ_HANDLED;
/* If it's not active, resume device first before access regs */
if (pnw->rt_quiesce) {
spin_lock_irqsave(&pnw->lock, flags);
if (pnw->rt_quiesce) {
dev_dbg(pnw->dev, "Wake up? Interrupt detected in suspended\n");
pnw->rt_resuming = 1;
pm_runtime_get(pnw->dev);
}
spin_unlock_irqrestore(&pnw->lock, flags);
return IRQ_HANDLED;
}
#endif /* CONFIG_PM_RUNTIME */
return otg_irq_handle(pnw, iotg);
}
static void penwell_otg_start_ulpi_poll(void)
{
struct penwell_otg *pnw = the_transceiver;
int retval = 0;
retval = penwell_otg_ulpi_write(&pnw->iotg, 0x16, 0x5a);
if (retval)
dev_err(pnw->dev, "ulpi write in init failed\n");
schedule_delayed_work(&pnw->ulpi_poll_work, HZ);
}
static void penwell_otg_continue_ulpi_poll(void)
{
struct penwell_otg *pnw = the_transceiver;
schedule_delayed_work(&pnw->ulpi_poll_work, HZ);
}
static void penwell_otg_stop_ulpi_poll(void)
{
struct penwell_otg *pnw = the_transceiver;
/* we have to use this version because this function can
* be called in irq handler */
__cancel_delayed_work(&pnw->ulpi_poll_work);
}
static int penwell_otg_iotg_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = data;
int flag = 0;
struct pci_dev *pdev;
if (iotg == NULL)
return NOTIFY_BAD;
if (pnw == NULL)
return NOTIFY_BAD;
pdev = to_pci_dev(pnw->dev);
switch (action) {
case MID_OTG_NOTIFY_CONNECT:
dev_dbg(pnw->dev, "PNW OTG Notify Connect Event\n");
if (iotg->otg.otg->default_a == 1)
iotg->hsm.b_conn = 1;
else
iotg->hsm.a_conn = 1;
flag = 1;
break;
case MID_OTG_NOTIFY_DISCONN:
dev_dbg(pnw->dev, "PNW OTG Notify Disconnect Event\n");
if (iotg->otg.otg->default_a == 1)
iotg->hsm.b_conn = 0;
else
iotg->hsm.a_conn = 0;
flag = 1;
break;
case MID_OTG_NOTIFY_HSUSPEND:
dev_dbg(pnw->dev, "PNW OTG Notify Host Bus suspend Event\n");
break;
case MID_OTG_NOTIFY_HRESUME:
dev_dbg(pnw->dev, "PNW OTG Notify Host Bus resume Event\n");
if (iotg->otg.otg->default_a == 1 && iotg->hsm.a_bus_req == 0) {
iotg->hsm.a_bus_req = 1;
flag = 1;
}
break;
case MID_OTG_NOTIFY_CSUSPEND:
dev_dbg(pnw->dev, "PNW OTG Notify Client Bus suspend Event\n");
if (iotg->otg.otg->default_a == 1) {
iotg->hsm.b_bus_suspend = 1;
flag = 1;
} else {
penwell_otg_stop_ulpi_poll();
if (iotg->hsm.a_bus_suspend == 0) {
iotg->hsm.a_bus_suspend = 1;
flag = 1;
} else
flag = 0;
}
break;
case MID_OTG_NOTIFY_CRESUME:
dev_dbg(pnw->dev, "PNW OTG Notify Client Bus resume Event\n");
if (iotg->otg.otg->default_a == 1) {
/* in A_PERIPHERAL state */
iotg->hsm.b_bus_suspend = 0;
flag = 1;
} else {
/* in B_PERIPHERAL state */
if (!is_clovertrail(pdev))
penwell_otg_start_ulpi_poll();
iotg->hsm.a_bus_suspend = 0;
flag = 0;
}
break;
case MID_OTG_NOTIFY_CRESET:
dev_dbg(pnw->dev, "PNW OTG Notify Client Bus reset Event\n");
penwell_otg_set_power(&pnw->iotg.otg, CHRG_CURR_SDP_SUSP);
flag = 0;
break;
case MID_OTG_NOTIFY_HOSTADD:
dev_dbg(pnw->dev, "PNW OTG Nofity Host Driver Add\n");
flag = 1;
break;
case MID_OTG_NOTIFY_HOSTREMOVE:
dev_dbg(pnw->dev, "PNW OTG Nofity Host Driver remove\n");
flag = 1;
break;
case MID_OTG_NOTIFY_CLIENTADD:
dev_dbg(pnw->dev, "PNW OTG Nofity Client Driver Add\n");
flag = 1;
break;
case MID_OTG_NOTIFY_CLIENTREMOVE:
dev_dbg(pnw->dev, "PNW OTG Nofity Client Driver remove\n");
flag = 1;
break;
/* Test mode support */
case MID_OTG_NOTIFY_TEST_MODE_START:
dev_dbg(pnw->dev, "PNW OTG Notfiy Client Testmode Start\n");
iotg->hsm.in_test_mode = 1;
flag = 0;
break;
case MID_OTG_NOTIFY_TEST_MODE_STOP:
dev_dbg(pnw->dev, "PNW OTG Notfiy Client Testmode Stop\n");
iotg->hsm.in_test_mode = 0;
flag = 0;
break;
case MID_OTG_NOTIFY_TEST_SRP_REQD:
dev_dbg(pnw->dev, "PNW OTG Notfiy Client SRP REQD\n");
iotg->hsm.otg_srp_reqd = 1;
flag = 1;
break;
case MID_OTG_NOTIFY_TEST:
dev_dbg(pnw->dev, "PNW OTG Notfiy Test device detected\n");
iotg->hsm.test_device = 1;
flag = 0;
break;
case MID_OTG_NOTIFY_TEST_VBUS_OFF:
dev_dbg(pnw->dev, "PNW OTG Notfiy Test device Vbus off mode\n");
iotg->hsm.test_device = 1;
iotg->hsm.otg_vbus_off = 1;
flag = 1;
break;
default:
dev_dbg(pnw->dev, "PNW OTG Nofity unknown notify message\n");
return NOTIFY_DONE;
}
if (flag)
penwell_update_transceiver();
return NOTIFY_OK;
}
static void penwell_otg_hnp_poll_work(struct work_struct *work)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
struct usb_device *udev;
int err = 0;
u8 data;
if (iotg->otg.otg->host && iotg->otg.otg->host->root_hub) {
udev = usb_hub_find_child(iotg->otg.otg->host->root_hub, 1);
} else {
dev_warn(pnw->dev, "no host or root_hub registered\n");
return;
}
if (iotg->otg.state != OTG_STATE_A_HOST
&& iotg->otg.state != OTG_STATE_B_HOST)
return;
if (!udev) {
dev_warn(pnw->dev,
"no usb dev connected, stop HNP polling\n");
return;
}
/* Skip HS Electrical Test Device */
if (le16_to_cpu(udev->descriptor.idVendor) == 0x1A0A &&
le16_to_cpu(udev->descriptor.idProduct) > 0x0100 &&
le16_to_cpu(udev->descriptor.idProduct) < 0x0109) {
return;
}
/* get host request flag from connected USB device */
err = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN, 0, 0xF000, &data, 1, 5000);
if (err < 0) {
dev_warn(pnw->dev,
"ERR in HNP polling = %d, stop HNP polling\n", err);
return;
}
if (data & HOST_REQUEST_FLAG) {
/* start HNP sequence to switch role */
dev_dbg(pnw->dev, "host_request_flag = 1\n");
if (iotg->hsm.id == ID_B) {
dev_dbg(pnw->dev,
"Device B host - start HNP - b_bus_req = 0\n");
iotg->hsm.b_bus_req = 0;
} else if (iotg->hsm.id == ID_A) {
dev_dbg(pnw->dev,
"Device A host - start HNP - a_bus_req = 0\n");
iotg->hsm.a_bus_req = 0;
}
penwell_update_transceiver();
} else {
dev_dbg(pnw->dev, "host_request_flag = 0\n");
penwell_otg_continue_hnp_poll(&pnw->iotg);
}
}
static int penwell_otg_ulpi_check(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
u8 data;
int retval;
retval = penwell_otg_ulpi_read(iotg, 0x16, &data);
if (retval) {
dev_err(pnw->dev,
"%s: [ ULPI hang ] detected\n"
"reset PHY & ctrl to recover\n",
__func__);
pnw_phy_ctrl_rst();
return retval;
}
return 0;
}
static void penwell_otg_ulpi_check_work(struct work_struct *work)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int status;
status = pm_runtime_get_sync(pnw->dev);
if (status < 0) {
dev_err(pnw->dev, "%s: pm_runtime_get_sync FAILED err = %d\n",
__func__, status);
pm_runtime_put_sync(pnw->dev);
return;
}
if (iotg->otg.state == OTG_STATE_B_IDLE) {
/* Before charger detection or charger detection done */
dev_dbg(pnw->dev, "ulpi_check health\n");
penwell_otg_ulpi_check();
} else if (iotg->otg.state == OTG_STATE_B_PERIPHERAL) {
/* After charger detection, SDP/CDP is detected */
dev_dbg(pnw->dev, "ulpi_check health\n");
status = penwell_otg_ulpi_check();
if (status) {
/* After phy rst then restart peripheral stack */
if (iotg->stop_peripheral)
iotg->stop_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver not support\n");
if (iotg->start_peripheral)
iotg->start_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver not support\n");
}
}
pm_runtime_put(pnw->dev);
}
static void penwell_otg_uevent_work(struct work_struct *work)
{
struct penwell_otg *pnw = the_transceiver;
char *uevent_envp[2] = { "USB_INTR=BOGUS", NULL };
dev_info(pnw->dev, "%s: send uevent USB_INTR=BOGUS\n", __func__);
kobject_uevent_env(&pnw->dev->kobj, KOBJ_CHANGE, uevent_envp);
}
static void penwell_otg_ulpi_poll_work(struct work_struct *work)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
u8 data;
if (iotg->otg.state != OTG_STATE_B_PERIPHERAL)
return;
if (iotg->hsm.in_test_mode)
return;
if (penwell_otg_ulpi_read(iotg, 0x16, &data)) {
dev_err(pnw->dev, "ulpi read time out by polling\n");
iotg->hsm.ulpi_error = 1;
iotg->hsm.ulpi_polling = 0;
penwell_update_transceiver();
} else if (data != 0x5A) {
dev_err(pnw->dev, "ulpi read value incorrect by polling\n");
iotg->hsm.ulpi_error = 1;
iotg->hsm.ulpi_polling = 0;
penwell_update_transceiver();
} else {
dev_dbg(pnw->dev, "ulpi fine by polling\n");
iotg->hsm.ulpi_error = 0;
iotg->hsm.ulpi_polling = 1;
penwell_otg_continue_ulpi_poll();
}
}
static void penwell_otg_psc_notify_work(struct work_struct *work)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
struct power_supply_cable_props psc_cap;
enum power_supply_charger_event chrg_event;
unsigned long flags;
struct otg_bc_event *event, *temp;
spin_lock_irqsave(&pnw->charger_lock, flags);
list_for_each_entry_safe(event, temp, &pnw->chrg_evt_queue, node) {
list_del(&event->node);
spin_unlock_irqrestore(&pnw->charger_lock, flags);
spin_lock_irqsave(&pnw->cap_lock, flags);
chrg_event = check_psc_event(pnw->psc_cap, event->cap);
if (chrg_event == -1)
dev_dbg(pnw->dev, "no need to notify\n");
else if (chrg_event == POWER_SUPPLY_CHARGER_EVENT_DISCONNECT) {
/* In Disconnect case, EM driver needs same chrg type
* like Connect even, construct one here */
psc_cap = event->cap;
psc_cap.chrg_evt = chrg_event;
psc_cap.chrg_type = pnw->psc_cap.chrg_type;
pnw->psc_cap = event->cap;
pnw->psc_cap.chrg_evt = chrg_event;
} else {
pnw->psc_cap = event->cap;
pnw->psc_cap.chrg_evt = chrg_event;
psc_cap = pnw->psc_cap;
}
spin_unlock_irqrestore(&pnw->cap_lock, flags);
if (chrg_event != -1) {
dev_dbg(pnw->dev, "ma = %d, evt = %d, type = %s\n",
psc_cap.ma, psc_cap.chrg_evt,
psc_string(psc_cap.chrg_type));
atomic_notifier_call_chain(&iotg->otg.notifier,
USB_EVENT_CHARGER, &psc_cap);
}
kfree(event);
spin_lock_irqsave(&pnw->charger_lock, flags);
}
spin_unlock_irqrestore(&pnw->charger_lock, flags);
}
static void penwell_otg_sdp_check_work(struct work_struct *work)
{
struct penwell_otg *pnw = the_transceiver;
struct otg_bc_cap cap;
/* Need to handle MFLD/CLV differently per different interface */
if (!is_clovertrail(to_pci_dev(pnw->dev))) {
if (penwell_otg_query_charging_cap(&cap)) {
dev_warn(pnw->dev, "SDP checking failed\n");
return;
}
/* If current charging cap is still 100ma SDP,
* assume this is a invalid charger and do 500ma
* charging */
if (cap.ma != 100 || cap.chrg_type != CHRG_SDP)
return;
} else
return;
dev_info(pnw->dev, "Notify invalid SDP at %dma\n", CHRG_CURR_SDP_INVAL);
penwell_otg_update_chrg_cap(CHRG_SDP_INVAL, CHRG_CURR_SDP_INVAL);
}
static void penwell_otg_work(struct work_struct *work)
{
struct penwell_otg *pnw = container_of(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;
enum power_supply_charger_cable_type type;
int retval;
struct pci_dev *pdev;
unsigned long flags;
dev_dbg(pnw->dev,
"old state = %s\n", state_string(iotg->otg.state));
pm_runtime_get_sync(pnw->dev);
pdev = to_pci_dev(pnw->dev);
switch (iotg->otg.state) {
case OTG_STATE_UNDEFINED:
case OTG_STATE_B_IDLE:
if (hsm->id == ID_A || hsm->id == ID_ACA_A) {
/* Move to A_IDLE state, ID changes */
/* Delete current timer */
penwell_otg_del_timer(TB_SRP_FAIL_TMR);
iotg->otg.otg->default_a = 1;
hsm->a_srp_det = 0;
set_host_mode();
penwell_otg_phy_low_power(0);
/* Always set a_bus_req to 1, in case no ADP */
hsm->a_bus_req = 1;
/* Prevent device enter D0i1 or S3*/
wake_lock(&pnw->wake_lock);
pm_runtime_get(pnw->dev);
iotg->otg.state = OTG_STATE_A_IDLE;
penwell_update_transceiver();
} else if (hsm->b_adp_sense_tmout) {
hsm->b_adp_sense_tmout = 0;
} else if (hsm->b_srp_fail_tmout) {
hsm->b_srp_fail_tmr = 0;
hsm->b_srp_fail_tmout = 0;
hsm->b_bus_req = 0;
penwell_otg_nsf_msg(6);
penwell_update_transceiver();
} else if (hsm->b_sess_vld) {
/* Check if DCP is detected */
spin_lock_irqsave(&pnw->charger_lock, flags);
charger_type = pnw->charging_cap.chrg_type;
type = pnw->psc_cap.chrg_type;
if (charger_type == CHRG_DCP ||
type == POWER_SUPPLY_CHARGER_TYPE_USB_DCP) {
spin_unlock_irqrestore(&pnw->charger_lock,
flags);
break;
}
spin_unlock_irqrestore(&pnw->charger_lock, flags);
penwell_otg_phy_low_power(0);
/* Clear power_up */
hsm->power_up = 0;
/* Move to B_PERIPHERAL state, Session Valid */
/* Delete current timer */
penwell_otg_del_timer(TB_SRP_FAIL_TMR);
hsm->b_sess_end = 0;
hsm->a_bus_suspend = 0;
/* Start USB Battery charger detection flow */
/* We need new charger detection flow for Clovertrail.
* But for now(power-on), we just skip it.
* Later on we'll figure it out.
*/
if (!is_clovertrail(pdev)) {
mutex_lock(&pnw->msic_mutex);
if (pdev->revision >= 0x8) {
retval = penwell_otg_manual_chrg_det();
if (retval < 0) {
/* if failed, reset controller
* and try charger detection
* flow again */
dev_warn(pnw->dev,
"detection failed, retry");
set_client_mode();
msleep(100);
penwell_otg_phy_low_power(0);
penwell_spi_reset_phy();
retval = penwell_otg_manual_chrg_det();
}
} else {
/* Enable data contact detection */
penwell_otg_data_contact_detect();
/* Enable charger detection */
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;
}
} else {
/* Clovertrail charger detection flow */
retval = penwell_otg_charger_det_clt();
if (retval < 0) {
dev_warn(pnw->dev, "detect failed\n");
/* Reset PHY and redo the detection */
pnw_phy_ctrl_rst();
/* Restart charger detection */
retval = penwell_otg_charger_det_clt();
if (retval)
dev_warn(pnw->dev,
"detect fail again\n");
break;
} else
charger_type = retval;
}
/* This is a workaround for self-powered hub case,
* vbus valid event comes several ms before id change */
if (hsm->id == ID_A) {
dev_warn(pnw->dev, "ID changed\n");
break;
}
if (charger_type == CHRG_SE1) {
dev_info(pnw->dev, "SE1 detected\n");
/* SE1: set charger type, current, notify EM */
penwell_otg_update_chrg_cap(CHRG_SE1,
CHRG_CURR_SE1);
dev_info(pnw->dev,
"reset PHY via SPI if SE1 detected\n");
if (!is_clovertrail(pdev)) {
/* Reset PHY for MFLD only */
penwell_otg_msic_spi_access(true);
penwell_otg_msic_write(MSIC_FUNCTRLSET,
PHYRESET);
penwell_otg_msic_spi_access(false);
}
break;
} else if (charger_type == CHRG_DCP) {
dev_info(pnw->dev, "DCP detected\n");
/* DCP: set charger type, current, notify EM */
penwell_otg_update_chrg_cap(CHRG_DCP,
CHRG_CURR_DCP);
#if defined(CONFIG_ME372CG_BATTERY_SMB345)
setSMB345Charger(AC_IN);
#endif
set_client_mode();
break;
} else if (charger_type == CHRG_ACA) {
dev_info(pnw->dev, "ACA detected\n");
if (hsm->id == ID_ACA_A) {
/* Move to A_IDLE state, ID changes */
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
/* Delete current timer */
penwell_otg_del_timer(TB_SRP_FAIL_TMR);
iotg->otg.otg->default_a = 1;
hsm->a_srp_det = 0;
set_host_mode();
penwell_otg_phy_low_power(0);
/* Always set a_bus_req to 1,
* in case no ADP */
hsm->a_bus_req = 1;
/* Prevent device enter D0i1 or S3*/
wake_lock(&pnw->wake_lock);
pm_runtime_get(pnw->dev);
iotg->otg.state = OTG_STATE_A_IDLE;
penwell_update_transceiver();
break;
} else if (hsm->id == ID_ACA_B) {
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
break;
} else if (hsm->id == ID_ACA_C) {
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
/* Clear HNP polling flag */
if (iotg->otg.otg->gadget)
iotg->otg.otg->gadget->
host_request_flag = 0;
penwell_otg_phy_low_power(0);
set_client_mode();
if (iotg->start_peripheral) {
iotg->start_peripheral(iotg);
} else {
dev_dbg(pnw->dev,
"client driver not support\n");
break;
}
}
} else if (charger_type == CHRG_CDP) {
dev_info(pnw->dev, "CDP detected\n");
/* MFLD WA: MSIC issue need disable phy intr */
if (!is_clovertrail(pdev)) {
dev_dbg(pnw->dev,
"MFLD WA: enable PHY int\n");
penwell_otg_phy_intr(0);
}
#if defined(CONFIG_ME372CG_BATTERY_SMB345)
setSMB345Charger(AC_IN);
#endif
/* CDP: set charger type, current, notify EM */
penwell_otg_update_chrg_cap(CHRG_CDP,
CHRG_CURR_CDP);
/* Clear HNP polling flag */
if (iotg->otg.otg->gadget)
iotg->otg.otg->gadget->
host_request_flag = 0;
if (iotg->start_peripheral) {
iotg->start_peripheral(iotg);
} else {
dev_dbg(pnw->dev,
"client driver not support\n");
break;
}
} else if (charger_type == CHRG_SDP) {
dev_info(pnw->dev, "SDP detected\n");
#if defined(CONFIG_ME372CG_BATTERY_SMB345)
setSMB345Charger(USB_IN);
#endif
/* MFLD WA: MSIC issue need disable phy intr */
if (!is_clovertrail(pdev)) {
dev_dbg(pnw->dev,
"MFLD WA: enable PHY int\n");
penwell_otg_phy_intr(0);
}
/* SDP: set charger type and 100ma by default */
penwell_otg_update_chrg_cap(CHRG_SDP, 100);
/* Clear HNP polling flag */
if (iotg->otg.otg->gadget)
iotg->otg.otg->gadget->
host_request_flag = 0;
penwell_otg_phy_low_power(0);
set_client_mode();
if (iotg->start_peripheral) {
iotg->start_peripheral(iotg);
} else {
dev_dbg(pnw->dev,
"client driver not support\n");
break;
}
/* Schedule the SDP checking after TIMEOUT */
queue_delayed_work(pnw->qwork,
&pnw->sdp_check_work,
INVALID_SDP_TIMEOUT);
} else if (charger_type == CHRG_UNKNOWN) {
dev_info(pnw->dev, "Unknown Charger Found\n");
#if defined(CONFIG_ME372CG_BATTERY_SMB345)
setSMB345Charger(USB_IN);
#endif
/* Unknown: set charger type */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN, 0);
}
penwell_otg_eye_diagram_optimize();
/* MFLD WA for PHY issue */
iotg->hsm.in_test_mode = 0;
iotg->hsm.ulpi_error = 0;
if (!is_clovertrail(pdev))
penwell_otg_start_ulpi_poll();
iotg->otg.state = OTG_STATE_B_PERIPHERAL;
} else if ((hsm->b_bus_req || hsm->power_up || hsm->adp_change
|| hsm->otg_srp_reqd) && !hsm->b_srp_fail_tmr) {
penwell_otg_mon_bus();
if (hsm->b_ssend_srp && hsm->b_se0_srp) {
hsm->power_up = 0;
hsm->adp_change = 0;
/* clear the PHCD before start srp */
penwell_otg_phy_low_power(0);
/* Start SRP */
if (pnw->iotg.otg.otg->start_srp)
pnw->iotg.otg.otg->start_srp(
pnw->iotg.otg.otg);
penwell_otg_add_timer(TB_SRP_FAIL_TMR);
} else {
hsm->b_bus_req = 0;
dev_info(pnw->dev,
"BUS is active, try SRP later\n");
}
/* clear after SRP attemp */
if (hsm->otg_srp_reqd) {
dev_dbg(pnw->dev, "Test mode: SRP done\n");
hsm->otg_srp_reqd = 0;
}
} else if (!hsm->b_sess_vld && hsm->id == ID_B) {
spin_lock_irqsave(&pnw->charger_lock, flags);
charger_type = pnw->charging_cap.chrg_type;
type = pnw->psc_cap.chrg_type;
spin_unlock_irqrestore(&pnw->charger_lock, flags);
if (charger_type == CHRG_DCP) {
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
retval = penwell_otg_ulpi_write(iotg,
ULPI_PWRCTRLCLR, DPVSRCEN);
if (retval)
dev_warn(pnw->dev, "ulpi failed\n");
penwell_otg_charger_hwdet(false);
} else if (charger_type == CHRG_SE1) {
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
/* WA: on SE1 detach, reset PHY over SPI */
dev_info(pnw->dev,
"reset PHY over SPI if SE1 detached\n");
penwell_otg_msic_spi_access(true);
penwell_otg_msic_write(MSIC_FUNCTRLSET,
PHYRESET);
penwell_otg_msic_spi_access(false);
} else if (type == POWER_SUPPLY_CHARGER_TYPE_USB_ACA) {
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
penwell_otg_charger_hwdet(false);
} else if (type == POWER_SUPPLY_CHARGER_TYPE_USB_DCP) {
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
retval = penwell_otg_ulpi_write(iotg,
ULPI_PWRCTRLCLR, DPVSRCEN);
if (retval)
dev_warn(pnw->dev, "ulpi failed\n");
penwell_otg_charger_hwdet(false);
} else if (type == POWER_SUPPLY_CHARGER_TYPE_SE1) {
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
}
#if defined(CONFIG_ME372CG_BATTERY_SMB345)
setSMB345Charger(CABLE_OUT);
#endif
}
break;
case OTG_STATE_B_PERIPHERAL:
/* FIXME: Check if ID_ACA_A event will happened in this state */
if (hsm->id == ID_A) {
iotg->otg.otg->default_a = 1;
hsm->a_srp_det = 0;
cancel_delayed_work_sync(&pnw->ulpi_poll_work);
cancel_delayed_work_sync(&pnw->sdp_check_work);
penwell_otg_charger_hwdet(false);
if (iotg->stop_peripheral)
iotg->stop_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver has been removed.\n");
set_host_mode();
/* Always set a_bus_req to 1, in case no ADP */
hsm->a_bus_req = 1;
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
/* Prevent device enter D0i1 or S3*/
wake_lock(&pnw->wake_lock);
pm_runtime_get(pnw->dev);
iotg->otg.state = OTG_STATE_A_IDLE;
penwell_update_transceiver();
} else if (hsm->ulpi_error && !hsm->in_test_mode) {
/* WA: try to recover once detected PHY issue */
hsm->ulpi_error = 0;
cancel_delayed_work_sync(&pnw->ulpi_poll_work);
cancel_delayed_work_sync(&pnw->sdp_check_work);
if (iotg->stop_peripheral)
iotg->stop_peripheral(iotg);
msleep(2000);
if (iotg->start_peripheral)
iotg->start_peripheral(iotg);
if (!is_clovertrail(pdev))
penwell_otg_start_ulpi_poll();
} else if (!hsm->b_sess_vld || hsm->id == ID_ACA_B) {
/* Move to B_IDLE state, VBUS off/ACA */
cancel_delayed_work(&pnw->ulpi_poll_work);
cancel_delayed_work_sync(&pnw->sdp_check_work);
hsm->b_bus_req = 0;
if (is_clovertrail(pdev)) {
queue_delayed_work(pnw->qwork,
&pnw->ulpi_check_work, HZ);
}
if (iotg->stop_peripheral)
iotg->stop_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver has been removed.\n");
/* MFLD WA: reenable it for unplug event */
if (!is_clovertrail(pdev)) {
dev_dbg(pnw->dev, "MFLD WA: disable PHY int\n");
penwell_otg_phy_intr(1);
}
if (hsm->id == ID_ACA_B)
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
else if (hsm->id == ID_B) {
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
penwell_otg_charger_hwdet(false);
}
iotg->otg.state = OTG_STATE_B_IDLE;
} else if (hsm->b_bus_req && hsm->a_bus_suspend
&& iotg->otg.otg->gadget
&& iotg->otg.otg->gadget->b_hnp_enable) {
penwell_otg_phy_low_power(0);
msleep(10);
if (iotg->stop_peripheral)
iotg->stop_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver has been removed.\n");
penwell_otg_phy_low_power(0);
hsm->a_conn = 0;
hsm->a_bus_resume = 0;
if (iotg->start_host) {
iotg->start_host(iotg);
hsm->test_device = 0;
/* FIXME: can we allow D3 and D0i3
* in B_WAIT_ACON?
* Now just disallow it
*/
/* disallow D3 or D0i3 */
pm_runtime_get(pnw->dev);
wake_lock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_B_WAIT_ACON;
penwell_otg_add_timer(TB_ASE0_BRST_TMR);
} else
dev_dbg(pnw->dev, "host driver not loaded.\n");
} else if (hsm->id == ID_ACA_C) {
cancel_delayed_work_sync(&pnw->sdp_check_work);
/* Make sure current limit updated */
penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
} else if (hsm->id == ID_B) {
spin_lock_irqsave(&pnw->charger_lock, flags);
type = pnw->psc_cap.chrg_type;
spin_unlock_irqrestore(&pnw->charger_lock, flags);
if (type == POWER_SUPPLY_CHARGER_TYPE_USB_ACA) {
/* Notify EM charger ACA removal event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
penwell_otg_charger_hwdet(false);
/* Set current when switch from ACA to SDP */
if (!hsm->a_bus_suspend && iotg->otg.set_power)
iotg->otg.set_power(&iotg->otg, 500);
}
}
break;
case OTG_STATE_B_WAIT_ACON:
if (hsm->id == ID_A) {
/* Move to A_IDLE state, ID changes */
/* Delete current timer */
penwell_otg_del_timer(TB_ASE0_BRST_TMR);
iotg->otg.otg->default_a = 1;
hsm->a_srp_det = 0;
penwell_otg_HAAR(0);
PNW_STOP_HOST(pnw);
set_host_mode();
/* Always set a_bus_req to 1, in case no ADP */
iotg->hsm.a_bus_req = 1;
iotg->otg.state = OTG_STATE_A_IDLE;
penwell_update_transceiver();
} else if (!hsm->b_sess_vld || hsm->id == ID_ACA_B) {
/* Move to B_IDLE state, VBUS off/ACA */
if (hsm->id == ID_ACA_B)
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
else if (hsm->id == ID_B) {
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
}
/* Delete current timer */
penwell_otg_del_timer(TB_ASE0_BRST_TMR);
hsm->b_hnp_enable = 0;
hsm->b_bus_req = 0;
penwell_otg_HAAR(0);
PNW_STOP_HOST(pnw);
set_client_mode();
/* allow D3 and D0i3 */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_B_IDLE;
} else if (hsm->a_conn) {
/* Move to B_HOST state, A connected */
/* Delete current timer */
penwell_otg_del_timer(TB_ASE0_BRST_TMR);
penwell_otg_HAAR(0);
iotg->otg.state = OTG_STATE_B_HOST;
penwell_update_transceiver();
} else if (hsm->a_bus_resume || hsm->b_ase0_brst_tmout) {
/* Move to B_HOST state, A connected */
/* Delete current timer */
penwell_otg_del_timer(TB_ASE0_BRST_TMR);
penwell_otg_HAAR(0);
penwell_otg_nsf_msg(7);
PNW_STOP_HOST(pnw);
hsm->a_bus_suspend = 0;
hsm->b_bus_req = 0;
if (iotg->start_peripheral)
iotg->start_peripheral(iotg);
else
dev_dbg(pnw->dev, "client driver not loaded\n");
/* allow D3 and D0i3 in A_WAIT_BCON */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_B_PERIPHERAL;
} else if (hsm->id == ID_ACA_C) {
/* Make sure current limit updated */
penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
} else if (hsm->id == ID_B) {
#if 0
/* only set 2ma due to client function stopped */
if (iotg->otg.set_power)
iotg->otg.set_power(&iotg->otg, 2);
#endif
}
break;
case OTG_STATE_B_HOST:
if (hsm->id == ID_A) {
iotg->otg.otg->default_a = 1;
hsm->a_srp_det = 0;
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
PNW_STOP_HOST(pnw);
set_host_mode();
/* Always set a_bus_req to 1, in case no ADP */
hsm->a_bus_req = 1;
iotg->otg.state = OTG_STATE_A_IDLE;
penwell_update_transceiver();
} else if (!hsm->b_sess_vld || hsm->id == ID_ACA_B) {
/* Move to B_IDLE state, VBUS off/ACA */
if (hsm->id == ID_ACA_B)
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
else if (hsm->id == ID_B) {
/* Notify EM charger remove event */
penwell_otg_update_chrg_cap(CHRG_UNKNOWN,
CHRG_CURR_DISCONN);
}
hsm->b_hnp_enable = 0;
hsm->b_bus_req = 0;
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
PNW_STOP_HOST(pnw);
set_client_mode();
/* allow D3 and D0i3 in A_WAIT_BCON */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_B_IDLE;
} else if (!hsm->b_bus_req || !hsm->a_conn
|| hsm->test_device) {
hsm->b_bus_req = 0;
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
PNW_STOP_HOST(pnw);
hsm->a_bus_suspend = 0;
/* Clear HNP polling flag */
if (iotg->otg.otg->gadget)
iotg->otg.otg->gadget->host_request_flag = 0;
if (iotg->start_peripheral)
iotg->start_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver not loaded.\n");
/* allow D3 and D0i3 in A_WAIT_BCON */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_B_PERIPHERAL;
} else if (hsm->id == ID_ACA_C) {
/* Make sure current limit updated */
penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
}
break;
case OTG_STATE_A_IDLE:
if (hsm->id == ID_B || hsm->id == ID_ACA_B) {
pnw->iotg.otg.otg->default_a = 0;
hsm->b_bus_req = 0;
if (hsm->id == ID_ACA_B)
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
hsm->b_bus_req = 0;
set_client_mode();
iotg->otg.state = OTG_STATE_B_IDLE;
penwell_update_transceiver();
/* Decrement the device usage counter */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
} else if (hsm->id == ID_ACA_A) {
penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
if (hsm->power_up)
hsm->power_up = 0;
if (hsm->adp_change)
hsm->adp_change = 0;
if (hsm->a_srp_det)
hsm->a_srp_det = 0;
hsm->b_conn = 0;
hsm->hnp_poll_enable = 0;
if (iotg->start_host)
iotg->start_host(iotg);
else {
dev_dbg(pnw->dev, "host driver not loaded.\n");
break;
}
/* allow D3 and D0i3 in A_WAIT_BCON */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_WAIT_BCON;
} else if (!hsm->a_bus_drop && (hsm->power_up || hsm->a_bus_req
|| hsm->a_srp_det || hsm->adp_change)) {
/* power up / adp changes / srp detection should be
* cleared at once after handled. */
if (hsm->power_up)
hsm->power_up = 0;
if (hsm->adp_change)
hsm->adp_change = 0;
if (hsm->a_srp_det) {
hsm->a_srp_det = 0;
/* wait SRP done, then enable VBUS */
usleep_range(10000, 11000);
}
otg_set_vbus(iotg->otg.otg, true);
penwell_otg_add_timer(TA_WAIT_VRISE_TMR);
iotg->otg.state = OTG_STATE_A_WAIT_VRISE;
penwell_update_transceiver();
} else if (hsm->b_sess_end || hsm->a_sess_vld ||
hsm->a_srp_det || !hsm->b_sess_vld) {
hsm->a_srp_det = 0;
dev_dbg(pnw->dev, "reconfig...\n");
}
break;
case OTG_STATE_A_WAIT_VRISE:
if (hsm->a_bus_drop ||
hsm->id == ID_B || hsm->id == ID_ACA_B) {
/* Move to A_WAIT_VFALL, over current/user request */
/* Delete current timer */
penwell_otg_del_timer(TA_WAIT_VRISE_TMR);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
penwell_otg_add_timer(TA_WAIT_VFALL_TMR);
iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
} else if (hsm->a_vbus_vld || hsm->a_wait_vrise_tmout
|| hsm->id == ID_ACA_A) {
/* Move to A_WAIT_BCON state, a vbus vld */
/* Delete current timer and clear flags */
penwell_otg_del_timer(TA_WAIT_VRISE_TMR);
if (!hsm->a_vbus_vld) {
dev_warn(pnw->dev, "vbus can't rise to vbus vld, overcurrent!\n");
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
penwell_otg_add_timer(TA_WAIT_VFALL_TMR);
iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
break;
}
if (hsm->id == ID_ACA_A) {
otg_set_vbus(iotg->otg.otg, false);
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
}
hsm->a_bus_req = 1;
hsm->b_conn = 0;
hsm->hnp_poll_enable = 0;
penwell_otg_eye_diagram_optimize();
if (iotg->start_host) {
dev_dbg(pnw->dev, "host_ops registered!\n");
iotg->start_host(iotg);
} else {
dev_dbg(pnw->dev, "host driver not loaded.\n");
break;
}
penwell_otg_add_timer(TA_WAIT_BCON_TMR);
/* allow D3 and D0i3 in A_WAIT_BCON */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
/* at least give some time to USB HOST to enumerate
* devices before trying to suspend the system*/
wake_lock_timeout(&pnw->wake_lock, 5 * HZ);
iotg->otg.state = OTG_STATE_A_WAIT_BCON;
}
break;
case OTG_STATE_A_WAIT_BCON:
if (hsm->id == ID_B || hsm->id == ID_ACA_B || hsm->a_bus_drop ||
hsm->a_wait_bcon_tmout) {
/* Move to A_WAIT_VFALL state, user request */
/* Delete current timer and clear flags for B-Device */
penwell_otg_del_timer(TA_WAIT_BCON_TMR);
hsm->b_bus_req = 0;
PNW_STOP_HOST(pnw);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
penwell_otg_add_timer(TA_WAIT_VFALL_TMR);
/* disallow D3 or D0i3 */
pm_runtime_get(pnw->dev);
wake_lock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
} else if (!hsm->a_vbus_vld) {
/* Move to A_VBUS_ERR state, over-current detected */
/* CTP SW Workaround, add 300ms debouce on VBUS drop */
if (is_clovertrail(pdev)) {
msleep(300);
if (hsm->a_vbus_vld)
break;
}
/* Notify user space for vbus invalid event */
penwell_otg_notify_warning(USB_WARNING_VBUS_INVALID);
/* Delete current timer and disable host function */
penwell_otg_del_timer(TA_WAIT_BCON_TMR);
PNW_STOP_HOST(pnw);
/* Turn off VBUS and enter PHY low power mode */
otg_set_vbus(iotg->otg.otg, false);
/* disallow D3 or D0i3 */
pm_runtime_get(pnw->dev);
wake_lock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_VBUS_ERR;
} else if (hsm->b_conn) {
/* Move to A_HOST state, device connected */
/* Delete current timer and disable host function */
penwell_otg_del_timer(TA_WAIT_BCON_TMR);
/* Start HNP polling */
if (iotg->start_hnp_poll)
iotg->start_hnp_poll(iotg);
if (!hsm->a_bus_req)
hsm->a_bus_req = 1;
if (hsm->test_device)
penwell_otg_add_timer(TTST_MAINT_TMR);
iotg->otg.state = OTG_STATE_A_HOST;
} else if (hsm->id == ID_ACA_A) {
penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
}
break;
case OTG_STATE_A_HOST:
if (hsm->id == ID_B || hsm->id == ID_ACA_B || hsm->a_bus_drop) {
/* Move to A_WAIT_VFALL state, timeout/user request */
/* Delete current timer and clear flags */
if (hsm->test_device) {
hsm->test_device = 0;
penwell_otg_del_timer(TTST_MAINT_TMR);
}
if (hsm->id == ID_ACA_B)
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
penwell_otg_phy_low_power(0);
PNW_STOP_HOST(pnw);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
penwell_otg_add_timer(TA_WAIT_VFALL_TMR);
/* disallow D3 or D0i3 */
pm_runtime_get(pnw->dev);
wake_lock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
} else if (hsm->test_device && hsm->tst_maint_tmout) {
hsm->test_device = 0;
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
penwell_otg_phy_low_power(0);
PNW_STOP_HOST(pnw);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
/* Clear states and wait for SRP */
hsm->a_srp_det = 0;
hsm->a_bus_req = 0;
/* disallow D3 or D0i3 */
pm_runtime_get(pnw->dev);
wake_lock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_IDLE;
} else if (!hsm->a_vbus_vld) {
/* Move to A_VBUS_ERR state */
/* CTP SW Workaround, add 300ms debouce on VBUS drop */
if (is_clovertrail(pdev)) {
msleep(300);
if (hsm->a_vbus_vld)
break;
}
/* Notify user space for vbus invalid event */
penwell_otg_notify_warning(USB_WARNING_VBUS_INVALID);
/* Delete current timer and clear flags */
if (hsm->test_device) {
hsm->test_device = 0;
penwell_otg_del_timer(TTST_MAINT_TMR);
}
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
PNW_STOP_HOST(pnw);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
/* disallow D3 or D0i3 */
pm_runtime_get(pnw->dev);
wake_lock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_VBUS_ERR;
} else if (!hsm->a_bus_req &&
iotg->otg.otg->host->b_hnp_enable) {
/* Move to A_SUSPEND state */
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
/* According to Spec 7.1.5 */
penwell_otg_add_timer(TA_AIDL_BDIS_TMR);
/* Set HABA to enable hardware assistance to
* signal A-connect after receiver B-disconnect
* Hardware will then set client mode and
* enable URE, SLE and PCE after the assistance
* otg_dummy_irq is used to clean these ints
* when client driver is not resumed.
*/
if (request_irq(pdev->irq, otg_dummy_irq,
IRQF_SHARED, driver_name,
iotg->base) != 0) {
dev_dbg(pnw->dev,
"request interrupt %d failed\n",
pdev->irq);
}
penwell_otg_HABA(1);
penwell_otg_loc_sof(0);
penwell_otg_phy_low_power(0);
/* disallow D3 or D0i3 */
pm_runtime_get(pnw->dev);
wake_lock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_SUSPEND;
} else if (!hsm->b_conn && hsm->test_device
&& hsm->otg_vbus_off) {
/* If it is a test device with otg_vbus_off bit set,
* turn off VBUS on disconnect event and stay for
* TTST_NOADP without ADP */
penwell_otg_del_timer(TTST_MAINT_TMR);
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
penwell_otg_phy_low_power(0);
PNW_STOP_HOST(pnw);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
penwell_otg_add_timer(TTST_NOADP_TMR);
/* disallow D3 or D0i3 */
pm_runtime_get(pnw->dev);
wake_lock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
} else if (!hsm->b_conn) {
/* Delete current timer and clear flags */
if (hsm->test_device) {
hsm->test_device = 0;
penwell_otg_del_timer(TTST_MAINT_TMR);
}
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
/* add kernel timer */
iotg->otg.state = OTG_STATE_A_WAIT_BCON;
} else if (hsm->id == ID_ACA_A) {
penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
} else if (hsm->id == ID_A) {
/* Turn on VBUS */
otg_set_vbus(iotg->otg.otg, true);
}
break;
case OTG_STATE_A_SUSPEND:
if (hsm->id == ID_B || hsm->id == ID_ACA_B ||
hsm->a_bus_drop || hsm->a_aidl_bdis_tmout) {
/* Move to A_WAIT_VFALL state, timeout/user request */
penwell_otg_HABA(0);
free_irq(pdev->irq, iotg->base);
/* Delete current timer and clear HW assist */
if (hsm->a_aidl_bdis_tmout)
hsm->a_aidl_bdis_tmout = 0;
penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
if (hsm->id == ID_ACA_B)
penwell_otg_update_chrg_cap(CHRG_ACA,
CHRG_CURR_ACA);
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
PNW_STOP_HOST(pnw);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
penwell_otg_add_timer(TA_WAIT_VFALL_TMR);
iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
} else if (!hsm->a_vbus_vld) {
/* Move to A_VBUS_ERR state, Over-current */
penwell_otg_HABA(0);
free_irq(pdev->irq, iotg->base);
/* Delete current timer and clear flags */
penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
PNW_STOP_HOST(pnw);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
iotg->otg.state = OTG_STATE_A_VBUS_ERR;
} else if (!hsm->b_conn &&
!pnw->iotg.otg.otg->host->b_hnp_enable) {
/* Move to A_WAIT_BCON */
/* delete current timer */
penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
/* add kernel timer */
penwell_otg_add_timer(TA_WAIT_BCON_TMR);
/* allow D3 and D0i3 in A_WAIT_BCON */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_WAIT_BCON;
} else if (!hsm->b_conn &&
pnw->iotg.otg.otg->host->b_hnp_enable) {
/* Move to A_PERIPHERAL state, HNP */
penwell_otg_HABA(0);
free_irq(pdev->irq, iotg->base);
/* Delete current timer and clear flags */
penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
penwell_otg_phy_low_power(0);
PNW_STOP_HOST(pnw);
penwell_otg_phy_low_power(0);
hsm->b_bus_suspend = 0;
/* Clear HNP polling flag */
if (iotg->otg.otg->gadget)
iotg->otg.otg->gadget->host_request_flag = 0;
penwell_otg_phy_low_power(0);
if (iotg->start_peripheral)
iotg->start_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver not loaded.\n");
penwell_otg_add_timer(TA_BIDL_ADIS_TMR);
iotg->otg.state = OTG_STATE_A_PERIPHERAL;
} else if (hsm->a_bus_req) {
/* Move to A_HOST state, user request */
penwell_otg_HABA(0);
free_irq(pdev->irq, iotg->base);
/* Delete current timer and clear flags */
penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
penwell_otg_loc_sof(1);
/* Start HNP polling */
if (iotg->start_hnp_poll)
iotg->start_hnp_poll(iotg);
/* allow D3 and D0i3 in A_HOST */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_HOST;
} else if (hsm->id == ID_ACA_A) {
penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
}
break;
case OTG_STATE_A_PERIPHERAL:
if (hsm->id == ID_B || hsm->a_bus_drop) {
/* Move to A_WAIT_VFALL state */
/* Delete current timer and clear flags */
penwell_otg_del_timer(TA_BIDL_ADIS_TMR);
if (iotg->stop_peripheral)
iotg->stop_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver has been removed.\n");
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
set_host_mode();
penwell_otg_add_timer(TA_WAIT_VFALL_TMR);
iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
} else if (!hsm->a_vbus_vld) {
/* Move to A_VBUS_ERR state, over-current detected */
/* Delete current timer and disable client function */
penwell_otg_del_timer(TA_BIDL_ADIS_TMR);
if (iotg->stop_peripheral)
iotg->stop_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver has been removed.\n");
/* Turn off the VBUS and enter PHY low power mode */
otg_set_vbus(iotg->otg.otg, false);
iotg->otg.state = OTG_STATE_A_VBUS_ERR;
} else if (hsm->a_bidl_adis_tmout) {
/* Move to A_WAIT_BCON state */
hsm->a_bidl_adis_tmr = 0;
msleep(10);
penwell_otg_phy_low_power(0);
/* Disable client function and switch to host mode */
if (iotg->stop_peripheral)
iotg->stop_peripheral(iotg);
else
dev_dbg(pnw->dev,
"client driver has been removed.\n");
hsm->hnp_poll_enable = 0;
hsm->b_conn = 0;
penwell_otg_phy_low_power(0);
if (iotg->start_host)
iotg->start_host(iotg);
else
dev_dbg(pnw->dev,
"host driver not loaded.\n");
penwell_otg_add_timer(TA_WAIT_BCON_TMR);
/* allow D3 and D0i3 in A_WAIT_BCON */
pm_runtime_put(pnw->dev);
wake_unlock(&pnw->wake_lock);
iotg->otg.state = OTG_STATE_A_WAIT_BCON;
} else if (hsm->id == ID_A && hsm->b_bus_suspend) {
if (!timer_pending(&pnw->hsm_timer))
penwell_otg_add_timer(TA_BIDL_ADIS_TMR);
} else if (hsm->id == ID_A && !hsm->b_bus_suspend) {
penwell_otg_del_timer(TA_BIDL_ADIS_TMR);
} else if (hsm->id == ID_ACA_A) {
penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA);
/* Turn off VBUS */
otg_set_vbus(iotg->otg.otg, false);
}
break;
case OTG_STATE_A_VBUS_ERR:
if (hsm->id == ID_B || hsm->id == ID_ACA_B ||
hsm->id == ID_ACA_A || hsm->a_bus_drop ||
hsm->a_clr_err) {
if (hsm->a_clr_err)
hsm->a_clr_err = 0;
penwell_otg_add_timer(TA_WAIT_VFALL_TMR);
iotg->otg.state = OTG_STATE_A_WAIT_VFALL;
}
break;
case OTG_STATE_A_WAIT_VFALL:
if (hsm->a_wait_vfall_tmout) {
hsm->a_srp_det = 0;
hsm->a_wait_vfall_tmout = 0;
/* Move to A_IDLE state, vbus falls */
/* Always set a_bus_req to 1, in case no ADP */
hsm->a_bus_req = 1;
iotg->otg.state = OTG_STATE_A_IDLE;
penwell_update_transceiver();
} else if (hsm->test_device && hsm->otg_vbus_off
&& hsm->tst_noadp_tmout) {
/* After noadp timeout, switch back to normal mode */
hsm->test_device = 0;
hsm->otg_vbus_off = 0;
hsm->tst_noadp_tmout = 0;
hsm->a_bus_req = 1;
iotg->otg.state = OTG_STATE_A_IDLE;
penwell_update_transceiver();
}
break;
default:
break;
;
}
pm_runtime_put_sync(pnw->dev);
dev_dbg(pnw->dev,
"new state = %s\n", state_string(iotg->otg.state));
}
static ssize_t
show_registers(struct device *_dev, struct device_attribute *attr, char *buf)
{
struct penwell_otg *pnw = the_transceiver;
char *next;
unsigned size;
unsigned t;
next = buf;
size = PAGE_SIZE;
pm_runtime_get_sync(pnw->dev);
t = scnprintf(next, size,
"\n"
"USBCMD = 0x%08x\n"
"USBSTS = 0x%08x\n"
"USBINTR = 0x%08x\n"
"ASYNCLISTADDR = 0x%08x\n"
"PORTSC1 = 0x%08x\n"
"HOSTPC1 = 0x%08x\n"
"OTGSC = 0x%08x\n"
"USBMODE = 0x%08x\n",
readl(pnw->iotg.base + 0x30),
readl(pnw->iotg.base + 0x34),
readl(pnw->iotg.base + 0x38),
readl(pnw->iotg.base + 0x48),
readl(pnw->iotg.base + 0x74),
readl(pnw->iotg.base + 0xb4),
readl(pnw->iotg.base + 0xf4),
readl(pnw->iotg.base + 0xf8)
);
pm_runtime_put_sync(pnw->dev);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static DEVICE_ATTR(registers, S_IRUGO, show_registers, NULL);
static ssize_t
show_hsm(struct device *_dev, struct device_attribute *attr, char *buf)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
char *next;
unsigned size, t;
next = buf;
size = PAGE_SIZE;
if (iotg->otg.otg->host)
iotg->hsm.a_set_b_hnp_en = iotg->otg.otg->host->b_hnp_enable;
if (iotg->otg.otg->gadget)
iotg->hsm.b_hnp_enable = iotg->otg.otg->gadget->b_hnp_enable;
t = scnprintf(next, size,
"\n"
"current state = %s\n"
"a_bus_resume = \t%d\n"
"a_bus_suspend = \t%d\n"
"a_conn = \t%d\n"
"a_sess_vld = \t%d\n"
"a_srp_det = \t%d\n"
"a_vbus_vld = \t%d\n"
"b_bus_suspend = \t%d\n"
"b_conn = \t%d\n"
"b_se0_srp = \t%d\n"
"b_ssend_srp = \t%d\n"
"b_sess_end = \t%d\n"
"b_sess_vld = \t%d\n"
"id = \t%d\n"
"power_up = \t%d\n"
"adp_change = \t%d\n"
"test_device = \t%d\n"
"a_set_b_hnp_en = \t%d\n"
"b_srp_done = \t%d\n"
"b_hnp_enable = \t%d\n"
"hnp_poll_enable = \t%d\n"
"a_wait_vrise_tmout = \t%d\n"
"a_wait_bcon_tmout = \t%d\n"
"a_aidl_bdis_tmout = \t%d\n"
"a_bidl_adis_tmout = \t%d\n"
"a_bidl_adis_tmr = \t%d\n"
"a_wait_vfall_tmout = \t%d\n"
"b_ase0_brst_tmout = \t%d\n"
"b_srp_fail_tmout = \t%d\n"
"b_srp_fail_tmr = \t%d\n"
"b_adp_sense_tmout = \t%d\n"
"tst_maint_tmout = \t%d\n"
"tst_noadp_tmout = \t%d\n"
"a_bus_drop = \t%d\n"
"a_bus_req = \t%d\n"
"a_clr_err = \t%d\n"
"b_bus_req = \t%d\n"
"ulpi_error = \t%d\n"
"ulpi_polling = \t%d\n",
state_string(iotg->otg.state),
iotg->hsm.a_bus_resume,
iotg->hsm.a_bus_suspend,
iotg->hsm.a_conn,
iotg->hsm.a_sess_vld,
iotg->hsm.a_srp_det,
iotg->hsm.a_vbus_vld,
iotg->hsm.b_bus_suspend,
iotg->hsm.b_conn,
iotg->hsm.b_se0_srp,
iotg->hsm.b_ssend_srp,
iotg->hsm.b_sess_end,
iotg->hsm.b_sess_vld,
iotg->hsm.id,
iotg->hsm.power_up,
iotg->hsm.adp_change,
iotg->hsm.test_device,
iotg->hsm.a_set_b_hnp_en,
iotg->hsm.b_srp_done,
iotg->hsm.b_hnp_enable,
iotg->hsm.hnp_poll_enable,
iotg->hsm.a_wait_vrise_tmout,
iotg->hsm.a_wait_bcon_tmout,
iotg->hsm.a_aidl_bdis_tmout,
iotg->hsm.a_bidl_adis_tmout,
iotg->hsm.a_bidl_adis_tmr,
iotg->hsm.a_wait_vfall_tmout,
iotg->hsm.b_ase0_brst_tmout,
iotg->hsm.b_srp_fail_tmout,
iotg->hsm.b_srp_fail_tmr,
iotg->hsm.b_adp_sense_tmout,
iotg->hsm.tst_maint_tmout,
iotg->hsm.tst_noadp_tmout,
iotg->hsm.a_bus_drop,
iotg->hsm.a_bus_req,
iotg->hsm.a_clr_err,
iotg->hsm.b_bus_req,
iotg->hsm.ulpi_error,
iotg->hsm.ulpi_polling
);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static DEVICE_ATTR(hsm, S_IRUGO, show_hsm, NULL);
static ssize_t
show_chargers(struct device *_dev, struct device_attribute *attr, char *buf)
{
struct penwell_otg *pnw = the_transceiver;
char *next;
unsigned size, t;
enum usb_charger_type type;
unsigned int ma;
unsigned long flags;
struct pci_dev *pdev;
struct power_supply_cable_props psc_cap;
pdev = to_pci_dev(pnw->dev);
next = buf;
size = PAGE_SIZE;
if (!is_clovertrail(pdev)) {
spin_lock_irqsave(&pnw->charger_lock, flags);
type = pnw->charging_cap.chrg_type;
ma = pnw->charging_cap.ma;
spin_unlock_irqrestore(&pnw->charger_lock, flags);
t = scnprintf(next, size,
"USB Battery Charging Capability\n"
"\tUSB Charger Type: %s\n"
"\tMax Charging Current: %u\n",
charger_string(type),
ma
);
} else {
spin_lock_irqsave(&pnw->charger_lock, flags);
psc_cap = pnw->psc_cap;
spin_unlock_irqrestore(&pnw->charger_lock, flags);
t = scnprintf(next, size,
"USB Battery Charging Capability(CLV)\n"
"\tUSB Charger Type: %s\n"
"\tMax Charging Current: %u\n",
psc_string(psc_cap.chrg_type),
psc_cap.ma
);
}
size -= t;
next += t;
return PAGE_SIZE - size;
}
static DEVICE_ATTR(chargers, S_IRUGO, show_chargers, NULL);
static ssize_t
get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
{
struct penwell_otg *pnw = the_transceiver;
char *next;
unsigned size, t;
next = buf;
size = PAGE_SIZE;
t = scnprintf(next, size, "%d", pnw->iotg.hsm.a_bus_req);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static ssize_t
set_a_bus_req(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
if (!iotg->otg.otg->default_a)
return -1;
if (count > 2)
return -1;
if (buf[0] == '0') {
iotg->hsm.a_bus_req = 0;
dev_dbg(pnw->dev, "a_bus_req = 0\n");
} else if (buf[0] == '1') {
/* If a_bus_drop is TRUE, a_bus_req can't be set */
if (iotg->hsm.a_bus_drop)
return -1;
iotg->hsm.a_bus_req = 1;
dev_dbg(pnw->dev, "a_bus_req = 1\n");
if (iotg->otg.state == OTG_STATE_A_PERIPHERAL) {
dev_warn(pnw->dev, "Role switch will be "
"performed soon, if connected OTG device "
"supports role switch request.\n");
dev_warn(pnw->dev, "It may cause data"
"corruption during data transfer\n");
}
}
penwell_update_transceiver();
return count;
}
static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR | S_IWGRP,
get_a_bus_req, set_a_bus_req);
static ssize_t
get_a_bus_drop(struct device *dev, struct device_attribute *attr, char *buf)
{
struct penwell_otg *pnw = the_transceiver;
char *next;
unsigned size;
unsigned t;
next = buf;
size = PAGE_SIZE;
t = scnprintf(next, size, "%d", pnw->iotg.hsm.a_bus_drop);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static ssize_t
set_a_bus_drop(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
if (!iotg->otg.otg->default_a)
return -1;
if (count > 2)
return -1;
if (buf[0] == '0') {
iotg->hsm.a_bus_drop = 0;
dev_dbg(pnw->dev, "a_bus_drop = 0\n");
} else if (buf[0] == '1') {
iotg->hsm.a_bus_drop = 1;
iotg->hsm.a_bus_req = 0;
dev_dbg(pnw->dev, "a_bus_drop = 1, so a_bus_req = 0\n");
}
penwell_update_transceiver();
return count;
}
static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR | S_IWGRP,
get_a_bus_drop, set_a_bus_drop);
static ssize_t
get_b_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
{
struct penwell_otg *pnw = the_transceiver;
char *next;
unsigned size;
unsigned t;
next = buf;
size = PAGE_SIZE;
t = scnprintf(next, size, "%d", pnw->iotg.hsm.b_bus_req);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static ssize_t
set_b_bus_req(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
if (iotg->otg.otg->default_a)
return -1;
if (count > 2)
return -1;
if (buf[0] == '0') {
iotg->hsm.b_bus_req = 0;
dev_dbg(pnw->dev, "b_bus_req = 0\n");
if (iotg->otg.otg->gadget)
iotg->otg.otg->gadget->host_request_flag = 0;
} else if (buf[0] == '1') {
iotg->hsm.b_bus_req = 1;
dev_dbg(pnw->dev, "b_bus_req = 1\n");
if (iotg->otg.state == OTG_STATE_B_PERIPHERAL) {
if (iotg->otg.otg->gadget)
iotg->otg.otg->gadget->host_request_flag = 1;
dev_warn(pnw->dev, "Role switch will be "
"performed soon, if connected OTG device "
"supports role switch request.\n");
dev_warn(pnw->dev, "It may cause data "
"corruption during data transfer\n");
}
}
penwell_update_transceiver();
return count;
}
static DEVICE_ATTR(b_bus_req, S_IRUGO | S_IWUSR | S_IWGRP,
get_b_bus_req, set_b_bus_req);
static ssize_t
set_a_clr_err(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
if (!iotg->otg.otg->default_a)
return -1;
if (iotg->otg.state != OTG_STATE_A_VBUS_ERR)
return -1;
if (count > 2)
return -1;
if (buf[0] == '1') {
iotg->hsm.a_clr_err = 1;
dev_dbg(pnw->dev, "a_clr_err = 1\n");
}
penwell_update_transceiver();
return count;
}
static DEVICE_ATTR(a_clr_err, S_IRUGO | S_IWUSR | S_IWGRP, NULL, set_a_clr_err);
static ssize_t
set_ulpi_err(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
dev_dbg(pnw->dev, "trigger ulpi error manually\n");
iotg->hsm.ulpi_error = 1;
penwell_update_transceiver();
return count;
}
static DEVICE_ATTR(ulpi_err, S_IRUGO | S_IWUSR | S_IWGRP, NULL, set_ulpi_err);
static struct attribute *inputs_attrs[] = {
&dev_attr_a_bus_req.attr,
&dev_attr_a_bus_drop.attr,
&dev_attr_b_bus_req.attr,
&dev_attr_a_clr_err.attr,
&dev_attr_ulpi_err.attr,
NULL,
};
static struct attribute_group debug_dev_attr_group = {
.name = "inputs",
.attrs = inputs_attrs,
};
static int penwell_otg_aca_enable(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval = 0;
struct pci_dev *pdev;
dev_dbg(pnw->dev, "%s --->\n", __func__);
pdev = to_pci_dev(pnw->dev);
if (!is_clovertrail(pdev)) {
penwell_otg_msic_spi_access(true);
retval = intel_scu_ipc_update_register(SPI_TI_VS4,
TI_ACA_DET_EN, TI_ACA_DET_EN);
if (retval)
goto done;
retval = intel_scu_ipc_update_register(SPI_TI_VS5,
TI_ID_FLOAT_EN | TI_ID_RES_EN,
TI_ID_FLOAT_EN | TI_ID_RES_EN);
if (retval)
goto done;
} else {
retval = penwell_otg_ulpi_write(iotg, ULPI_VS4SET,
ACADET);
if (retval)
goto done;
retval = penwell_otg_ulpi_write(iotg, ULPI_VS5SET,
IDFLOAT_EN | IDRES_EN);
}
done:
if (!is_clovertrail(pdev))
penwell_otg_msic_spi_access(false);
if (retval)
dev_warn(pnw->dev, "Failed to enable ACA device detection\n");
dev_dbg(pnw->dev, "%s <---\n", __func__);
return retval;
}
static int penwell_otg_aca_disable(void)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int retval = 0;
dev_dbg(pnw->dev, "%s --->\n", __func__);
retval = penwell_otg_ulpi_write(iotg, ULPI_VS5CLR,
IDFLOAT_EN | IDRES_EN);
if (retval)
goto done;
retval = penwell_otg_ulpi_write(iotg, ULPI_VS4CLR,
ACADET);
done:
if (retval)
dev_warn(pnw->dev, "Failed to disable ACA device detection\n");
dev_dbg(pnw->dev, "%s <---\n", __func__);
return retval;
}
static void penwell_spi_reset_phy(void)
{
struct penwell_otg *pnw = the_transceiver;
dev_dbg(pnw->dev, "Reset Phy over SPI\n");
penwell_otg_msic_spi_access(true);
penwell_otg_msic_write(MSIC_FUNCTRLSET, PHYRESET);
penwell_otg_msic_spi_access(false);
dev_dbg(pnw->dev, "Reset Phy over SPI Done\n");
}
static int penwell_otg_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
unsigned long resource, len;
void __iomem *base = NULL;
int retval;
u32 val32;
struct penwell_otg *pnw;
char qname[] = "penwell_otg_queue";
char chrg_qname[] = "penwell_otg_chrg_queue";
retval = 0;
dev_info(&pdev->dev, "Intel OTG2.0 controller is detected.\n");
dev_info(&pdev->dev, "Driver version: " DRIVER_VERSION "\n");
if (pci_enable_device(pdev) < 0) {
retval = -ENODEV;
goto done;
}
pnw = kzalloc(sizeof(*pnw), GFP_KERNEL);
if (pnw == NULL) {
retval = -ENOMEM;
goto done;
}
the_transceiver = pnw;
/* control register: BAR 0 */
resource = pci_resource_start(pdev, 0);
len = pci_resource_len(pdev, 0);
if (!request_mem_region(resource, len, driver_name)) {
retval = -EBUSY;
goto err;
}
pnw->region = 1;
base = ioremap_nocache(resource, len);
if (base == NULL) {
retval = -EFAULT;
goto err;
}
pnw->iotg.base = base;
if (!request_mem_region(USBCFG_ADDR, USBCFG_LEN, driver_name)) {
retval = -EBUSY;
goto err;
}
pnw->cfg_region = 1;
if (!pdev->irq) {
dev_dbg(&pdev->dev, "No IRQ.\n");
retval = -ENODEV;
goto err;
}
pnw->qwork = create_singlethread_workqueue(qname);
if (!pnw->qwork) {
dev_dbg(&pdev->dev, "cannot create workqueue %s\n", qname);
retval = -ENOMEM;
goto err;
}
pnw->chrg_qwork = create_singlethread_workqueue(chrg_qname);
if (!pnw->chrg_qwork) {
dev_dbg(&pdev->dev, "cannot create workqueue %s\n", chrg_qname);
retval = -ENOMEM;
goto err;
}
INIT_LIST_HEAD(&pnw->chrg_evt_queue);
INIT_WORK(&pnw->work, penwell_otg_work);
INIT_WORK(&pnw->psc_notify, penwell_otg_psc_notify_work);
INIT_WORK(&pnw->hnp_poll_work, penwell_otg_hnp_poll_work);
INIT_WORK(&pnw->uevent_work, penwell_otg_uevent_work);
INIT_DELAYED_WORK(&pnw->ulpi_poll_work, penwell_otg_ulpi_poll_work);
INIT_DELAYED_WORK(&pnw->ulpi_check_work, penwell_otg_ulpi_check_work);
INIT_DELAYED_WORK(&pnw->sdp_check_work, penwell_otg_sdp_check_work);
/* OTG common part */
pnw->dev = &pdev->dev;
pnw->iotg.otg.dev = &pdev->dev;
pnw->iotg.otg.label = driver_name;
pnw->iotg.otg.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL);
if (!pnw->iotg.otg.otg) {
retval = -ENOMEM;
goto err;
}
pnw->iotg.otg.otg->set_host = penwell_otg_set_host;
pnw->iotg.otg.otg->set_peripheral = penwell_otg_set_peripheral;
pnw->iotg.otg.set_power = penwell_otg_set_power;
pnw->iotg.otg.otg->set_vbus = penwell_otg_set_vbus;
pnw->iotg.otg.otg->start_srp = penwell_otg_start_srp;
pnw->iotg.otg.get_chrg_status = penwell_otg_get_chrg_status;
pnw->iotg.set_adp_probe = NULL;
pnw->iotg.set_adp_sense = NULL;
pnw->iotg.start_hnp_poll = NULL;
pnw->iotg.stop_hnp_poll = NULL;
pnw->iotg.otg.state = OTG_STATE_UNDEFINED;
pnw->rt_resuming = 0;
pnw->rt_quiesce = 0;
pnw->queue_stop = 0;
pnw->phy_power_state = 1;
if (usb_add_phy(&pnw->iotg.otg, USB_PHY_TYPE_USB2)) {
dev_err(pnw->dev, "can't set transceiver\n");
retval = -EBUSY;
goto err;
}
pnw->iotg.ulpi_ops.read = penwell_otg_ulpi_read;
pnw->iotg.ulpi_ops.write = penwell_otg_ulpi_write;
spin_lock_init(&pnw->iotg.hnp_poll_lock);
spin_lock_init(&pnw->lock);
wake_lock_init(&pnw->wake_lock, WAKE_LOCK_SUSPEND, "pnw_wake_lock");
init_timer(&pnw->hsm_timer);
init_timer(&pnw->bus_mon_timer);
init_timer(&pnw->hnp_poll_timer);
init_completion(&pnw->adp.adp_comp);
/* Battery Charging part */
spin_lock_init(&pnw->charger_lock);
spin_lock_init(&pnw->cap_lock);
pnw->charging_cap.ma = CHRG_CURR_DISCONN;
pnw->charging_cap.chrg_type = CHRG_UNKNOWN;
pnw->charging_cap.current_event = USBCHRG_EVENT_DISCONN;
pnw->psc_cap.ma = CHRG_CURR_DISCONN;
pnw->psc_cap.chrg_type = POWER_SUPPLY_CHARGER_TYPE_NONE;
pnw->psc_cap.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
ATOMIC_INIT_NOTIFIER_HEAD(&pnw->iotg.iotg_notifier);
/* For generic otg notifications */
ATOMIC_INIT_NOTIFIER_HEAD(&pnw->iotg.otg.notifier);
pnw->iotg_notifier.notifier_call = penwell_otg_iotg_notify;
if (intel_mid_otg_register_notifier(&pnw->iotg, &pnw->iotg_notifier)) {
dev_dbg(pnw->dev, "Failed to register notifier\n");
retval = -EBUSY;
goto err;
}
if (register_pm_notifier(&pnw_sleep_pm_notifier)) {
dev_dbg(pnw->dev, "Fail to register PM notifier\n");
retval = -EBUSY;
goto err;
}
/* listen usb core events */
usb_register_notify(&otg_nb);
pnw->otg_pdata = pdev->dev.platform_data;
if (pnw->otg_pdata == NULL) {
dev_err(pnw->dev, "Failed to get OTG platform data.\n");
retval = -ENODEV;
goto err;
}
if (pnw->otg_pdata->hnp_poll_support) {
pnw->iotg.start_hnp_poll = penwell_otg_start_hnp_poll;
pnw->iotg.stop_hnp_poll = penwell_otg_stop_hnp_poll;
}
/* FIXME: Reads Charging compliance bit from scu mip.
* This snippet needs to be cleaned up after EM inteface is ready
*/
if (is_clovertrail(pdev)) {
u8 smip_data = 0;
if (!intel_scu_ipc_read_mip(&smip_data, 1, 0x2e7, 1)) {
pnw->otg_pdata->charging_compliance =
!(smip_data & 0x40);
dev_info(pnw->dev, "charging_compliance = %d\n",
pnw->otg_pdata->charging_compliance);
} else
dev_err(pnw->dev, "scu mip read error\n");
}
if (!is_clovertrail(pdev)) {
if (pnw->otg_pdata->gpio_vbus) {
retval = gpio_request(pnw->otg_pdata->gpio_vbus,
"usb_otg_phy_reset");
if (retval < 0) {
dev_err(pnw->dev, "request gpio(%d) failed\n",
pnw->otg_pdata->gpio_vbus);
retval = -ENODEV;
goto err;
}
}
}
if (is_clovertrail(pdev)) {
/* Set up gpio for Clovertrail */
retval = gpio_request(pnw->otg_pdata->gpio_reset,
"usb_otg_phy_reset");
if (retval < 0) {
dev_err(pnw->dev, "request phy reset gpio(%d) failed\n",
pnw->otg_pdata->gpio_reset);
retval = -ENODEV;
goto err;
}
retval = gpio_request(pnw->otg_pdata->gpio_cs,
"usb_otg_phy_cs");
if (retval < 0) {
dev_err(pnw->dev, "request phy cs gpio(%d) failed\n",
pnw->otg_pdata->gpio_cs);
gpio_free(pnw->otg_pdata->gpio_reset);
retval = -ENODEV;
goto err;
}
}
penwell_otg_phy_power(1);
penwell_otg_phy_reset();
mutex_init(&pnw->msic_mutex);
pnw->msic = penwell_otg_check_msic();
penwell_otg_phy_low_power(0);
if (!is_clovertrail(pdev)) {
/* Workaround for ULPI lockup issue, need turn off PHY 4ms */
penwell_otg_phy_enable(0);
usleep_range(4000, 4500);
penwell_otg_phy_enable(1);
/* reset phy */
dev_dbg(pnw->dev, "Reset Phy over SPI\n");
penwell_otg_msic_spi_access(true);
penwell_otg_msic_write(MSIC_FUNCTRLSET, PHYRESET);
penwell_otg_msic_spi_access(false);
dev_dbg(pnw->dev, "Reset Phy over SPI Done\n");
}
/* Enable ID pullup immediately after reeable PHY */
val32 = readl(pnw->iotg.base + CI_OTGSC);
writel(val32 | OTGSC_IDPU, pnw->iotg.base + CI_OTGSC);
/* Wait correct value to be synced */
set_host_mode();
usleep_range(2000, 3000);
penwell_otg_phy_low_power(1);
msleep(100);
/* enable ACA device detection for CTP */
if (is_clovertrail(pdev))
penwell_otg_aca_enable();
reset_otg();
init_hsm();
/* we need to set active early or the first irqs will be ignored */
pm_runtime_set_active(&pdev->dev);
if (request_irq(pdev->irq, otg_irq, IRQF_SHARED,
driver_name, pnw) != 0) {
dev_dbg(pnw->dev,
"request interrupt %d failed\n", pdev->irq);
retval = -EBUSY;
goto err;
}
/* enable OTGSC int */
val32 = OTGSC_DPIE | OTGSC_BSEIE | OTGSC_BSVIE |
OTGSC_ASVIE | OTGSC_AVVIE | OTGSC_IDIE | OTGSC_IDPU;
writel(val32, pnw->iotg.base + CI_OTGSC);
retval = device_create_file(&pdev->dev, &dev_attr_registers);
if (retval < 0) {
dev_dbg(pnw->dev,
"Can't register sysfs attribute: %d\n", retval);
goto err;
}
retval = device_create_file(&pdev->dev, &dev_attr_hsm);
if (retval < 0) {
dev_dbg(pnw->dev,
"Can't hsm sysfs attribute: %d\n", retval);
goto err;
}
retval = device_create_file(&pdev->dev, &dev_attr_chargers);
if (retval < 0) {
dev_dbg(pnw->dev,
"Can't chargers sysfs attribute: %d\n", retval);
goto err;
}
retval = sysfs_create_group(&pdev->dev.kobj, &debug_dev_attr_group);
if (retval < 0) {
dev_dbg(pnw->dev,
"Can't register sysfs attr group: %d\n", retval);
goto err;
}
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_allow(&pdev->dev);
penwell_update_transceiver();
return 0;
err:
if (the_transceiver)
penwell_otg_remove(pdev);
done:
return retval;
}
static void penwell_otg_remove(struct pci_dev *pdev)
{
struct penwell_otg *pnw = the_transceiver;
struct otg_bc_event *evt, *tmp;
/* ACA device detection disable */
penwell_otg_aca_disable();
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_forbid(&pdev->dev);
if (pnw->qwork) {
flush_workqueue(pnw->qwork);
destroy_workqueue(pnw->qwork);
}
if (pnw->chrg_qwork) {
flush_workqueue(pnw->chrg_qwork);
destroy_workqueue(pnw->chrg_qwork);
list_for_each_entry_safe(evt, tmp, &pnw->chrg_evt_queue, node) {
list_del(&evt->node);
kfree(evt);
}
}
/* disable OTGSC interrupt as OTGSC doesn't change in reset */
writel(0, pnw->iotg.base + CI_OTGSC);
wake_lock_destroy(&pnw->wake_lock);
if (pdev->irq)
free_irq(pdev->irq, pnw);
if (pnw->cfg_region)
release_mem_region(USBCFG_ADDR, USBCFG_LEN);
if (pnw->iotg.base)
iounmap(pnw->iotg.base);
kfree(pnw->iotg.otg.otg);
if (pnw->region)
release_mem_region(pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
usb_remove_phy(&pnw->iotg.otg);
pci_disable_device(pdev);
sysfs_remove_group(&pdev->dev.kobj, &debug_dev_attr_group);
device_remove_file(&pdev->dev, &dev_attr_chargers);
device_remove_file(&pdev->dev, &dev_attr_hsm);
device_remove_file(&pdev->dev, &dev_attr_registers);
usb_unregister_notify(&otg_nb);
kfree(pnw);
pnw = NULL;
}
void penwell_otg_shutdown(struct pci_dev *pdev)
{
struct penwell_otg *pnw = the_transceiver;
dev_dbg(pnw->dev, "%s --->\n", __func__);
if (!is_clovertrail(pdev)) {
/* Disable MSIC Interrupt Notifications */
penwell_otg_msic_spi_access(true);
penwell_otg_msic_write(MSIC_INT_EN_RISE_CLR, 0x1F);
penwell_otg_msic_write(MSIC_INT_EN_FALL_CLR, 0x1F);
penwell_otg_msic_spi_access(false);
}
dev_dbg(pnw->dev, "%s <---\n", __func__);
}
static int penwell_otg_suspend_noirq(struct device *dev)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int ret = 0;
unsigned long flags;
dev_dbg(pnw->dev, "%s --->\n", __func__);
switch (iotg->otg.state) {
case OTG_STATE_A_VBUS_ERR:
set_host_mode();
iotg->otg.state = OTG_STATE_A_IDLE;
break;
case OTG_STATE_A_WAIT_VFALL:
penwell_otg_del_timer(TA_WAIT_VFALL_TMR);
iotg->otg.state = OTG_STATE_A_IDLE;
case OTG_STATE_A_IDLE:
case OTG_STATE_B_IDLE:
break;
case OTG_STATE_A_WAIT_VRISE:
penwell_otg_del_timer(TA_WAIT_VRISE_TMR);
iotg->hsm.a_srp_det = 0;
/* Turn off VBus */
otg_set_vbus(iotg->otg.otg, false);
iotg->otg.state = OTG_STATE_A_IDLE;
break;
case OTG_STATE_A_WAIT_BCON:
case OTG_STATE_A_HOST:
if (pnw->iotg.suspend_noirq_host)
ret = pnw->iotg.suspend_noirq_host(&pnw->iotg);
goto done;
break;
case OTG_STATE_A_SUSPEND:
penwell_otg_del_timer(TA_AIDL_BDIS_TMR);
penwell_otg_HABA(0);
PNW_STOP_HOST(pnw);
iotg->hsm.a_srp_det = 0;
penwell_otg_phy_vbus_wakeup(false);
/* Turn off VBus */
otg_set_vbus(iotg->otg.otg, false);
iotg->otg.state = OTG_STATE_A_IDLE;
break;
case OTG_STATE_A_PERIPHERAL:
penwell_otg_del_timer(TA_BIDL_ADIS_TMR);
if (pnw->iotg.stop_peripheral)
pnw->iotg.stop_peripheral(&pnw->iotg);
else
dev_dbg(pnw->dev, "client driver has been stopped.\n");
/* Turn off VBus */
otg_set_vbus(iotg->otg.otg, false);
iotg->hsm.a_srp_det = 0;
iotg->otg.state = OTG_STATE_A_IDLE;
break;
case OTG_STATE_B_HOST:
/* Stop HNP polling */
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
PNW_STOP_HOST(pnw);
iotg->hsm.b_bus_req = 0;
iotg->otg.state = OTG_STATE_B_IDLE;
break;
case OTG_STATE_B_PERIPHERAL:
dev_dbg(pnw->dev, "don't suspend, client still alive\n");
ret = -EBUSY;
break;
case OTG_STATE_B_WAIT_ACON:
penwell_otg_del_timer(TB_ASE0_BRST_TMR);
penwell_otg_HAAR(0);
PNW_STOP_HOST(pnw);
iotg->hsm.b_bus_req = 0;
iotg->otg.state = OTG_STATE_B_IDLE;
break;
default:
dev_dbg(pnw->dev, "error state before suspend\n");
break;
}
if (ret) {
spin_lock_irqsave(&pnw->lock, flags);
pnw->queue_stop = 0;
spin_unlock_irqrestore(&pnw->lock, flags);
penwell_update_transceiver();
} else {
penwell_otg_phy_low_power(1);
penwell_otg_vusb330_low_power(1);
#ifdef CONFIG_USB_PENWELL_OTG_PHY_OFF
if (iotg->otg.state == OTG_STATE_B_IDLE) {
penwell_otg_phy_power(0);
pnw->phy_power_state = 0;
}
#endif
}
done:
dev_dbg(pnw->dev, "%s <---\n", __func__);
return ret;
}
static int penwell_otg_suspend(struct device *dev)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int ret = 0;
unsigned long flags;
dev_dbg(pnw->dev, "%s --->\n", __func__);
if (iotg->otg.state == OTG_STATE_B_PERIPHERAL) {
dev_dbg(pnw->dev, "still alive, don't suspend\n");
ret = -EBUSY;
goto done;
}
/* quiesce any work scheduled */
spin_lock_irqsave(&pnw->lock, flags);
pnw->queue_stop = 1;
spin_unlock_irqrestore(&pnw->lock, flags);
flush_workqueue(pnw->qwork);
if (delayed_work_pending(&pnw->ulpi_check_work)) {
spin_lock_irqsave(&pnw->lock, flags);
pnw->queue_stop = 0;
spin_unlock_irqrestore(&pnw->lock, flags);
ret = -EBUSY;
goto done;
} else
flush_delayed_work_sync(&pnw->ulpi_check_work);
switch (iotg->otg.state) {
case OTG_STATE_A_WAIT_BCON:
penwell_otg_del_timer(TA_WAIT_BCON_TMR);
iotg->hsm.a_srp_det = 0;
if (iotg->suspend_host)
ret = iotg->suspend_host(iotg);
break;
case OTG_STATE_A_HOST:
if (iotg->stop_hnp_poll)
iotg->stop_hnp_poll(iotg);
if (iotg->suspend_host)
ret = iotg->suspend_host(iotg);
break;
default:
break;
}
done:
dev_dbg(pnw->dev, "%s <---\n", __func__);
return ret;
}
static void penwell_otg_dump_bogus_wake(void)
{
struct penwell_otg *pnw = the_transceiver;
int addr = 0x2C8, retval;
u8 val;
/* Enable SPI access */
penwell_otg_msic_spi_access(true);
retval = penwell_otg_msic_read(addr, &val);
if (retval) {
dev_err(pnw->dev, "msic read failed\n");
goto out;
}
dev_info(pnw->dev, "0x%03x: 0x%02x", addr, val);
for (addr = 0x340; addr <= 0x348; addr++) {
retval = penwell_otg_msic_read(addr, &val);
if (retval) {
dev_err(pnw->dev, "msic read failed\n");
goto out;
}
dev_info(pnw->dev, "0x%03x: 0x%02x", addr, val);
}
for (addr = 0x394; addr <= 0x3BF; addr++) {
retval = penwell_otg_msic_read(addr, &val);
if (retval) {
dev_err(pnw->dev, "msic read failed\n");
goto out;
}
dev_info(pnw->dev, "0x%03x: 0x%02x", addr, val);
}
addr = 0x192;
retval = penwell_otg_msic_read(addr, &val);
if (retval) {
dev_err(pnw->dev, "msic read failed\n");
goto out;
}
dev_info(pnw->dev, "0x%03x: 0x%02x", addr, val);
out:
penwell_otg_msic_spi_access(false);
}
static int penwell_otg_resume_noirq(struct device *dev)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
struct pci_dev *pdev;
int ret = 0;
u32 val;
dev_dbg(pnw->dev, "%s --->\n", __func__);
pdev = to_pci_dev(pnw->dev);
/* If USB PHY is in OFF state, power on it and do basic init work */
if (!pnw->phy_power_state) {
penwell_otg_phy_power(1);
/* Change phy_power_state to 1 again */
pnw->phy_power_state = 1;
penwell_otg_phy_reset();
/* Reset controller and clear PHY low power mode setting */
reset_otg();
penwell_otg_phy_low_power(0);
/* Wait ID value to be synced */
msleep(60);
}
/* add delay in case controller is back to D0, controller
* needs time to sync/latch value for OTGSC register */
usleep_range(2000, 2500);
if (mid_pmu_is_wake_source(PMU_OTG_WAKE_SOURCE)) {
/* dump OTGSC register for wakeup event */
val = readl(pnw->iotg.base + CI_OTGSC);
dev_info(pnw->dev, "%s: CI_OTGSC=0x%x\n", __func__, val);
if (val & OTGSC_IDIS)
dev_info(pnw->dev, "%s: id change\n", __func__);
if (val & OTGSC_DPIS)
dev_info(pnw->dev, "%s: data pulse\n", __func__);
if (val & OTGSC_BSEIS)
dev_info(pnw->dev, "%s: b sess end\n", __func__);
if (val & OTGSC_BSVIS)
dev_info(pnw->dev, "%s: b sess valid\n", __func__);
if (val & OTGSC_ASVIS)
dev_info(pnw->dev, "%s: a sess valid\n", __func__);
if (val & OTGSC_AVVIS)
dev_info(pnw->dev, "%s: a vbus valid\n", __func__);
if (!(val & OTGSC_INTSTS_MASK)) {
static bool uevent_reported;
dev_info(pnw->dev,
"%s: waking up from USB source, but not a OTG wakeup event\n",
__func__);
if (!uevent_reported) {
if (!is_clovertrail(pdev))
penwell_otg_dump_bogus_wake();
queue_work(pnw->qwork, &pnw->uevent_work);
uevent_reported = true;
}
}
}
if (iotg->otg.state != OTG_STATE_A_WAIT_BCON &&
iotg->otg.state != OTG_STATE_A_HOST) {
penwell_otg_vusb330_low_power(0);
penwell_otg_phy_low_power(0);
}
/* D3->D0 controller will be reset, so reset work mode and PHY state
* which is cleared by the reset */
switch (pnw->iotg.otg.state) {
case OTG_STATE_B_IDLE:
break;
case OTG_STATE_A_WAIT_BCON:
case OTG_STATE_A_HOST:
if (iotg->resume_noirq_host)
ret = iotg->resume_noirq_host(iotg);
break;
default:
break;
}
/* We didn't disable otgsc interrupt, to prevent intr from happening
* before penwell_otg_resume, intr is disabled here, and can be enabled
* by penwell_otg_resume
*/
penwell_otg_intr(0);
dev_dbg(pnw->dev, "%s <---\n", __func__);
return ret;
}
static int penwell_otg_resume(struct device *dev)
{
struct penwell_otg *pnw = the_transceiver;
struct intel_mid_otg_xceiv *iotg = &pnw->iotg;
int ret = 0;
unsigned long flags;
dev_dbg(pnw->dev, "%s --->\n", __func__);
switch (iotg->otg.state) {
case OTG_STATE_A_WAIT_BCON:
if (iotg->resume_host)
ret = iotg->resume_host(iotg);
penwell_otg_add_timer(TA_WAIT_BCON_TMR);
break;
case OTG_STATE_A_HOST:
if (iotg->resume_host)
ret = iotg->resume_host(iotg);
/* FIXME: Ideally here should re-start HNP polling,
* no start HNP here, because it blocks the resume
*/
break;
default:
break;
}
if (ret)
return ret;
/* allow queue work from notifier */
spin_lock_irqsave(&pnw->lock, flags);
pnw->queue_stop = 0;
spin_unlock_irqrestore(&pnw->lock, flags);
penwell_otg_intr(1);
/* If a plugging in or pluggout event happens during D3,
* we will miss the interrupt, so check OTGSC here to check
* if any ID change and update hsm correspondingly
*/
update_hsm();
penwell_update_transceiver();
dev_dbg(pnw->dev, "%s <---\n", __func__);
return ret;
}
#ifdef CONFIG_PM_RUNTIME
/* Runtime PM */
static int penwell_otg_runtime_suspend(struct device *dev)
{
struct penwell_otg *pnw = the_transceiver;
struct pci_dev *pdev = to_pci_dev(dev);
int ret = 0;
u32 val;
unsigned long flags;
dev_dbg(dev, "%s --->\n", __func__);
pnw->rt_quiesce = 1;
/* Flush any pending otg irq on local or any other CPUs.
*
* Host mode or Device mode irq should be synchronized by itself in
* their runtime_suspend handler. In fact, Host mode does so. For
* device mode, we don't care as its runtime PM is disabled.
*
* As device's runtime_status is already RPM_SUSPENDING, after flushing,
* any new irq handling will be rejected (otg irq handler only continues
* if runtime_status is RPM_ACTIVE).
* Thus, now it's safe to put PHY into low power mode and gate the
* fabric later in pci_set_power_state().
*/
synchronize_irq(pdev->irq);
switch (pnw->iotg.otg.state) {
case OTG_STATE_A_IDLE:
break;
case OTG_STATE_B_IDLE:
val = readl(pnw->iotg.base + CI_USBMODE);
if (!(val & USBMODE_CM)) {
/* Controller needs to reset & set mode */
dev_dbg(dev, "reset to client mode\n");
set_client_mode();
}
break;
case OTG_STATE_A_WAIT_BCON:
case OTG_STATE_A_HOST:
case OTG_STATE_A_SUSPEND:
if (pnw->iotg.runtime_suspend_host)
ret = pnw->iotg.runtime_suspend_host(&pnw->iotg);
break;
case OTG_STATE_A_PERIPHERAL:
case OTG_STATE_B_PERIPHERAL:
if (pnw->iotg.runtime_suspend_peripheral)
ret = pnw->iotg.runtime_suspend_peripheral(&pnw->iotg);
break;
default:
break;
}
if (ret) {
spin_lock_irqsave(&pnw->lock, flags);
pnw->rt_quiesce = 0;
if (pnw->rt_resuming) {
pnw->rt_resuming = 0;
pm_runtime_put(pnw->dev);
}
spin_unlock_irqrestore(&pnw->lock, flags);
goto DONE;
}
penwell_otg_phy_low_power(1);
msleep(2);
penwell_otg_vusb330_low_power(1);
DONE:
dev_dbg(dev, "%s <---: ret = %d\n", __func__, ret);
return ret;
}
static int penwell_otg_runtime_resume(struct device *dev)
{
struct penwell_otg *pnw = the_transceiver;
int ret = 0;
u32 val;
unsigned long flags;
dev_dbg(dev, "%s --->\n", __func__);
penwell_otg_phy_low_power(0);
penwell_otg_vusb330_low_power(0);
/* waiting for hardware stable */
usleep_range(2000, 4000);
switch (pnw->iotg.otg.state) {
case OTG_STATE_A_IDLE:
break;
case OTG_STATE_B_IDLE:
val = readl(pnw->iotg.base + CI_USBMODE);
if (!(val & USBMODE_CM)) {
/* Controller needs to reset & set mode */
dev_dbg(dev, "reset to client mode\n");
set_client_mode();
}
break;
case OTG_STATE_A_WAIT_BCON:
case OTG_STATE_A_HOST:
case OTG_STATE_A_SUSPEND:
if (pnw->iotg.runtime_resume_host)
ret = pnw->iotg.runtime_resume_host(&pnw->iotg);
break;
case OTG_STATE_A_PERIPHERAL:
case OTG_STATE_B_PERIPHERAL:
if (pnw->iotg.runtime_resume_peripheral)
ret = pnw->iotg.runtime_resume_peripheral(&pnw->iotg);
break;
default:
break;
}
spin_lock_irqsave(&pnw->lock, flags);
pnw->rt_quiesce = 0;
if (pnw->rt_resuming) {
pnw->rt_resuming = 0;
pm_runtime_put(pnw->dev);
}
spin_unlock_irqrestore(&pnw->lock, flags);
dev_dbg(dev, "%s <---\n", __func__);
return ret;
}
static int penwell_otg_runtime_idle(struct device *dev)
{
struct penwell_otg *pnw = the_transceiver;
dev_dbg(dev, "%s --->\n", __func__);
switch (pnw->iotg.otg.state) {
case OTG_STATE_A_WAIT_VRISE:
case OTG_STATE_A_WAIT_VFALL:
case OTG_STATE_A_VBUS_ERR:
case OTG_STATE_B_WAIT_ACON:
case OTG_STATE_B_HOST:
dev_dbg(dev, "Keep in active\n");
dev_dbg(dev, "%s <---\n", __func__);
return -EBUSY;
case OTG_STATE_A_WAIT_BCON:
case OTG_STATE_A_HOST:
/* Schedule runtime_suspend without delay */
pm_schedule_suspend(dev, 0);
dev_dbg(dev, "%s <---\n", __func__);
return -EBUSY;
default:
break;
}
/* some delay for stability */
pm_schedule_suspend(dev, 500);
dev_dbg(dev, "%s <---\n", __func__);
return -EBUSY;
}
#else
#define penwell_otg_runtime_suspend NULL
#define penwell_otg_runtime_resume NULL
#define penwell_otg_runtime_idle NULL
#endif
/*----------------------------------------------------------*/
DEFINE_PCI_DEVICE_TABLE(pci_ids) = {{
.class = ((PCI_CLASS_SERIAL_USB << 8) | 0x20),
.class_mask = ~0,
.vendor = 0x8086,
.device = 0x0829,
.subvendor = PCI_ANY_ID,
.subdevice = PCI_ANY_ID,
},
{ /* Cloverview */
.class = ((PCI_CLASS_SERIAL_USB << 8) | 0x20),
.class_mask = ~0,
.vendor = 0x8086,
.device = 0xE006,
.subvendor = PCI_ANY_ID,
.subdevice = PCI_ANY_ID,
},
{ /* end: all zeroes */ }
};
static const struct dev_pm_ops penwell_otg_pm_ops = {
.runtime_suspend = penwell_otg_runtime_suspend,
.runtime_resume = penwell_otg_runtime_resume,
.runtime_idle = penwell_otg_runtime_idle,
.suspend = penwell_otg_suspend,
.suspend_noirq = penwell_otg_suspend_noirq,
.resume = penwell_otg_resume,
.resume_noirq = penwell_otg_resume_noirq,
};
static struct pci_driver otg_pci_driver = {
.name = (char *) driver_name,
.id_table = pci_ids,
.probe = penwell_otg_probe,
.remove = penwell_otg_remove,
.shutdown = penwell_otg_shutdown,
.driver = {
.pm = &penwell_otg_pm_ops
},
};
static int __init penwell_otg_init(void)
{
#ifdef CONFIG_DEBUG_FS
pm_sss0_base = ioremap_nocache(0xFF11D030, 0x100);
#endif
return pci_register_driver(&otg_pci_driver);
}
module_init(penwell_otg_init);
static void __exit penwell_otg_cleanup(void)
{
#ifdef CONFIG_DEBUG_FS
iounmap(pm_sss0_base);
#endif
pci_unregister_driver(&otg_pci_driver);
}
module_exit(penwell_otg_cleanup);