592 lines
13 KiB
C
592 lines
13 KiB
C
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/usb/otg.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/freezer.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/version.h>
|
|
#include <linux/gpio.h>
|
|
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/hcd.h>
|
|
#include <linux/usb/gadget.h>
|
|
#include <linux/usb/dwc3-intel-mid.h>
|
|
#include <linux/usb/xhci-ush-hsic-pci.h>
|
|
#include "otg.h"
|
|
|
|
#define VERSION "2.50a"
|
|
|
|
static int otg_id = -1;
|
|
static int dwc3_intel_cht_notify_charger_type(struct dwc_otg2 *otg,
|
|
enum power_supply_charger_event event);
|
|
static int dwc3_set_id_mux_pm_sync(struct dwc_otg2 *otg, int is_device_on);
|
|
|
|
static int charger_detect_enable(struct dwc_otg2 *otg)
|
|
{
|
|
struct intel_dwc_otg_pdata *data;
|
|
|
|
if (!otg || !otg->otg_data)
|
|
return 0;
|
|
|
|
data = (struct intel_dwc_otg_pdata *)otg->otg_data;
|
|
|
|
return data->charger_detect_enable;
|
|
}
|
|
|
|
static ssize_t store_vbus_evt(struct device *_dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
unsigned long flags;
|
|
struct dwc_otg2 *otg = dwc3_get_otg();
|
|
|
|
if (count != 2) {
|
|
otg_err(otg, "return EINVAL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (count > 0 && buf[count-1] == '\n')
|
|
((char *) buf)[count-1] = 0;
|
|
|
|
switch (buf[0]) {
|
|
case '1':
|
|
otg_dbg(otg, "Change the VBUS to High\n");
|
|
otg->otg_events |= OEVT_B_DEV_SES_VLD_DET_EVNT;
|
|
spin_lock_irqsave(&otg->lock, flags);
|
|
dwc3_wakeup_otg_thread(otg);
|
|
spin_unlock_irqrestore(&otg->lock, flags);
|
|
return count;
|
|
case '0':
|
|
otg_dbg(otg, "Change the VBUS to Low\n");
|
|
otg->otg_events |= OEVT_A_DEV_SESS_END_DET_EVNT;
|
|
spin_lock_irqsave(&otg->lock, flags);
|
|
dwc3_wakeup_otg_thread(otg);
|
|
spin_unlock_irqrestore(&otg->lock, flags);
|
|
return count;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR(vbus_evt, S_IWUSR|S_IWGRP,
|
|
NULL, store_vbus_evt);
|
|
|
|
|
|
static ssize_t store_otg_id(struct device *_dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
unsigned long flags;
|
|
struct dwc_otg2 *otg = dwc3_get_otg();
|
|
|
|
if (!otg)
|
|
return 0;
|
|
if (count != 2) {
|
|
otg_err(otg, "return EINVAL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (count > 0 && buf[count-1] == '\n')
|
|
((char *) buf)[count-1] = 0;
|
|
|
|
switch (buf[0]) {
|
|
case 'a':
|
|
case 'A':
|
|
otg_dbg(otg, "Change ID to A\n");
|
|
otg->user_events |= USER_ID_A_CHANGE_EVENT;
|
|
spin_lock_irqsave(&otg->lock, flags);
|
|
dwc3_wakeup_otg_thread(otg);
|
|
otg_id = 0;
|
|
spin_unlock_irqrestore(&otg->lock, flags);
|
|
return count;
|
|
case 'b':
|
|
case 'B':
|
|
otg_dbg(otg, "Change ID to B\n");
|
|
otg->user_events |= USER_ID_B_CHANGE_EVENT;
|
|
spin_lock_irqsave(&otg->lock, flags);
|
|
dwc3_wakeup_otg_thread(otg);
|
|
otg_id = 1;
|
|
spin_unlock_irqrestore(&otg->lock, flags);
|
|
return count;
|
|
default:
|
|
otg_err(otg, "Just support change ID to A!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
show_otg_id(struct device *_dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
char *next;
|
|
unsigned size, t;
|
|
|
|
next = buf;
|
|
size = PAGE_SIZE;
|
|
|
|
t = scnprintf(next, size,
|
|
"USB OTG ID: %s\n",
|
|
(otg_id ? "B" : "A")
|
|
);
|
|
size -= t;
|
|
next += t;
|
|
|
|
return PAGE_SIZE - size;
|
|
}
|
|
|
|
static DEVICE_ATTR(otg_id, S_IRUGO|S_IWUSR|S_IWGRP,
|
|
show_otg_id, store_otg_id);
|
|
|
|
static void set_sus_phy(struct dwc_otg2 *otg, int bit)
|
|
{
|
|
u32 data = 0;
|
|
|
|
data = otg_read(otg, GUSB2PHYCFG0);
|
|
if (bit)
|
|
data |= GUSB2PHYCFG_SUS_PHY;
|
|
else
|
|
data &= ~GUSB2PHYCFG_SUS_PHY;
|
|
|
|
otg_write(otg, GUSB2PHYCFG0, data);
|
|
|
|
data = otg_read(otg, GUSB3PIPECTL0);
|
|
if (bit)
|
|
data |= GUSB3PIPECTL_SUS_EN;
|
|
else
|
|
data &= ~GUSB3PIPECTL_SUS_EN;
|
|
otg_write(otg, GUSB3PIPECTL0, data);
|
|
}
|
|
|
|
int dwc3_intel_cht_platform_init(struct dwc_otg2 *otg)
|
|
{
|
|
u32 gctl;
|
|
int retval;
|
|
|
|
/* Don't let phy go to suspend mode, which
|
|
* will cause FS/LS devices enum failed in host mode.
|
|
*/
|
|
#if 0
|
|
set_sus_phy(otg, 0);
|
|
#endif
|
|
|
|
retval = device_create_file(otg->dev, &dev_attr_otg_id);
|
|
if (retval < 0) {
|
|
otg_dbg(otg,
|
|
"Can't register sysfs attribute: %d\n", retval);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
retval = device_create_file(otg->dev, &dev_attr_vbus_evt);
|
|
if (retval < 0) {
|
|
otg_dbg(otg,
|
|
"Can't register sysfs attribute: %d\n", retval);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
otg_dbg(otg, "\n");
|
|
otg_write(otg, OEVTEN, 0);
|
|
otg_write(otg, OCTL, 0);
|
|
gctl = otg_read(otg, GCTL);
|
|
gctl |= GCTL_PRT_CAP_DIR_OTG << GCTL_PRT_CAP_DIR_SHIFT;
|
|
otg_write(otg, GCTL, gctl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dwc3_intel_cht_get_id(struct dwc_otg2 *otg)
|
|
{
|
|
/* For BYT ID is not connected to USB, always FLOAT */
|
|
return RID_FLOAT;
|
|
}
|
|
|
|
int dwc3_intel_cht_b_idle(struct dwc_otg2 *otg)
|
|
{
|
|
u32 ret;
|
|
u32 gctl, tmp;
|
|
|
|
/* set mux to host mode in b_idle state */
|
|
ret = dwc3_set_id_mux_pm_sync(otg, 0);
|
|
if (ret)
|
|
otg_err(otg, "Can't set mux to host, %d\n", ret);
|
|
|
|
/* Disable hibernation mode by default */
|
|
gctl = otg_read(otg, GCTL);
|
|
gctl &= ~GCTL_GBL_HIBERNATION_EN;
|
|
otg_write(otg, GCTL, gctl);
|
|
|
|
/* Reset ADP related registers */
|
|
otg_write(otg, ADPCFG, 0);
|
|
otg_write(otg, ADPCTL, 0);
|
|
otg_write(otg, ADPEVTEN, 0);
|
|
tmp = otg_read(otg, ADPEVT);
|
|
otg_write(otg, ADPEVT, tmp);
|
|
|
|
otg_write(otg, OCFG, 0);
|
|
otg_write(otg, OEVTEN, 0);
|
|
tmp = otg_read(otg, OEVT);
|
|
otg_write(otg, OEVT, tmp);
|
|
otg_write(otg, OCTL, OCTL_PERI_MODE);
|
|
|
|
/* Force config to device mode as default */
|
|
gctl = otg_read(otg, GCTL);
|
|
gctl &= ~GCTL_PRT_CAP_DIR;
|
|
gctl |= GCTL_PRT_CAP_DIR_DEV << GCTL_PRT_CAP_DIR_SHIFT;
|
|
otg_write(otg, GCTL, gctl);
|
|
|
|
mdelay(100);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_intel_cht_set_power(struct usb_phy *_otg,
|
|
unsigned ma)
|
|
{
|
|
unsigned long flags;
|
|
struct dwc_otg2 *otg = dwc3_get_otg();
|
|
struct power_supply_cable_props cap;
|
|
struct intel_dwc_otg_pdata *data;
|
|
|
|
/* Just return if charger detection is not enabled */
|
|
if (!charger_detect_enable(otg))
|
|
return 0;
|
|
|
|
data = (struct intel_dwc_otg_pdata *)otg->otg_data;
|
|
if (!data)
|
|
return -EINVAL;
|
|
|
|
if (otg->charging_cap.chrg_type ==
|
|
POWER_SUPPLY_CHARGER_TYPE_USB_CDP)
|
|
return 0;
|
|
else if (otg->charging_cap.chrg_type !=
|
|
POWER_SUPPLY_CHARGER_TYPE_USB_SDP) {
|
|
otg_err(otg, "%s: currently, chrg type is not SDP!\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ma == OTG_DEVICE_SUSPEND) {
|
|
spin_lock_irqsave(&otg->lock, flags);
|
|
cap.chrg_type = otg->charging_cap.chrg_type;
|
|
cap.ma = otg->charging_cap.ma;
|
|
cap.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_SUSPEND;
|
|
spin_unlock_irqrestore(&otg->lock, flags);
|
|
|
|
/* mA is zero mean D+/D- opened cable.
|
|
* If SMIP set, then notify 500mA.
|
|
* Otherwise, notify 0mA.
|
|
*/
|
|
if (!cap.ma) {
|
|
if (data->charging_compliance) {
|
|
cap.ma = 500;
|
|
cap.chrg_evt =
|
|
POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
}
|
|
/* For standard SDP, if SMIP set, then ignore suspend */
|
|
} else if (data->charging_compliance)
|
|
return 0;
|
|
/* Stander SDP(cap.mA != 0) and SMIP not set.
|
|
* Should send 0mA with SUSPEND event
|
|
*/
|
|
else
|
|
cap.ma = 0;
|
|
|
|
atomic_notifier_call_chain(&otg->usb2_phy.notifier,
|
|
USB_EVENT_CHARGER, &cap);
|
|
otg_dbg(otg, "Notify EM CHARGER_EVENT_SUSPEND\n");
|
|
|
|
return 0;
|
|
} else if (ma == OTG_DEVICE_RESUME) {
|
|
otg_dbg(otg, "Notify EM CHARGER_EVENT_CONNECT\n");
|
|
dwc3_intel_cht_notify_charger_type(otg,
|
|
POWER_SUPPLY_CHARGER_EVENT_CONNECT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* For SMIP set case, only need to report 500/900mA */
|
|
if (data->charging_compliance) {
|
|
if ((ma != OTG_USB2_500MA) &&
|
|
(ma != OTG_USB3_900MA))
|
|
return 0;
|
|
}
|
|
|
|
/* Covert macro to integer number*/
|
|
switch (ma) {
|
|
case OTG_USB2_100MA:
|
|
ma = 100;
|
|
break;
|
|
case OTG_USB3_150MA:
|
|
ma = 150;
|
|
break;
|
|
case OTG_USB2_500MA:
|
|
ma = 500;
|
|
break;
|
|
case OTG_USB3_900MA:
|
|
ma = 900;
|
|
break;
|
|
default:
|
|
otg_err(otg, "Device driver set invalid SDP current value!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&otg->lock, flags);
|
|
otg->charging_cap.ma = ma;
|
|
spin_unlock_irqrestore(&otg->lock, flags);
|
|
|
|
dwc3_intel_cht_notify_charger_type(otg,
|
|
POWER_SUPPLY_CHARGER_EVENT_CONNECT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dwc3_intel_cht_enable_vbus(struct dwc_otg2 *otg, int enable)
|
|
{
|
|
/* Return 0, as VBUS is controlled by FSA in BYT */
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_intel_cht_notify_charger_type(struct dwc_otg2 *otg,
|
|
enum power_supply_charger_event event)
|
|
{
|
|
struct power_supply_cable_props cap;
|
|
unsigned long flags;
|
|
|
|
/* Just return if charger detection is not enabled */
|
|
if (!charger_detect_enable(otg))
|
|
return 0;
|
|
|
|
if (event > POWER_SUPPLY_CHARGER_EVENT_DISCONNECT) {
|
|
otg_err(otg,
|
|
"%s: Invalid power_supply_charger_event!\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((otg->charging_cap.chrg_type ==
|
|
POWER_SUPPLY_CHARGER_TYPE_USB_SDP) &&
|
|
((otg->charging_cap.ma != 100) &&
|
|
(otg->charging_cap.ma != 150) &&
|
|
(otg->charging_cap.ma != 500) &&
|
|
(otg->charging_cap.ma != 900))) {
|
|
otg_err(otg, "%s: invalid SDP current!\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&otg->lock, flags);
|
|
cap.chrg_type = otg->charging_cap.chrg_type;
|
|
cap.ma = otg->charging_cap.ma;
|
|
cap.chrg_evt = event;
|
|
spin_unlock_irqrestore(&otg->lock, flags);
|
|
|
|
atomic_notifier_call_chain(&otg->usb2_phy.notifier, USB_EVENT_CHARGER,
|
|
&cap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum power_supply_charger_cable_type
|
|
dwc3_intel_cht_get_charger_type(struct dwc_otg2 *otg)
|
|
{
|
|
/* No need to do charger detection if not enabled */
|
|
return POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
|
|
}
|
|
|
|
static int dwc3_intel_cht_handle_notification(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct dwc_otg2 *otg = dwc3_get_otg();
|
|
int state, val;
|
|
unsigned long flags;
|
|
|
|
if (!otg)
|
|
return NOTIFY_BAD;
|
|
|
|
val = *(int *)data;
|
|
|
|
spin_lock_irqsave(&otg->lock, flags);
|
|
switch (event) {
|
|
case USB_EVENT_VBUS:
|
|
if (val) {
|
|
otg->otg_events |= OEVT_B_DEV_SES_VLD_DET_EVNT;
|
|
otg->otg_events &= ~OEVT_A_DEV_SESS_END_DET_EVNT;
|
|
} else {
|
|
otg->otg_events |= OEVT_A_DEV_SESS_END_DET_EVNT;
|
|
otg->otg_events &= ~OEVT_B_DEV_SES_VLD_DET_EVNT;
|
|
}
|
|
state = NOTIFY_OK;
|
|
break;
|
|
default:
|
|
otg_dbg(otg, "DWC OTG Notify unknow notify message\n");
|
|
state = NOTIFY_DONE;
|
|
}
|
|
dwc3_wakeup_otg_thread(otg);
|
|
spin_unlock_irqrestore(&otg->lock, flags);
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
static int dwc3_set_id_mux(void __iomem *reg_base, int is_device_on)
|
|
{
|
|
u32 reg;
|
|
u32 timeout = 1000;
|
|
|
|
/* Set ID Mux to host. The power of the registers are always on. */
|
|
reg = readl(reg_base + DUAL_ROLE_CFG0);
|
|
if (!(reg & SW_IDPIN_EN)) {
|
|
reg |= SW_IDPIN_EN;
|
|
writel(reg, reg_base + DUAL_ROLE_CFG0);
|
|
}
|
|
|
|
reg = readl(reg_base + DUAL_ROLE_CFG0);
|
|
if (is_device_on)
|
|
reg |= SW_IDPIN;
|
|
else
|
|
reg &= ~SW_IDPIN;
|
|
writel(reg, reg_base + DUAL_ROLE_CFG0);
|
|
|
|
do {
|
|
reg = readl(reg_base + DUAL_ROLE_CFG1);
|
|
if (is_device_on) {
|
|
if (!(reg & SUS))
|
|
break;
|
|
} else {
|
|
if (reg & SUS)
|
|
break;
|
|
}
|
|
timeout--;
|
|
if (!timeout) {
|
|
pr_err("cfg1 polling timeout, reg = 0x%08x\n", reg);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
mdelay(1);
|
|
} while (1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_set_id_mux_pm_sync(struct dwc_otg2 *otg, int is_device_on)
|
|
{
|
|
struct usb_bus *host = otg->otg.host;
|
|
int ret;
|
|
|
|
if (!host) {
|
|
otg_err(otg,
|
|
"Can't switch mux, USB host is not registered\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* cfg0, cfg1 can't be accessed if xHCI is fabric gated */
|
|
pm_runtime_get_sync(host->controller);
|
|
ret = dwc3_set_id_mux(bus_to_hcd(host)->regs, is_device_on);
|
|
pm_runtime_put(host->controller);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int dwc3_intel_cht_after_stop_peripheral(struct dwc_otg2 *otg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int dwc3_intel_cht_prepare_start_peripheral(struct dwc_otg2 *otg)
|
|
{
|
|
return dwc3_set_id_mux_pm_sync(otg, 1);
|
|
}
|
|
|
|
int dwc3_intel_cht_suspend(struct dwc_otg2 *otg)
|
|
{
|
|
struct pci_dev *pci_dev;
|
|
pci_power_t state = PCI_D3cold;
|
|
|
|
if (!otg)
|
|
return 0;
|
|
|
|
pci_dev = to_pci_dev(otg->dev);
|
|
|
|
if (otg->state == DWC_STATE_B_PERIPHERAL ||
|
|
otg->state == DWC_STATE_A_HOST)
|
|
state = PCI_D3hot;
|
|
|
|
set_sus_phy(otg, 1);
|
|
|
|
if (pci_save_state(pci_dev)) {
|
|
otg_err(otg, "pci_save_state failed!\n");
|
|
return -EIO;
|
|
}
|
|
|
|
pci_disable_device(pci_dev);
|
|
pci_set_power_state(pci_dev, state);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dwc3_intel_cht_resume(struct dwc_otg2 *otg)
|
|
{
|
|
struct pci_dev *pci_dev;
|
|
|
|
if (!otg)
|
|
return 0;
|
|
|
|
pci_dev = to_pci_dev(otg->dev);
|
|
|
|
/* From synopsys spec 12.2.11.
|
|
* Software cannot access memory-mapped I/O space
|
|
* for 10ms.
|
|
*/
|
|
mdelay(10);
|
|
|
|
pci_restore_state(pci_dev);
|
|
if (pci_enable_device(pci_dev) < 0) {
|
|
otg_err(otg, "pci_enable_device failed.\n");
|
|
return -EIO;
|
|
}
|
|
|
|
#if 0
|
|
set_sus_phy(otg, 0);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
struct dwc3_otg_hw_ops dwc3_intel_cht_otg_pdata = {
|
|
.mode = DWC3_DEVICE_ONLY,
|
|
.bus = DWC3_PCI,
|
|
.get_id = dwc3_intel_cht_get_id,
|
|
.b_idle = dwc3_intel_cht_b_idle,
|
|
.set_power = dwc3_intel_cht_set_power,
|
|
.enable_vbus = dwc3_intel_cht_enable_vbus,
|
|
.platform_init = dwc3_intel_cht_platform_init,
|
|
.get_charger_type = dwc3_intel_cht_get_charger_type,
|
|
.otg_notifier_handler = dwc3_intel_cht_handle_notification,
|
|
.prepare_start_peripheral = dwc3_intel_cht_prepare_start_peripheral,
|
|
.after_stop_peripheral = dwc3_intel_cht_after_stop_peripheral,
|
|
.notify_charger_type = dwc3_intel_cht_notify_charger_type,
|
|
|
|
.suspend = dwc3_intel_cht_suspend,
|
|
.resume = dwc3_intel_cht_resume,
|
|
};
|
|
|
|
static int __init dwc3_intel_cht_init(void)
|
|
{
|
|
return dwc3_otg_register(&dwc3_intel_cht_otg_pdata);
|
|
}
|
|
module_init(dwc3_intel_cht_init);
|
|
|
|
static void __exit dwc3_intel_cht_exit(void)
|
|
{
|
|
dwc3_otg_unregister(&dwc3_intel_cht_otg_pdata);
|
|
}
|
|
module_exit(dwc3_intel_cht_exit);
|
|
|
|
MODULE_AUTHOR("Wang Yu <yu.y.wang@intel.com>");
|
|
MODULE_AUTHOR("Wu, Hao <hao.wu@intel.com>");
|
|
MODULE_AUTHOR("Yuan, Hang <hang.yuan@intel.com>");
|
|
MODULE_DESCRIPTION("DWC3 Intel CHT OTG Driver");
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
MODULE_VERSION(VERSION);
|