blob: efdd9bfadb677986fd8ceae48df95c693de66f62 [file] [log] [blame]
/*
* Intel MID Platform XHCI/SSIC Controller PCI Bus Glue.
*
* Copyright (c) 2014, Intel Corporation.
* Author: Tang, Jianqiang <jianqiang.tang@intel.com>
* Some code borrowed from usbcore hub and xhci driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 2 as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/usb/xhci-ssic-pci.h>
static const char ssic_group_name[] = "ssic";
static struct pci_dev *ssic_pci_dev;
static struct ssic_xhci_hcd ssic_hcd;
static struct class *ssic_class;
static struct device *ssic_class_dev;
static int create_ssic_class_device_files(struct pci_dev *pdev);
static void remove_ssic_class_device_files(void);
static int xhci_ssic_private_reset(struct usb_hcd *hcd);
static int is_ssic_host(struct usb_device *udev)
{
struct pci_dev *pdev;
pdev = to_pci_dev(udev->bus->controller);
if (!pdev || !udev) {
pr_err("%s %d pdev or udev is NULL, return\n",
__func__, __LINE__);
return 0;
}
dev_dbg(&udev->dev, "device ID: %d, portnum: %d",
pdev->device, udev->portnum);
/* CherryTrail and ANN SSIC Controller */
if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
if (pdev->device != PCI_DEVICE_ID_INTEL_CHT_USH &&
pdev->device != PCI_DEVICE_ID_INTEL_CHT_USH_A1 &&
pdev->device != PCI_DEVICE_ID_INTEL_MOOR_SSIC) {
dev_dbg(&udev->dev, "NOT SSIC Controller uevent, ignore\n");
return 0;
}
}
/* Ignore USB devices on external hub */
if (udev->parent && udev->parent->parent)
return 0;
return 1;
}
static void ssic_wake_lock(void)
{
mutex_lock(&ssic_hcd.wakelock_mutex);
if (ssic_hcd.wakelock_state == UNLOCKED) {
wake_lock(&ssic_hcd.ssic_wake_lock);
ssic_hcd.wakelock_state = LOCKED;
}
mutex_unlock(&ssic_hcd.wakelock_mutex);
}
static void ssic_wake_unlock(void)
{
mutex_lock(&ssic_hcd.wakelock_mutex);
if (ssic_hcd.wakelock_state == LOCKED) {
wake_unlock(&ssic_hcd.ssic_wake_lock);
ssic_hcd.wakelock_state = UNLOCKED;
}
mutex_unlock(&ssic_hcd.wakelock_mutex);
}
int ssic_set_port_link_state(struct usb_hub *hub,
int port1, unsigned int link_status)
{
return set_port_feature(hub->hdev,
port1 | (link_status << 3),
USB_PORT_FEAT_LINK_STATE);
}
static int ssic_get_port_status(struct usb_device *hdev, int port1,
struct usb_port_status *data)
{
int i, status = -ETIMEDOUT;
for (i = 0; i < SSIC_STS_RETRIES &&
(status == -ETIMEDOUT || status == -EPIPE); i++) {
status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1,
data, sizeof(*data), SSIC_STS_TIMEOUT);
}
return status;
}
static int ssic_hub_port_status(struct usb_hub *hub, int port1,
u16 *status, u16 *change)
{
int ret;
mutex_lock(&hub->status_mutex);
ret = ssic_get_port_status(hub->hdev, port1, &hub->status->port);
if (ret < 4) {
if (ret != -ENODEV)
dev_err(hub->intfdev,
"%s failed (err = %d)\n", __func__, ret);
if (ret >= 0)
ret = -EIO;
} else {
*status = le16_to_cpu(hub->status->port.wPortStatus);
*change = le16_to_cpu(hub->status->port.wPortChange);
ret = 0;
}
mutex_unlock(&hub->status_mutex);
return ret;
}
static int hub_usb3_port_disable(struct usb_hub *hub, int port1)
{
int ret;
int total_time;
u16 portchange, portstatus;
if (!(hub->hdev->speed == USB_SPEED_SUPER))
return -EINVAL;
ret = ssic_set_port_link_state(hub, port1, USB_SS_PORT_LS_SS_DISABLED);
if (ret)
return ret;
/* Wait for the link to enter the disabled state. */
for (total_time = 0;; total_time += SSIC_HUB_DEBOUNCE_STEP) {
ret = ssic_hub_port_status(hub, port1, &portstatus, &portchange);
if (ret < 0)
return ret;
if ((portstatus & USB_PORT_STAT_LINK_STATE) ==
USB_SS_PORT_LS_SS_DISABLED)
break;
if (total_time >= SSIC_HUB_DEBOUNCE_TIMEOUT)
break;
msleep(SSIC_HUB_DEBOUNCE_STEP);
}
if (total_time >= SSIC_HUB_DEBOUNCE_TIMEOUT) {
dev_warn(hub->intfdev, "Could not disable port %d after %d ms\n",
port1, total_time);
return -ETIMEDOUT;
}
pr_err("SSIC: Place Link to SS.Disable successful\n");
return 0;
}
static int ssic_port_enable(struct xhci_hcd *xhci, int enable)
{
int status;
u32 temp;
__le32 __iomem **port_array;
port_array = xhci->usb3_ports;
if (enable) {
if (ssic_hcd.rh_dev) {
dev_dbg(&ssic_pci_dev->dev,
"%s----> enable port\n", __func__);
/* In Cherryview, REGISTER_BANK_VALID is lost after D3/D0 transition.
* Make sure this bit is set before PROG_DONE bit is set
*/
temp = xhci_readl(xhci, &ssic_hcd.profile_regs->access_control);
if (!(temp & REGISTER_BANK_VALID)) {
temp |= REGISTER_BANK_VALID;
xhci_writel(xhci, temp, &ssic_hcd.profile_regs->access_control);
}
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X before write\n", temp);
/* Write PROG_DONE[30] == 0 */
xhci_dbg(xhci, "Clear PROG_DONE\n");
temp &= ~PROG_DONE;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X after clear PROG_DONE\n", temp);
if (temp & SSIC_PORT_UNUSED) {
xhci_dbg(xhci, "temp & SSIC_PORT_UNUSED, config_register2 = 0x%08X\n", temp);
temp &= ~SSIC_PORT_UNUSED;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
}
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X after clear PORT_UNUSED\n", temp);
temp |= PROG_DONE;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X after set PROG_DONE\n", temp);
temp = xhci_readl(xhci, port_array[ssic_hcd.ssic_port - 1]);
xhci_dbg(xhci, "ssic_enable = 1 , before set PP, portsc = 0x%X\n", temp);
status = set_port_feature(ssic_hcd.rh_dev,
ssic_hcd.ssic_port,
USB_PORT_FEAT_POWER);
if (status < 0) {
dev_err(&ssic_pci_dev->dev,
"%s set port power failed, return %d\n",
__func__, status);
return status;
}
if (status == 0)
xhci_dbg(xhci, "set port_power successful\n");
temp = xhci_readl(xhci, port_array[ssic_hcd.ssic_port - 1]);
xhci_dbg(xhci, "ssic_enable = 1 , after set PP, portsc = 0x%X\n", temp);
if (ssic_hcd.modem_dev) {
dev_dbg(&ssic_pci_dev->dev,
"Disable auto suspend in port enable\n");
usb_disable_autosuspend(ssic_hcd.modem_dev);
}
usb_disable_autosuspend(ssic_hcd.rh_dev);
ssic_hcd.autosuspend_enable = 0;
ssic_hcd.u1_enable = 0;
ssic_hcd.u2_enable = 0;
}
} else {
if (ssic_hcd.rh_dev) {
dev_dbg(&ssic_pci_dev->dev,
"%s----> disable port\n", __func__);
hub_usb3_port_disable(usb_hub_to_struct_hub(ssic_hcd.rh_dev), ssic_hcd.ssic_port);
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X before write\n", temp);
/* Write PROG_DONE[30] == 0 */
temp &= ~PROG_DONE;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X after clear PROG_DONE\n", temp);
/* Write PORT_UNUSED[31] == 1 */
temp |= SSIC_PORT_UNUSED;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X after set PORT_UNUSED\n", temp);
xhci_dbg(xhci, "Begin to do 2ms sleep\n");
usleep_range(2000, 2500);
xhci_dbg(xhci, "Finish the 2ms sleep\n");
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X after 2ms sleep\n", temp);
/* Write PROG_DONE[30] == 1 */
temp |= PROG_DONE;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config Register2 = 0x%08X after write PROG_DONE\n", temp);
xhci_dbg(xhci, "Before Clear PP, portsc = %X\n",
xhci_readl(xhci, port_array[ssic_hcd.ssic_port - 1]));
status = clear_port_feature(ssic_hcd.rh_dev,
ssic_hcd.ssic_port,
USB_PORT_FEAT_POWER);
if (status < 0) {
dev_err(&ssic_pci_dev->dev,
"%s clear port power failed, return %d\n",
__func__, status);
return status;
}
if (status == 0)
xhci_dbg(xhci, "clear port power() successful\n");
xhci_dbg(xhci, "After clear PP, portsc = 0x%X\n",
xhci_readl(xhci, port_array[ssic_hcd.ssic_port - 1]));
status = set_port_feature(ssic_hcd.rh_dev,
ssic_hcd.ssic_port,
USB_PORT_FEAT_POWER);
if (status < 0) {
dev_err(&ssic_pci_dev->dev,
"%s set port power failed, return %d\n",
__func__, status);
return status;
}
if (status == 0)
xhci_dbg(xhci, "set port power() successful\n");
xhci_dbg(xhci, "After set PP, portsc = 0x%X\n",
xhci_readl(xhci, port_array[ssic_hcd.ssic_port - 1]));
dev_dbg(&ssic_pci_dev->dev,
"Enable root hub auto suspend in port disable\n");
usb_enable_autosuspend(ssic_hcd.rh_dev);
ssic_hcd.autosuspend_enable = 1;
}
}
/* set the ssic_enable state */
ssic_hcd.ssic_enable = enable;
/* kick the change if modem attached */
if (ssic_hcd.modem_dev) {
xhci_dbg(xhci, "Modem is there\n");
usb_set_change_bits(ssic_hcd.rh_dev,
ssic_hcd.ssic_port);
usb_kick_khubd(ssic_hcd.rh_dev);
/*
* need to do delay 150-200ms to confirm
* disconnect flow finished, this info is
* from COE guys.
*/
msleep(200);
}
return 0;
}
static ssize_t ssic_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.ssic_enable);
}
static ssize_t ssic_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int retval;
int value;
struct usb_hcd *hcd = dev_get_drvdata(&ssic_pci_dev->dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
if (sscanf(buf, "%d", &value) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
/* just return if ssic_enable == 0 and request enable == 0 */
if ((!ssic_hcd.ssic_enable) && (value == 0)) {
dev_dbg(dev, "SSIC state is already %d,ignore request\n",
value);
return -EINVAL;
}
usb_lock_device(ssic_hcd.rh_dev);
mutex_lock(&ssic_hcd.ssic_mutex);
if (!ssic_hcd.rh_dev) {
dev_dbg(dev, "root hub is already removed\n");
retval = -ENODEV;
goto out;
}
/* need to resume if Controller already in D0i3 */
if (ssic_hcd.modem_dev)
pm_runtime_get_sync(&ssic_hcd.modem_dev->dev);
if (ssic_hcd.rh_dev)
pm_runtime_get_sync(&ssic_hcd.rh_dev->dev);
if (value) {
/* request IPC on while IPC already on */
if (ssic_hcd.ssic_enable) {
/* need to disable and re-enable again */
dev_dbg(dev, "disable IPC before enable it\n");
retval = ssic_port_enable(xhci, 0);
if (retval) {
dev_err(dev, "disable IPC failed, retval = %d\n",
retval);
goto pm_out;
}
/* re-enable IPC again */
retval = ssic_port_enable(xhci, 1);
if (retval) {
dev_err(dev, "enable IPC failed, retval = %d\n",
retval);
goto pm_out;
}
} else {
/* request IPC on while current IPC is off */
dev_dbg(dev, "enable SSIC\n");
retval = ssic_port_enable(xhci, 1);
if (retval) {
dev_err(dev, "enable IPC failed, retval = %d\n",
retval);
goto pm_out;
}
}
/* hold wakelock */
ssic_wake_lock();
} else {
if (ssic_hcd.ssic_enable) {
/* disable IPC if current state is on
* and request IPC off
*/
dev_dbg(dev, "disable SSIC IPC\n");
retval = ssic_port_enable(xhci, 0);
if (retval) {
dev_err(dev, "disable IPC failed, retval = %d\n",
retval);
goto pm_out;
}
/* hold wakelock */
ssic_wake_unlock();
}
}
pm_out:
/* need to resume if Controller already in D0i3 */
if (ssic_hcd.modem_dev)
pm_runtime_put(&ssic_hcd.modem_dev->dev);
if (ssic_hcd.rh_dev)
pm_runtime_put(&ssic_hcd.rh_dev->dev);
out:
mutex_unlock(&ssic_hcd.ssic_mutex);
usb_unlock_device(ssic_hcd.rh_dev);
return size;
}
static DEVICE_ATTR(ssic_enable, S_IRUGO | S_IWUSR | S_IROTH | S_IWOTH,
ssic_enable_show, ssic_enable_store);
static ssize_t ssic_show_registers(struct device *dev,
struct device_attribute *attr, char *buf)
{
char *next;
unsigned size;
unsigned t = 0;
int status;
struct usb_hcd *hcd;
struct xhci_hcd *xhci;
__le32 __iomem **port_array;
next = buf;
size = PAGE_SIZE;
/* check if pm_runtime_get_sync successful or not */
status = pm_runtime_get_sync(&ssic_pci_dev->dev);
if (status < 0) {
dev_err(&ssic_pci_dev->dev,
"%s: pm_runtime_get_sync FAILED err = %d\n",
__func__, status);
pm_runtime_put_sync(&ssic_pci_dev->dev);
return -EINVAL;
}
hcd = dev_get_drvdata(&ssic_pci_dev->dev);
if (!hcd) {
dev_err(&ssic_pci_dev->dev, "hcd is NULL\n");
pm_runtime_put_sync(&ssic_pci_dev->dev);
return -ENODEV;
}
if (hcd->regs) {
xhci = hcd_to_xhci(hcd);
if (!xhci) {
dev_err(&ssic_pci_dev->dev, "xhci is NULL\n");
pm_runtime_put_sync(&ssic_pci_dev->dev);
return -ENODEV;
}
port_array = xhci->usb3_ports;
t = scnprintf(next, size,
"\n"
"Controller Virtual Base Address = 0x%p\n"
"USBCMD = 0x%x\n"
"USBSTS = 0x%x\n"
"PORTSC1 = 0x%x, address = 0x%p\n"
"PORTPMSC1 = 0x%x\n"
"PORTLI1 = 0x%x\n"
"Capability ID = 0x%x\n"
"Global Control = 0x%x\n"
"Config register1 = 0x%x\n"
"Config register2 = 0x%x\n"
"Config register3 = 0x%x\n"
"Config register4 = 0x%x\n"
"Capability Register = 0x%x\n"
"Access Control = 0x%x\n"
"Access Control Status = 0x%x\n"
"PORT1: Config Register2 = 0x%x\n",
hcd->regs,
xhci_readl(xhci, &xhci->op_regs->command),
xhci_readl(xhci, &xhci->op_regs->status),
xhci_readl(xhci, port_array[ssic_hcd.ssic_port - 1]),
port_array[ssic_hcd.ssic_port - 1],
xhci_readl(xhci, port_array[ssic_hcd.ssic_port - 1] + 1),
xhci_readl(xhci, &xhci->op_regs->port_link_base),
xhci_readl(xhci, &ssic_hcd.policy_regs->cap_id),
xhci_readl(xhci, &ssic_hcd.policy_regs->global_control),
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg1),
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2),
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg3),
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg4),
xhci_readl(xhci, &ssic_hcd.profile_regs->cap_reg),
xhci_readl(xhci, &ssic_hcd.profile_regs->access_control),
xhci_readl(xhci, &ssic_hcd.profile_regs->access_status),
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2 + 12));
}
pm_runtime_put_sync(&ssic_pci_dev->dev);
size -= t;
next += t;
return PAGE_SIZE - size;
}
static DEVICE_ATTR(ssic_registers, S_IRUGO, ssic_show_registers, NULL);
/* port inactivityDuration sysfs interface */
static ssize_t ssic_port_inactivity_duration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.port_inactivity_duration);
}
static ssize_t ssic_port_inactivity_duration_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
unsigned duration;
if (sscanf(buf, "%d", &duration) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
if (duration < 0) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
mutex_lock(&ssic_hcd.ssic_mutex);
if (ssic_hcd.modem_dev != NULL) {
pm_runtime_set_autosuspend_delay
(&ssic_hcd.modem_dev->dev, duration);
ssic_hcd.port_inactivity_duration = duration;
dev_dbg(dev, "port Duration after change: %d\n",
ssic_hcd.port_inactivity_duration);
} else {
dev_dbg(dev, "No SSIC Modem, just ignore this request\n");
mutex_unlock(&ssic_hcd.ssic_mutex);
return -ENODEV;
}
mutex_unlock(&ssic_hcd.ssic_mutex);
return size;
}
static DEVICE_ATTR(port_inactivity_duration,
S_IRUGO | S_IWUSR | S_IROTH | S_IWOTH,
ssic_port_inactivity_duration_show,
ssic_port_inactivity_duration_store);
static ssize_t ssic_u1_inactivity_duration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.u1_inactivity_duration);
}
static ssize_t ssic_u1_inactivity_duration_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
unsigned duration;
if (sscanf(buf, "%d", &duration) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
if (duration <= 0 || duration > SSIC_MAX_U1_TIMEOUT) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
mutex_lock(&ssic_hcd.ssic_mutex);
if (ssic_hcd.modem_dev != NULL) {
ssic_hcd.u1_inactivity_duration = duration;
dev_dbg(dev, "u1 Duration after change: %d\n",
ssic_hcd.u1_inactivity_duration);
} else {
dev_dbg(dev, "No SSIC Modem, just ignore this request\n");
mutex_unlock(&ssic_hcd.ssic_mutex);
return -ENODEV;
}
mutex_unlock(&ssic_hcd.ssic_mutex);
return size;
}
static DEVICE_ATTR(u1_inactivity_duration,
S_IRUGO | S_IWUSR | S_IROTH | S_IWOTH,
ssic_u1_inactivity_duration_show,
ssic_u1_inactivity_duration_store);
static ssize_t ssic_u2_inactivity_duration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.u2_inactivity_duration);
}
static ssize_t ssic_u2_inactivity_duration_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
unsigned duration;
if (sscanf(buf, "%d", &duration) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
if (duration <= 0 || duration > SSIC_MAX_U2_TIMEOUT) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
mutex_lock(&ssic_hcd.ssic_mutex);
if (ssic_hcd.modem_dev != NULL) {
ssic_hcd.u2_inactivity_duration = duration;
dev_dbg(dev, "u2 Duration after change: %d\n",
ssic_hcd.u1_inactivity_duration);
} else {
dev_dbg(dev, "No SSIC Modem, just ignore this request\n");
mutex_unlock(&ssic_hcd.ssic_mutex);
return -ENODEV;
}
mutex_unlock(&ssic_hcd.ssic_mutex);
return size;
}
static DEVICE_ATTR(u2_inactivity_duration,
S_IRUGO | S_IWUSR | S_IROTH | S_IWOTH,
ssic_u2_inactivity_duration_show,
ssic_u2_inactivity_duration_store);
static int ssic_set_device_initiated_lpm(struct usb_device *udev,
enum usb3_link_state state, bool enable)
{
int ret;
int feature;
switch (state) {
case USB3_LPM_U1:
feature = USB_DEVICE_U1_ENABLE;
break;
case USB3_LPM_U2:
feature = USB_DEVICE_U2_ENABLE;
break;
default:
dev_warn(&udev->dev, "%s: Can't %s non-U1 or U2 state.\n",
__func__, enable ? "enable" : "disable");
return -EINVAL;
}
if (udev->state != USB_STATE_CONFIGURED) {
dev_dbg(&udev->dev, "%s: Can't %s %s state "
"for unconfigured device.\n",
__func__, enable ? "enable" : "disable",
state == USB_DEVICE_U1_ENABLE ? "U1" : "U2");
return 0;
}
if (enable) {
/*
* Now send the control transfer to enable device-initiated LPM
* for either U1 or U2.
*/
ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_SET_FEATURE,
USB_RECIP_DEVICE,
feature,
0, NULL, 0,
USB_CTRL_SET_TIMEOUT);
} else {
ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_CLEAR_FEATURE,
USB_RECIP_DEVICE,
feature,
0, NULL, 0,
USB_CTRL_SET_TIMEOUT);
}
if (ret < 0) {
dev_warn(&udev->dev, "%s of device-initiated %s failed.\n",
enable ? "Enable" : "Disable",
state == USB_DEVICE_U1_ENABLE ? "U1" : "U2");
return -EBUSY;
}
return 0;
}
/*
* A U1 timeout of 0x0 means the parent hub will reject any transitions to U1.
* 0xff means the parent hub will accept transitions to U1, but will not
* initiate a transition.
*
* A U1 timeout of 0x1 to 0x7F also causes the hub to initiate a transition to
* U1 after that many microseconds. Timeouts of 0x80 to 0xFE are reserved
* values.
*
* A U2 timeout of 0x0 means the parent hub will reject any transitions to U2.
* 0xff means the parent hub will accept transitions to U2, but will not
* initiate a transition.
*
* A U2 timeout of 0x1 to 0xFE also causes the hub to initiate a transition to
* U2 after N*256 microseconds. Therefore a U2 timeout value of 0x1 means a U2
* idle timer of 256 microseconds, 0x2 means 512 microseconds, 0xFE means
* 65.024ms.
*/
static int ssic_set_host_initiated_lpm(struct device *dev,
enum usb3_link_state state, bool enable)
{
struct usb_hcd *hcd;
struct xhci_hcd *xhci;
__le32 __iomem **port_array;
u32 temp, timeout, port;
int ret;
hcd = dev_get_drvdata(&ssic_pci_dev->dev);
if (!hcd)
return -ENODEV;
xhci = hcd_to_xhci(hcd);
port_array = xhci->usb3_ports;
port = ssic_hcd.ssic_port - 1;
if (state != USB3_LPM_U1 && state != USB3_LPM_U2)
return -EINVAL;
switch (state) {
case USB3_LPM_U1:
if (enable)
timeout = ssic_hcd.u1_inactivity_duration;
else
timeout = 0;
temp = xhci_readl(xhci, port_array[port] + PORTPMSC);
temp &= ~PORT_U1_TIMEOUT_MASK;
temp |= PORT_U1_TIMEOUT(timeout);
xhci_writel(xhci, temp, port_array[port] + PORTPMSC);
break;
case USB3_LPM_U2:
if (enable)
timeout = ssic_hcd.u1_inactivity_duration * 4;
else
timeout = 0;
temp = xhci_readl(xhci, port_array[port] + PORTPMSC);
temp &= ~PORT_U2_TIMEOUT_MASK;
temp |= PORT_U2_TIMEOUT(timeout);
xhci_writel(xhci, temp, port_array[port] + PORTPMSC);
break;
}
return 0;
}
/* Interfaces for u1_enable */
static ssize_t ssic_u1_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.u1_enable);
}
static ssize_t ssic_u1_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int request;
if (sscanf(buf, "%d", &request) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
if (!ssic_hcd.modem_dev || !ssic_hcd.rh_dev) {
dev_err(dev, "Modem/roothub is not connected, ignore any request\n");
return -ENODEV;
}
pm_runtime_get_sync(&ssic_hcd.modem_dev->dev);
mutex_lock(&ssic_hcd.ssic_mutex);
if (request) {
dev_dbg(dev, "Enable U1 for SSIC\n");
ssic_set_host_initiated_lpm(dev, USB3_LPM_U1, true);
ssic_set_device_initiated_lpm(ssic_hcd.modem_dev, USB3_LPM_U1, true);
} else {
dev_dbg(dev, "Disable U1 for SSIC\n");
ssic_set_host_initiated_lpm(dev, USB3_LPM_U1, false);
ssic_set_device_initiated_lpm(ssic_hcd.modem_dev, USB3_LPM_U1, false);
}
ssic_hcd.u1_enable = request;
mutex_unlock(&ssic_hcd.ssic_mutex);
pm_runtime_put(&ssic_hcd.modem_dev->dev);
return size;
}
static DEVICE_ATTR(u1_enable, S_IRUGO | S_IWUSR | S_IROTH | S_IWOTH,
ssic_u1_enable_show,
ssic_u1_enable_store);
static ssize_t ssic_u2_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int request;
if (sscanf(buf, "%d", &request) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
if (!ssic_hcd.modem_dev || !ssic_hcd.rh_dev) {
dev_err(dev, "Modem/roothub is not connected, ignore any request\n");
return -ENODEV;
}
pm_runtime_get_sync(&ssic_hcd.modem_dev->dev);
mutex_lock(&ssic_hcd.ssic_mutex);
if (request) {
dev_dbg(dev, "SSIC U2 enabled\n");
ssic_set_host_initiated_lpm(dev, USB3_LPM_U2, true);
ssic_set_device_initiated_lpm(ssic_hcd.modem_dev, USB3_LPM_U2, true);
} else {
dev_dbg(dev, "SSIC U2 disabled\n");
ssic_set_host_initiated_lpm(dev, USB3_LPM_U2, false);
ssic_set_device_initiated_lpm(ssic_hcd.modem_dev, USB3_LPM_U2, false);
}
ssic_hcd.u2_enable = request;
mutex_unlock(&ssic_hcd.ssic_mutex);
pm_runtime_put(&ssic_hcd.modem_dev->dev);
return size;
}
/* Interfaces for u2_enable */
static ssize_t ssic_u2_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.u2_enable);
}
static DEVICE_ATTR(u2_enable, S_IRUGO | S_IWUSR | S_IROTH | S_IWOTH,
ssic_u2_enable_show,
ssic_u2_enable_store);
/* Interfaces for L2 suspend */
static ssize_t ssic_autosuspend_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.autosuspend_enable);
}
static ssize_t ssic_autosuspend_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int request;
if (sscanf(buf, "%d", &request) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
mutex_lock(&ssic_hcd.ssic_mutex);
if (ssic_hcd.modem_dev != NULL) {
if (request == 0) {
dev_dbg(dev, "SSIC: modem autosuspend disable\n");
usb_disable_autosuspend(ssic_hcd.modem_dev);
} else {
dev_dbg(dev, "SSIC: modem dev autosuspend enable\n");
usb_enable_autosuspend(ssic_hcd.modem_dev);
}
}
if (ssic_hcd.rh_dev != NULL) {
if (request == 0) {
dev_dbg(dev, "SSIC: bus autosuspend disable\n");
usb_disable_autosuspend(ssic_hcd.rh_dev);
} else {
dev_dbg(dev, "SSIC: bus autosuspend enable\n");
usb_enable_autosuspend(ssic_hcd.rh_dev);
}
}
ssic_hcd.autosuspend_enable = request;
mutex_unlock(&ssic_hcd.ssic_mutex);
return size;
}
static DEVICE_ATTR(autosuspend_enable, S_IRUGO | S_IWUSR | S_IROTH | S_IWOTH,
ssic_autosuspend_enable_show,
ssic_autosuspend_enable_store);
static ssize_t ssic_bus_inactivity_duration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.bus_inactivity_duration);
}
static ssize_t ssic_bus_inactivity_duration_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
unsigned duration;
if (sscanf(buf, "%d", &duration) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
mutex_lock(&ssic_hcd.ssic_mutex);
if (ssic_hcd.rh_dev != NULL) {
pm_runtime_set_autosuspend_delay
(&ssic_hcd.rh_dev->dev,
duration);
ssic_hcd.bus_inactivity_duration = duration;
dev_dbg(dev, "bus Duration: %d\n",
ssic_hcd.bus_inactivity_duration);
} else {
dev_dbg(dev, "No SSIC root hub, just ignore this request\n");
mutex_unlock(&ssic_hcd.ssic_mutex);
return -ENODEV;
}
mutex_unlock(&ssic_hcd.ssic_mutex);
return size;
}
static DEVICE_ATTR(bus_inactivity_duration,
S_IRUGO | S_IWUSR | S_IROTH | S_IWOTH,
ssic_bus_inactivity_duration_show,
ssic_bus_inactivity_duration_store);
/* group all the SSIC related attributes */
static struct attribute *ssic_attrs[] = {
&dev_attr_ssic_registers.attr,
&dev_attr_autosuspend_enable.attr,
&dev_attr_port_inactivity_duration.attr,
&dev_attr_bus_inactivity_duration.attr,
&dev_attr_u1_inactivity_duration.attr,
&dev_attr_u2_inactivity_duration.attr,
&dev_attr_ssic_enable.attr,
&dev_attr_u1_enable.attr,
&dev_attr_u2_enable.attr,
NULL,
};
/* SSIC PM interface:
* Bit0 - U3 enable/disable
* Bit1 - U2 enable/disable
* Bit2 - U1 enale/disable
*/
static ssize_t ssic_pm_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ssic_hcd.autosuspend_enable | ssic_hcd.u2_enable << 1
| ssic_hcd.u1_enable << 2);
}
static ssize_t ssic_pm_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int rc1, rc2, rc3;
u8 pm_enable;
if (size > HSIC_ENABLE_SIZE)
return -EINVAL;
if (sscanf(buf, "%d", &pm_enable) != 1) {
dev_dbg(dev, "Invalid, value\n");
return -EINVAL;
}
if (pm_enable & 1)
rc1 = ssic_autosuspend_enable_store(dev, attr, "1", size);
else
rc1 = ssic_autosuspend_enable_store(dev, attr, "0", size);
if (pm_enable & 2)
rc2 = ssic_u2_enable_store(dev, attr, "1", size);
else
rc2 = ssic_u2_enable_store(dev, attr, "0", size);
if (pm_enable & 4)
rc3 = ssic_u1_enable_store(dev, attr, "1", size);
else
rc3 = ssic_u1_enable_store(dev, attr, "0", size);
if (rc1 == size && rc2 == size && rc3 == size)
return size;
else
return -EINVAL;
}
static DEVICE_ATTR(pm_enable, S_IRUGO | S_IWUSR | S_IROTH,
ssic_pm_enable_show,
ssic_pm_enable_store);
static struct attribute_group ssic_attr_group = {
.name = ssic_group_name,
.attrs = ssic_attrs,
};
static void ssicdev_add(struct usb_device *udev)
{
if (!is_ssic_host(udev)) {
pr_debug("%s Invalid device add event, ignore\n", __func__);
return;
}
if (udev->speed == USB_SPEED_HIGH) {
dev_dbg(&udev->dev, "USB2 root hub or device, just ignore\n");
return;
}
dev_dbg(&udev->dev, "notify SSIC add device\n");
/* Root hub */
if (!udev->parent) {
if (udev->speed == USB_SPEED_SUPER) {
dev_dbg(&udev->dev, "%s rh device set\n", __func__);
ssic_hcd.rh_dev = udev;
dev_dbg(&udev->dev,
"%s enable roothub autosuspend\n", __func__);
pm_runtime_set_autosuspend_delay(&udev->dev,
ssic_hcd.bus_inactivity_duration);
usb_enable_autosuspend(udev);
/* enable autosuspend before modem coming */
ssic_hcd.autosuspend_enable = 1;
}
} else {
if (udev->portnum != ssic_hcd.ssic_port) {
dev_dbg(&udev->dev, "%s ignore XHCI ports except SSIC port\n",
__func__);
dev_dbg(&udev->dev, "%s ush ports %d\n", __func__,
udev->portnum);
return;
}
/* ssic modem coming now */
if (udev->speed == USB_SPEED_SUPER) {
/* Modem devices */
/* hold wakelock */
ssic_wake_lock();
ssic_hcd.modem_dev = udev;
pm_runtime_set_autosuspend_delay
(&udev->dev, ssic_hcd.port_inactivity_duration);
/* disable modem persist_enable feature */
udev->persist_enabled = 0;
/* ssic should always use In-band remote wakeup */
if (ssic_hcd.remote_wakeup_enable) {
dev_dbg(&udev->dev, "%s Modem dev remote wakeup enabled\n",
__func__);
device_set_wakeup_capable(&ssic_hcd.modem_dev->dev, 1);
device_set_wakeup_capable(&ssic_hcd.rh_dev->dev, 1);
} else {
dev_dbg(&udev->dev, "%s Modem dev remote wakeup disabled\n",
__func__);
device_set_wakeup_capable
(&ssic_hcd.modem_dev->dev, 0);
device_set_wakeup_capable
(&ssic_hcd.rh_dev->dev, 0);
}
ssic_hcd.autosuspend_enable = SSIC_AUTOSUSPEND;
if (ssic_hcd.autosuspend_enable) {
dev_dbg(&udev->dev, "%s enable Modem autosuspend\n",
__func__);
usb_enable_autosuspend(ssic_hcd.modem_dev);
usb_enable_autosuspend(ssic_hcd.rh_dev);
}
if (ssic_hcd.autosuspend_enable == 0) {
dev_dbg(&udev->dev, "%s disable Modem dev autosuspend\n",
__func__);
usb_disable_autosuspend(ssic_hcd.modem_dev);
usb_disable_autosuspend(ssic_hcd.rh_dev);
}
}
}
}
static void ssicdev_remove(struct usb_device *udev)
{
if (!is_ssic_host(udev)) {
pr_debug("%s Invalid device add event, ignore\n", __func__);
return;
}
if (udev->speed == USB_SPEED_HIGH) {
dev_dbg(&udev->dev, "USB2 root hub or device, just ignore\n");
return;
}
dev_dbg(&udev->dev, "Notify SSIC remove device\n");
/* Root hub */
if (!udev->parent) {
if (udev->speed == USB_SPEED_SUPER) {
dev_dbg(&udev->dev,
"%s root hub dev deleted\n", __func__);
mutex_lock(&ssic_hcd.ssic_mutex);
ssic_hcd.rh_dev = NULL;
mutex_unlock(&ssic_hcd.ssic_mutex);
} else {
dev_dbg(&udev->dev, " USB2 root hub remove, ignore\n");
return;
}
} else {
if (udev->portnum != ssic_hcd.ssic_port) {
dev_dbg(&udev->dev, "%s ignore XHCI ports except SSIC\n",
__func__);
dev_dbg(&udev->dev, "%s SSIC ports %d\n", __func__,
udev->portnum);
return;
}
if (udev->speed == USB_SPEED_SUPER) {
/* Modem devices */
dev_dbg(&udev->dev, "%s modem dev deleted\n", __func__);
mutex_lock(&ssic_hcd.ssic_mutex);
ssic_hcd.modem_dev = NULL;
hub_usb3_port_disable(usb_hub_to_struct_hub(ssic_hcd.rh_dev), ssic_hcd.ssic_port);
/* enable autosuspend when modem remove
* sync internal autosuspend_enable value
*/
if (ssic_hcd.rh_dev)
usb_enable_autosuspend(ssic_hcd.rh_dev);
ssic_hcd.autosuspend_enable = 1;
mutex_unlock(&ssic_hcd.ssic_mutex);
}
}
}
/* the root hub will call this callback when device added/removed */
static int ssic_notify(struct notifier_block *self,
unsigned long action, void *dev)
{
switch (action) {
case USB_DEVICE_ADD:
ssicdev_add(dev);
break;
case USB_DEVICE_REMOVE:
ssicdev_remove(dev);
break;
default:
pr_debug("%s Invalid event = %lu, ignore\n",
__func__, action);
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static void ssic_port_suspend(struct usb_device *udev)
{
if (!is_ssic_host(udev)) {
pr_debug("port suspend event not belongs to SSIC\n");
return;
}
if (udev->portnum != ssic_hcd.ssic_port) {
dev_dbg(&udev->dev, "%s ignore ush ports %d\n",
__func__, udev->portnum);
return;
}
/* Modem dev */
if ((udev->parent)) {
dev_dbg(&udev->dev, "%s s3 wlock unlocked\n", __func__);
ssic_wake_unlock();
}
}
static void ssic_port_resume(struct usb_device *udev)
{
if (!is_ssic_host(udev)) {
pr_debug("port_resume event not belongs to SSIC\n");
return;
}
if (udev->portnum != ssic_hcd.ssic_port) {
dev_dbg(&udev->dev, "%s ignore ush ports %d\n",
__func__, udev->portnum);
return;
}
/* Modem dev */
if ((udev->parent) && (ssic_hcd.power_state != POWER_SUSPENDING)) {
dev_dbg(&udev->dev, "%s s3 wlock locked\n", __func__);
ssic_wake_lock();
}
}
static int ssic_pm_notify(struct notifier_block *self,
unsigned long action, void *dev)
{
switch (action) {
case USB_PORT_SUSPEND:
ssic_port_suspend(dev);
break;
case USB_PORT_RESUME:
ssic_port_resume(dev);
break;
default:
pr_debug("%s Invalid event = %lu, ignore\n",
__func__, action);
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static int ssic_power_notify(struct notifier_block *self,
unsigned long action, void *dummy)
{
switch (action) {
case PM_SUSPEND_PREPARE:
ssic_hcd.power_state = POWER_SUSPENDING;
break;
default:
pr_debug("%s Invalid event = %lu, ignore\n",
__func__, action);
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static int xhci_ssic_disable_feature(struct xhci_hcd *xhci)
{
u32 temp;
/* disable SSIC port1 for ANN and CHT as port1 never used */
temp = xhci_readl(xhci, (&ssic_hcd.policy_regs->config_reg2 + 11));
xhci_dbg(xhci, "CONFIG register2 for Port1 before write = 0x%X\n", temp);
temp |= SSIC_PORT_UNUSED;
xhci_writel(xhci, temp, (&ssic_hcd.policy_regs->config_reg2 + 11));
xhci_dbg(xhci, "CONFIG register2 for Port1 after write = 0x%X\n",
xhci_readl(xhci,
(&ssic_hcd.policy_regs->config_reg2 + 11)));
/* disable STALL in U0 in config register 3 for port 0 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg3);
xhci_dbg(xhci, "CONFIG register3 before write = 0x%X\n", temp);
temp |= DISABLE_U0_STALL;
temp &= ~LUP_LDN_TIMER_MAX;
temp &= ~(1<<20);
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg3);
xhci_dbg(xhci, "CONFIG register3 after write = 0x%X\n",
xhci_readl(xhci,
&ssic_hcd.policy_regs->config_reg3));
/* disable scrambling in config register 2 for port 0 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "CONFIG register2 before write = 0x%X\n", temp);
temp |= DISABLE_SCRAMBLING;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "CONFIG register2 after write = 0x%X\n",
xhci_readl(xhci,
&ssic_hcd.policy_regs->config_reg2));
return 0;
}
struct mphy_attr_setting ssic_register_bank[] = {
{LOCAL_PHY, ATTRID_TX_HSRATE_SERIES, ATTR_VAL_HSMODE_RATE_SERIES_A}, /* 0x22, 0x01 */
{LOCAL_PHY, ATTRID_RX_HSRATE_SERIES, ATTR_VAL_HSMODE_RATE_SERIES_A}, /* 0xA2, 0x01 */
{LOCAL_PHY, ATTRID_TX_HS_SYNC_LENGTH, ATTR_VAL_SYNC_LENGTH(5) | ATTR_VAL_SYNC_RANGE(1)}, /* 0x28, 0x45*/
{LOCAL_PHY, ATTRID_TX_PWM_BURST_CLOSURE_EXTENDSION, ATTR_VAL_BURST_CLOSURE_SEQ_DURATION(0x14)}, /* 0x2D, 0x14 */
/* All modem side registers definition are for reference only.
* Because there have no formal definition document released by modem team so far.
* Below definition are refer to MIPI spec. But maybe intel modify them for other usage.
* for example: 0xD7 ATTR in MIPI spec, only bit 0 is valid. But in here, we set it as 0x39.
*/
{REMOTE_PHY, ATTRID_MODEM_CB_REG_A38, 0x18}, /* 0xA38, 0x18 */
{REMOTE_PHY, ATTRID_MODEM_CB_REG_A39, 0x30}, /* 0xA39, 0x30 */
{REMOTE_PHY, ATTRID_MODEM_CB_UPDATE_40A, 0x01}, /* 0x40A, 0x01 */
{REMOTE_PHY, ATTRID_MODEM_CUSTOM_REG_CD, 0x88}, /* 0xCD, 0x88 */
{REMOTE_PHY, ATTRID_MC_RX_LA_CAPABILITY, 0x39}, /* 0xD7, 0x39 */
{REMOTE_PHY, ATTRID_MC_HS_START_TIME_VAR_CAPABILITY, 0x19}, /* 0xD4, 0x19 */
{REMOTE_PHY, ATTRID_MODEM_CUSTOM_REG_C5, 0x07}, /* 0xC5, 0x07 */
{REMOTE_PHY, ATTRID_RX_TERMINATION_FORCE_ENABLE, ATTR_VAL_ENABLE_RDIF_RX}, /* 0xA9, 0x01 */
{REMOTE_PHY, ATTRID_TX_HS_SYNC_LENGTH, ATTR_VAL_SYNC_LENGTH(6) | ATTR_VAL_SYNC_RANGE(1)}, /* 0x28, 0x46 */
{REMOTE_PHY, ATTRID_TX_HS_PREPARE_LENGTH, ATTR_VAL_M_TX_LENGTH_MULTIPLIER(6)}, /* 0x29, 0x06 */
{REMOTE_PHY, ATTRID_DISABLE_SCRAMBLING, ATTR_VAL_DISABLE_HSMODE_SCRAMBLING}, /* 0x403, 0x01 */
{REMOTE_PHY, ATTRID_DISABLE_STALL_IN_U0, ATTR_VAL_DISABLE_USP_STALL_IN_U0}, /* 0x404, 0x01 */
{REMOTE_PHY, ATTRID_MODEM_CUSTOM_REG_F3, 0x21}, /* 0xF3, 0x21 */
};
static int ssic_config_register_bank(struct xhci_hcd *xhci, struct mphy_attr_setting *settings, int size)
{
u32 temp = 0, i;
struct mphy_attr_setting index;
if (!xhci || !settings)
return -EINVAL;
temp = xhci_readl(xhci, &ssic_hcd.profile_regs->access_control);
xhci_dbg(xhci, "SSIC: access_control = 0x%X, address = %p\n",
temp, (&ssic_hcd.profile_regs->access_control));
/* set Register Bank is valid to indicate to Controller that register bank
* will be configured with commands that should be automatically issued.
*/
temp |= REGISTER_BANK_VALID;
/* set HS_CONFIG to 1 to indicate Controller should automatically issue
* CONFIGURE_FOR_HS RRAP command when finished issuing the RRAP commands
* in register bank.
*/
temp |= HS_CONFIG;
/* Upon detecting Command Phase Done in the control register (and executing
* Config for HS if HS Config flag is set), the host controller starts
* executing commands from the register bank if Register Bank Valid is
* set to ‘1’.*/
temp |= COMMAND_PHASE_DONE;
xhci_writel(xhci, temp, &ssic_hcd.profile_regs->access_control);
xhci_dbg(xhci, "access_control after write = 0x%X\n",
xhci_readl(xhci, &ssic_hcd.profile_regs->access_control));
for (i = 0; i < size; i++) {
index = settings[i];
temp = 0;
/* Attribute ID[27:16] */
temp |= ATTRIBUTE_ID(index.attr_id);
/* Target PHY[14] */
if (index.target == LOCAL_PHY)
temp |= TARGET_PHY;
/* Attribute Value[7:0] */
temp |= index.val;
/* Write Valid[15] to 1*/
temp |= ATTRIBUTE_VALID;
xhci_dbg(xhci, "%s: set %s attr 0x%x with value 0x%x\n", __func__,
temp & (1 << 14) ? "LOCAL PHY" : "REMOTE PHY", (temp >> 16) & 0xFFF,
temp & 0xFF);
xhci_writel(xhci, temp, &ssic_hcd.profile_regs->attribute_base + i);
}
return 0;
}
static int ann_config_register(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci;
u32 temp;
if (!hcd) {
pr_err("%s hcd is NULL, return -EINVAL\n", __func__);
return -EINVAL;
}
xhci = hcd_to_xhci(hcd);
if (!xhci) {
pr_err("%s xhci is NULL\n", __func__);
return -ENODEV;
}
if (hcd->regs) {
/* XHCC1 Write Value : 0x3401FD */
temp = xhci_readl(xhci, hcd->regs + 0x8640);
xhci_dbg(xhci, "XHCC1(0x8640) read value is = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8640));
temp |= (0x1 << 18);
temp |= (0x3 << 20);
xhci_writel(xhci, temp, hcd->regs + 0x8640);
xhci_dbg(xhci, "XHCC1(0x8640) write value is = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8640));
/* XHCC2 Write Value : 0x3CFC68F */
temp = xhci_readl(xhci, hcd->regs + 0x8644);
xhci_dbg(xhci, "XHCC2(0x8644) = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8644));
temp |= (0x1 << 25);
temp |= (0x7 << 22);
temp |= (0x3F << 14);
temp &= ~(0x1 << 11);
temp |= (0x1 << 10);
temp |= (0x2 << 8);
temp |= (0x2 << 6);
temp |= (0x1 << 3);
temp |= (0x7 << 0);
xhci_writel(xhci, temp, hcd->regs + 0x8644);
xhci_dbg(xhci, "XHCC2 after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8644));
/* XHCLKGTEN Write Value : 0xFBF6D3F */
temp = xhci_readl(xhci, hcd->regs + 0x8650);
temp |= (0x1 << 27);
temp |= (0x1 << 26);
temp |= (0x1 << 25);
temp |= (0x1 << 24);
temp |= (0xb << 20);
temp |= (0xF << 16);
temp &= ~(0x1 << 15);
temp |= (0x1 << 14);
temp |= (0x1 << 13);
temp |= (0x3 << 10);
temp |= (0x1 << 8);
temp |= (0x1 << 5);
temp |= (0x1 << 4);
temp |= (0x1 << 3);
temp |= (0x1 << 2);
temp |= (0x1 << 1);
temp |= (0x1 << 0);
xhci_writel(xhci, temp, hcd->regs + 0x8650);
xhci_dbg(xhci, "XHCLKGTEN(0x8650) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8650));
/* PCE 9C 0x40000 */
temp = xhci_readl(xhci, hcd->regs + 0x869C);
temp &= ~(0x1 << 21);
temp &= ~(0x1 << 19);
temp |= (0x1 << 18);
temp &= ~(0x1 << 17);
temp &= ~(0x1 << 16);
xhci_writel(xhci, temp, hcd->regs + 0x869C);
xhci_dbg(xhci, "PCE(0x869C) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x869C));
/* HSCFG2 0x3800 */
temp = xhci_readl(xhci, hcd->regs + 0x86A4);
xhci_dbg(xhci, "HSCFG2 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x86A4));
temp |= (0x3 << 11);
xhci_writel(xhci, temp, hcd->regs + 0x86A4);
xhci_dbg(xhci, "HSCFG2(0x86A4) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x86A4));
/* SSCFG1 0x200CF */
temp = xhci_readl(xhci, hcd->regs + 0x86A8);
xhci_dbg(xhci, "SSCFG1 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x86A8));
temp |= (0x1 << 17);
temp &= ~(0x1 << 14);
xhci_writel(xhci, temp, hcd->regs + 0x86A8);
xhci_dbg(xhci, "SSCFG1(0x86A8) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x86A8));
/* U2DEL and U1DEL 0x20000A */
temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params3);
xhci_dbg(xhci, "HCSPARAM3 before Write = 0x%X\n", temp);
temp &= ~(0x1 << 0);
temp &= ~(0x1 << 18);
temp |= (0x200 << 16);
temp |= (0xA);
xhci_writel(xhci, temp, &xhci->cap_regs->hcs_params3);
xhci_dbg(xhci, "HCSPARAM3 after Write = 0x%X\n",
xhci_readl(xhci, &xhci->cap_regs->hcs_params3));
/* XECP_CMDM_CTRL_REG1 0x3511AEFC */
temp = xhci_readl(xhci, hcd->regs + 0x818C);
xhci_dbg(xhci, "XECP_CMDM_CTRL_REG1 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x818C));
temp |= (0x1 << 20);
temp |= (0x1 << 16);
temp &= ~(0x1 << 8);
xhci_writel(xhci, temp, hcd->regs + 0x818C);
xhci_dbg(xhci, "XECP_CMDM_CTRL_REG1(0x818C) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x818C));
/* XECP_CMDM_CTRL_REG3 0x220505A */
temp = xhci_readl(xhci, hcd->regs + 0x8194);
xhci_dbg(xhci, "XECP_CMDM_CTRL_REG3 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8194));
temp |= (0x1 << 25);
xhci_writel(xhci, temp, hcd->regs + 0x8194);
xhci_dbg(xhci, "XECP_CMDM_CTRL_REG3(0x8194) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8194));
/* PMCTRL 0x1C1FF94 */
temp = xhci_readl(xhci, hcd->regs + 0x80A4);
xhci_dbg(xhci, "PMCTRL before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x80A4));
temp &= ~(0x1 << 31);
temp &= ~(0x1 << 29);
temp |= (0x1 << 24);
temp |= (0x1 << 23);
temp |= (0x1 << 22);
temp |= (0x1 << 2);
xhci_writel(xhci, temp, hcd->regs + 0x80A4);
xhci_dbg(xhci, "PMCTRL(0x80A4) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x80A4));
/* AUX_CTRL_REG1 0x80CCBCE0 */
temp = xhci_readl(xhci, hcd->regs + 0x80E0);
xhci_dbg(xhci, "AUX_CTRL_REG1 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x80E0));
temp |= (0x1 << 22);
temp &= ~(0x1 << 16);
temp &= ~(0x1 << 9);
temp |= (0x1 << 6);
xhci_writel(xhci, temp, hcd->regs + 0x80E0);
xhci_dbg(xhci, "AUX_CTRL_REG1(0x80E0) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x80E0));
/* HOST_CTRL_SCH_REG 0xA0C100 */
temp = xhci_readl(xhci, hcd->regs + 0x8094);
xhci_dbg(xhci, "HOST_CTRL_SCH_REG before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8094));
temp |= (0x1 << 23);
temp |= (0x1 << 21);
temp |= (0x1 << 14);
xhci_writel(xhci, temp, hcd->regs + 0x8094);
xhci_dbg(xhci, "HOST_CTRL_SCH_REG(0x8094) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8094));
/* HOST_CTRL_TRM_REG2 0xF0FC8B88 */
temp = xhci_readl(xhci, hcd->regs + 0x8110);
xhci_dbg(xhci, "HOST_CTRL_TRM_REG2 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8110));
temp |= (0x1 << 20);
temp |= (0x1 << 11);
temp &= ~(0x1 << 2);
xhci_writel(xhci, temp, hcd->regs + 0x8110);
xhci_dbg(xhci, "HOST_CTRL_TRM_REG2(0x8110) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8110));
/* AUX_CTRL_REG2 0x81192206 */
temp = xhci_readl(xhci, hcd->regs + 0x8154);
xhci_dbg(xhci, "AUX_CTRL_REG2 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8154));
temp |= (0x1 << 31);
temp &= ~(0x1 << 21);
temp |= (0x1 << 13);
xhci_writel(xhci, temp, hcd->regs + 0x8154);
xhci_dbg(xhci, "AUX_CTRL_REG2 after Write(0x8154) = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8154));
/* AUX_CLOCK_CONTROL 0xC403C */
temp = xhci_readl(xhci, hcd->regs + 0x816C);
xhci_dbg(xhci, "AUX_CLOCK_CTRL before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x816C));
temp |= (0x1 << 19);
temp |= (0x1 << 18);
temp |= (0x1 << 14);
temp &= ~(0x3 << 12);
temp &= ~(0xF << 8);
temp |= (0x1 << 5);
temp |= (0x1 << 4);
temp |= (0x1 << 3);
temp |= (0x1 << 2);
xhci_writel(xhci, temp, hcd->regs + 0x816C);
xhci_dbg(xhci, "AUX_CLOCK_CTRL after(0x816C) Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x816C));
/* IF_PWR_CTRL_REG0 0xFF03C132 */
temp = xhci_readl(xhci, hcd->regs + 0x8140);
xhci_dbg(xhci, "IF_PWR_CTRL_REG0 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8140));
temp |= (0xFF << 24);
temp &= ~(0x1 << 12);
temp &= ~(0x1 << 15);
temp &= ~(0x1 << 16);
temp |= (0x3C << 12);
temp |= (0x132 << 0);
xhci_writel(xhci, temp, hcd->regs + 0x8140);
xhci_dbg(xhci, "IF_PWR_CTRL_REG0(0x8140) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8140));
/* IF_PWR_CTRL_REG1 0x33F */
temp = xhci_readl(xhci, hcd->regs + 0x8144);
xhci_dbg(xhci, "IF_PWR_CTRL_REG1 before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8144));
temp |= (0x1 << 8);
temp &= ~(0x1 << 7);
temp &= ~(0x1 << 6);
xhci_writel(xhci, temp, hcd->regs + 0x8144);
xhci_dbg(xhci, "IF_PWR_CTRL_REG1(0x8144) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8144));
/* Latency Tolerance 0x40047D */
temp = xhci_readl(xhci, hcd->regs + 0x8174);
xhci_dbg(xhci, "Latency Tolerance before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8174));
temp &= ~(0x1 << 24);
xhci_writel(xhci, temp, hcd->regs + 0x8174);
xhci_dbg(xhci, "Latency Tolerance(0x8174) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x8174));
/* MISC REG 0x101037F */
temp = xhci_readl(xhci, hcd->regs + 0x80B0);
xhci_err(xhci, "MISC REG before Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x80B0));
temp |= (0x1 << 24);
xhci_writel(xhci, temp, hcd->regs + 0x80B0);
xhci_err(xhci, "MISC REG(0x80B0) after Write = 0x%X\n",
xhci_readl(xhci, hcd->regs + 0x80B0));
}
return 0;
}
/* xhci_ssic_init: Controller initialization flow for config local
* and remote MPHY via MMIO registers.
*/
static int xhci_ssic_init(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci;
u32 temp;
struct pci_dev *pdev;
int retval;
if (!hcd) {
pr_err("%s hcd is NULL, return -EINVAL\n", __func__);
return -EINVAL;
}
xhci = hcd_to_xhci(hcd);
if (!xhci) {
pr_err("%s xhci is NULL\n", __func__);
return -ENODEV;
}
pdev = to_pci_dev(hcd->self.controller);
if (!pdev) {
pr_err("%s pdev is NULL, return\n", __func__);
return -ENODEV;
}
/* ANN only MMIO register set config */
if (pdev->vendor == PCI_VENDOR_ID_INTEL && pdev->device == PCI_DEVICE_ID_INTEL_MOOR_SSIC) {
xhci_dbg(xhci, "ANN SSIC Controller, need to config MMIO register set\n");
retval = ann_config_register(hcd);
if (retval < 0) {
xhci_dbg(xhci, "ANN config register return %d\n", retval);
return retval;
}
}
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, " Config Register2 after boot up = 0x%08X\n", temp);
/* Set the U3 workaround in driver */
temp |= 0xF << 21;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "config register2 after write U3 workaround = 0x%X\n", temp);
/* Configure Local/Remote MPHY. */
ssic_config_register_bank(xhci, ssic_register_bank, ARRAY_SIZE(ssic_register_bank));
xhci_ssic_disable_feature(xhci);
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, " Config Register2 = 0x%08X\n", temp);
/*WA: Set T_ACT_H8_EXIT = 0x7 to fix U3 exit failure */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg1);
temp |= 0x7 << 8;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg1);
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg1);
xhci_dbg(xhci, "Config Regsiter1 = 0x%08X\n", temp);
return 0;
}
static int xhci_ipc_get_ports(struct usb_hcd *hcd,
__le32 __iomem ***port_array)
{
int max_ports;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
if (hcd->speed == HCD_USB3) {
max_ports = xhci->num_usb3_ports;
*port_array = xhci->usb3_ports;
} else {
max_ports = xhci->num_usb2_ports;
*port_array = xhci->usb2_ports;
}
return max_ports;
}
static int xhci_ipc_hub_control(struct usb_hcd *hcd,
u16 type_request, u16 type_value,
u16 type_index, char *buf, u16 type_length)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
int max_ports;
unsigned long flags;
u32 temp, value;
u16 index;
int retval = 0;
__le32 __iomem **port_array;
if (hsic_pdata->has_ssic) {
max_ports = xhci_ipc_get_ports(hcd, &port_array);
/**
* WorkAround: sighting HSD 5015749 for ANN/CHT A0
* Controller stuck in USP disconnect state due to
* no pwr ack from SSIC MPHY.
* Details: After USP disconnect and Controller sends
* PORTSC event(CSC) event with CCS == 0
* Driver need to do following steps:
* 1) Set DL_PWR_GATE_DIS bit
* 2) Wait for 5us
* 3) Clear DL_PWR_GATE_DIS bit
*/
xhci_dbg(xhci, "max_ports = %d\n", max_ports);
spin_lock_irqsave(&xhci->lock, flags);
index = type_index;
if (type_request == SSIC_GET_PORT_STATUS) {
if (!index || index > max_ports) {
xhci_err(xhci, "wIndex == 0 or wIndex > max_ports\n");
retval = -EPIPE;
goto error;
}
index--;
/* 1) Must be careful about the index match for SSIC
* 2) Do NOT do any modification of type_index, otherwise
* driver will stuck.
*/
if (type_index == ssic_hcd.ssic_port) {
temp = xhci_readl(xhci, port_array[index]);
if (temp == 0xffffffff) {
retval = -ENODEV;
goto error;
}
xhci_dbg(xhci, "get port status, actual port %d status = 0x%x\n",
index, temp);
/* CSC == 1 && CCS == 0 */
if ((temp & PORT_CSC) && !(temp & PORT_CONNECT)) {
/* set the DL_PWR_GATE_DIS_BIT */
value = xhci_readl(xhci,
&ssic_hcd.policy_regs->config_reg3);
xhci_dbg(xhci, "config reg3 = 0x%08X\n",
value);
xhci_writel(xhci, value | DL_PWR_GATE_DIS,
&ssic_hcd.policy_regs->config_reg3);
xhci_dbg(xhci,
"config reg3 after write = 0x%08X\n",
value);
/* FIXME OR use usleep is also enough? */
udelay(5);
value &= ~DL_PWR_GATE_DIS;
xhci_writel(xhci, value,
&ssic_hcd.policy_regs->config_reg3);
}
}
}
spin_unlock_irqrestore(&xhci->lock, flags);
}
retval = xhci_hub_control(hcd, type_request,
type_value, type_index,
buf, type_length);
return retval;
error:
spin_unlock_irqrestore(&xhci->lock, flags);
return retval;
}
static int create_ssic_class_device_files(struct pci_dev *pdev)
{
int retval;
ssic_class = class_create(NULL, "ssic");
if (IS_ERR(ssic_class))
return -EFAULT;
ssic_class_dev = device_create(ssic_class, &pdev->dev,
MKDEV(0, 0), NULL, "ssic0");
if (IS_ERR(ssic_class_dev)) {
retval = -EFAULT;
goto ssic_class_fail;
}
retval = device_create_file(ssic_class_dev, &dev_attr_ssic_registers);
if (retval < 0) {
dev_dbg(&pdev->dev, "error create ssic_register\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev, &dev_attr_ssic_enable);
if (retval < 0) {
dev_dbg(&pdev->dev, "error create ssic_enable\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev,
&dev_attr_autosuspend_enable);
if (retval < 0) {
dev_dbg(&pdev->dev, "Error create autosuspend_enable\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev,
&dev_attr_port_inactivity_duration);
if (retval < 0) {
dev_dbg(&pdev->dev, "Error create port_inactiveDuration\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev,
&dev_attr_bus_inactivity_duration);
if (retval < 0) {
dev_dbg(&pdev->dev, "Error create bus_inactiveDuration\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev,
&dev_attr_u1_inactivity_duration);
if (retval < 0) {
dev_dbg(&pdev->dev, "Error create u1_inactiveDuration\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev,
&dev_attr_u2_inactivity_duration);
if (retval < 0) {
dev_dbg(&pdev->dev, "Error create u2_inactiveDuration\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev,
&dev_attr_u1_enable);
if (retval < 0) {
dev_dbg(&pdev->dev, "Error create u1_enable\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev,
&dev_attr_u2_enable);
if (retval < 0) {
dev_dbg(&pdev->dev, "Error create u2_enable\n");
goto ssic_class_dev_fail;
}
retval = device_create_file(ssic_class_dev,
&dev_attr_pm_enable);
if (retval < 0) {
dev_dbg(&pdev->dev, "Error create pm_enable\n");
goto ssic_class_dev_fail;
}
if (retval == 0)
return retval;
ssic_class_dev_fail:
device_destroy(ssic_class, ssic_class_dev->devt);
ssic_class_fail:
class_destroy(ssic_class);
return retval;
}
static void remove_ssic_class_device_files(void)
{
device_destroy(ssic_class, ssic_class_dev->devt);
class_destroy(ssic_class);
}
static int xhci_ssic_private_reset(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci;
u32 temp;
int i;
struct pci_dev *pdev;
if (!hcd) {
pr_err("%s hcd is NULL, return -EINVAL\n",
__func__);
return -EINVAL;
}
xhci = hcd_to_xhci(hcd);
if (!xhci) {
pr_err("%s xhci is NULL\n", __func__);
return -EINVAL;
}
pdev = to_pci_dev(hcd->self.controller);
if (!pdev) {
pr_err("%s pdev is NULL, return\n", __func__);
return -ENODEV;
}
if (pdev->vendor == PCI_VENDOR_ID_INTEL && pdev->device == PCI_DEVICE_ID_INTEL_MOOR_SSIC) {
if (!hsic_pdata) {
pr_err("%s hsic_pdata is NULL, return\n", __func__);
return -ENODEV;
}
if (hsic_pdata->has_ssic) {
if (ssic_hcd.first_reset == 0) {
ssic_hcd.policy_regs = hcd->regs + SSIC_POLICY_BASE;
ssic_hcd.profile_regs = hcd->regs + SSIC_LOCAL_REMOTE_PROFILE_REGISTER;
ssic_hcd.first_reset = 1;
/* disable SSIC port1 for ANN */
temp = xhci_readl(xhci, (&ssic_hcd.policy_regs->config_reg2 + 12));
xhci_dbg(xhci, "CONFIG register2 for Port1 before write = 0x%X\n", temp);
temp |= SSIC_PORT_UNUSED;
xhci_writel(xhci, temp,
(&ssic_hcd.policy_regs->config_reg2 + 12));
xhci_dbg(xhci, "CONFIG register2 for Port1 after write = 0x%X\n",
xhci_readl(xhci,
(&ssic_hcd.policy_regs->config_reg2 + 12)));
/* Config SSIC Configuration Register2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "Config register2 = %X\n", temp);
/* disable SSIC port0 for ANN for the first time bring-up */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2);
temp |= SSIC_PORT_UNUSED;
xhci_writel(xhci, temp, &ssic_hcd.policy_regs->config_reg2);
xhci_dbg(xhci, "CONFIG register2 for Port0 after write = 0x%X\n",
xhci_readl(xhci, (&ssic_hcd.policy_regs->config_reg2)));
}
/* do flow before do HCRST every time */
for (i = 0; i < 2; i++) {
/* read the config register 2 */
temp = xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2 + 12 * i);
if (temp & SSIC_PORT_UNUSED) {
temp &= ~PROG_DONE;
xhci_dbg(xhci, "Clear PROG_DONE for port %d\n", i);
xhci_writel(xhci, temp, (&ssic_hcd.policy_regs->config_reg2 + 12 * i));
xhci_dbg(xhci, "CONFIG register2 after clear PROG_DONE = 0x%X\n",
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2 + 12 * i));
temp &= ~SSIC_PORT_UNUSED;
xhci_writel(xhci, temp, (&ssic_hcd.policy_regs->config_reg2 + 12 * i));
xhci_dbg(xhci, "CONFIG register2 after clear PORT_UNUSED = 0x%X\n",
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2 + 12 * i));
temp |= PROG_DONE;
xhci_dbg(xhci, "Set PROG_DONE for port %d\n", i);
xhci_writel(xhci, temp, (&ssic_hcd.policy_regs->config_reg2 + 12 * i));
xhci_dbg(xhci, "CONFIG register2 after Set PROG_DONE = 0x%X\n",
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2 + 12 * i));
xhci_dbg(xhci, "Begin to do msleep 100 ms\n");
msleep(100);
xhci_dbg(xhci, "End of msleep 100 ms\n");
temp &= ~PROG_DONE;
xhci_dbg(xhci, "Clear PROG_DONE for port %d\n", i);
xhci_writel(xhci, temp, (&ssic_hcd.policy_regs->config_reg2 + 12 * i));
xhci_dbg(xhci, "CONFIG register2 after clear PROG_DONE = 0x%X\n",
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2 + 12 * i));
temp |= SSIC_PORT_UNUSED;
xhci_writel(xhci, temp, (&ssic_hcd.policy_regs->config_reg2 + 12 * i));
xhci_dbg(xhci, "CONFIG register2 after set PORT_UNUSED = 0x%X\n",
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2 + 12 * i));
temp |= PROG_DONE;
xhci_dbg(xhci, "Set PROG_DONE for port %d\n", i);
xhci_writel(xhci, temp, (&ssic_hcd.policy_regs->config_reg2 + 12 * i));
xhci_dbg(xhci, "CONFIG register2 after Set PROG_DONE = 0x%X\n",
xhci_readl(xhci, &ssic_hcd.policy_regs->config_reg2 + 12 * i));
}
}
}
}
return 0;
}