SmartAudio/lichee/linux-4.9/drivers/usb/host/xhci_sunxi.c

803 lines
20 KiB
C
Raw Normal View History

2018-07-13 01:31:50 +00:00
/*
* drivers/usb/host/xhci_sunxi.c
* (C) Copyright 2010-2015
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* wangjx, 2016-9-9, create this file
*
* SoftWinner XHCI Driver
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
*/
#include <linux/platform_device.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/clk.h>
#include "sunxi_hci.h"
#include "xhci.h"
#ifdef CONFIG_PM
static void sunxi_xhci_resume_work(struct work_struct *work);
#endif
#define SUNXI_XHCI_NAME "sunxi-xhci"
static const char xhci_name[] = SUNXI_XHCI_NAME;
#define SUNXI_ALIGN_MASK (16 - 1)
#ifdef CONFIG_USB_SUNXI_XHCI
#define SUNXI_XHCI_OF_MATCH "allwinner,sunxi-xhci"
#else
#define SUNXI_XHCI_OF_MATCH "NULL"
#endif
static void sunxi_xhci_open_clock(struct sunxi_hci_hcd *sunxi_xhci);
static void sunxi_set_mode(struct sunxi_hci_hcd *sunxi_xhci, u32 mode);
static void sunxi_core_soft_reset(void __iomem *regs);
static int sunxi_core_open_phy(void __iomem *regs);
static struct sunxi_hci_hcd *g_sunxi_xhci;
static struct sunxi_hci_hcd *g_dev_data;
static ssize_t xhci_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
if (dev == NULL) {
DMSG_PANIC("Argment is invalid\n");
return 0;
}
sunxi_xhci = dev->platform_data;
if (sunxi_xhci == NULL) {
DMSG_PANIC("sunxi_xhci is null\n");
return 0;
}
return sprintf(buf, "xhci:%d, probe:%u\n",
sunxi_xhci->usbc_no, sunxi_xhci->probe);
}
static ssize_t xhci_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
int value = 0;
int ret = 0;
if (dev == NULL) {
DMSG_PANIC("Argment is invalid\n");
return count;
}
sunxi_xhci = dev->platform_data;
if (sunxi_xhci == NULL) {
DMSG_PANIC("sunxi_xhci is null\n");
return count;
}
sunxi_xhci->host_init_state = 0;
ret = kstrtoint(buf, 10, &value);
if (ret != 0)
return -EINVAL;
if (value == 1)
sunxi_usb_enable_xhci();
else if (value == 0)
sunxi_usb_disable_xhci();
else
DMSG_INFO("unknown value (%d)\n", value);
return count;
}
static DEVICE_ATTR(xhci_enable, 0664, xhci_enable_show, xhci_enable_store);
static int xhci_host2_test_mode(void __iomem *regs, int param)
{
int reg_value = 0;
switch (param) {
case TEST_J:
DMSG_INFO("xhci_host2_test_mode: TEST_J\n");
break;
case TEST_K:
DMSG_INFO("xhci_host2_test_mode: TEST_K\n");
break;
case TEST_SE0_NAK:
DMSG_INFO("xhci_host2_test_mode: TEST_SE0_NAK\n");
break;
case TEST_PACKET:
DMSG_INFO("xhci_host2_test_mode: TEST_PACKET\n");
break;
case TEST_FORCE_EN:
DMSG_INFO("xhci_host2_test_mode: TEST_FORCE_EN\n");
break;
default:
DMSG_INFO("not support test mode(%d)\n", param);
return -1;
}
reg_value = USBC_Readl(regs + XHCI_OP_REGS_HCPORT1SC);
reg_value &= ~(0x1 << 9);
USBC_Writel(reg_value, regs + XHCI_OP_REGS_HCPORT1SC);
msleep(20);
reg_value = USBC_Readl(regs + XHCI_OP_REGS_HCUSBCMD);
reg_value &= ~(0x1 << 0);
USBC_Writel(reg_value, regs + XHCI_OP_REGS_HCUSBCMD);
msleep(20);
reg_value = USBC_Readl(regs + XHCI_OP_REGS_HCUSBSTS);
reg_value &= ~(0x1 << 0);
USBC_Writel(reg_value, regs + XHCI_OP_REGS_HCUSBSTS);
msleep(20);
DMSG_INFO("Halted: 0x%x, param: %d\n",
USBC_Readl(regs + XHCI_OP_REGS_HCUSBSTS), param);
reg_value = USBC_Readl(regs + XHCI_OP_REGS_HCPORT1PMSC);
reg_value |= (param << 28);
USBC_Writel(reg_value, regs + XHCI_OP_REGS_HCPORT1PMSC);
msleep(20);
DMSG_INFO("test_code: %x\n",
USBC_Readl(regs + XHCI_OP_REGS_HCPORT1PMSC));
return 0;
}
static ssize_t ed_test_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
struct sunxi_hci_hcd *dev_data = NULL;
if (dev == NULL) {
DMSG_PANIC("Argment is invalid\n");
return 0;
}
sunxi_xhci = g_sunxi_xhci;
dev_data = g_dev_data;
if (sunxi_xhci == NULL) {
DMSG_PANIC("sunxi_xhci is null\n");
return 0;
}
return sprintf(buf, "USB2.0 host test mode:\n"
"echo:\ntest_j_state\ntest_k_state\ntest_se0_nak\n"
"test_pack\ntest_force_enable\n\n"
"USB3.0 host test mode:\n"
"echo:\ntest_host3\n");
}
static ssize_t ed_test_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
struct sunxi_hci_hcd *dev_data = NULL;
struct usb_hcd *hcd = NULL;
u32 testmode = 0;
if (dev == NULL) {
DMSG_PANIC("Argment is invalid\n");
return count;
}
sunxi_xhci = g_sunxi_xhci;
dev_data = g_dev_data;
if (sunxi_xhci == NULL) {
DMSG_PANIC("sunxi_xhci is null\n");
return count;
}
if (dev_data->probe == 0) {
DMSG_INFO("[%s]: is disable, can not enter test mode\n",
dev_data->hci_name);
return count;
}
hcd = dev_get_drvdata(&sunxi_xhci->pdev->dev);
if (hcd == NULL) {
DMSG_PANIC("xhci hcd is null\n");
return count;
}
/* USB3.0 test mode */
if (!strncmp(buf, "test_host3", 10)) {
DMSG_INFO("xhci usb3.0 host test mode\n");
sunxi_usb_disable_xhci();
sunxi_xhci_open_clock(dev_data);
sunxi_core_open_phy(sunxi_xhci->regs);
sunxi_core_soft_reset(sunxi_xhci->regs);
sunxi_set_mode(sunxi_xhci, SUNXI_GCTL_PRTCAP_HOST);
return count;
}
/* USB2.0 test mode */
if (!strncmp(buf, "test_j_state", 12))
testmode = TEST_J;
else if (!strncmp(buf, "test_k_state", 12))
testmode = TEST_K;
else if (!strncmp(buf, "test_se0_nak", 12))
testmode = TEST_SE0_NAK;
else if (!strncmp(buf, "test_pack", 9))
testmode = TEST_PACKET;
else if (!strncmp(buf, "test_force_enable", 17))
testmode = TEST_FORCE_EN;
else
testmode = 0;
xhci_host2_test_mode(hcd->regs, testmode);
return count;
}
static DEVICE_ATTR(ed_test, 0664, ed_test_show, ed_test_store);
/**
* sunxi_core_soft_reset - Issues core soft reset and PHY reset
* @sunxi_xhci: pointer to our context structure
*/
static void sunxi_core_soft_reset(void __iomem *regs)
{
int reg = 0;
/* Before Resetting PHY, put Core in Reset */
reg = USBC_Readl(regs + (SUNXI_GLOBALS_REGS_GCTL - SUNXI_GLOBALS_REGS_START));
reg |= SUNXI_GCTL_CORESOFTRESET;
USBC_Writel(reg, regs + (SUNXI_GLOBALS_REGS_GCTL - SUNXI_GLOBALS_REGS_START));
/* Assert USB3 PHY reset */
reg = USBC_Readl(regs + (SUNXI_GUSB3PIPECTL(0) - SUNXI_GLOBALS_REGS_START));
reg |= SUNXI_USB3PIPECTL_PHYSOFTRST;
USBC_Writel(reg, regs + (SUNXI_GUSB3PIPECTL(0) - SUNXI_GLOBALS_REGS_START));
/* Assert USB2 PHY reset */
reg = USBC_Readl(regs + (SUNXI_GUSB2PHYCFG(0) - SUNXI_GLOBALS_REGS_START));
reg |= SUNXI_USB2PHYCFG_PHYSOFTRST;
USBC_Writel(reg, regs + (SUNXI_GUSB2PHYCFG(0) - SUNXI_GLOBALS_REGS_START));
mdelay(100);
/* Clear USB3 PHY reset */
reg = USBC_Readl(regs + (SUNXI_GUSB3PIPECTL(0) - SUNXI_GLOBALS_REGS_START));
reg &= ~SUNXI_USB3PIPECTL_PHYSOFTRST;
USBC_Writel(reg, regs + (SUNXI_GUSB3PIPECTL(0) - SUNXI_GLOBALS_REGS_START));
/* Clear USB2 PHY reset */
reg = USBC_Readl(regs + (SUNXI_GUSB2PHYCFG(0) - SUNXI_GLOBALS_REGS_START));
reg &= ~SUNXI_USB2PHYCFG_PHYSOFTRST;
USBC_Writel(reg, regs + (SUNXI_GUSB2PHYCFG(0) - SUNXI_GLOBALS_REGS_START));
mdelay(100);
/* After PHYs are stable we can take Core out of reset state */
reg = USBC_Readl(regs + (SUNXI_GLOBALS_REGS_GCTL - SUNXI_GLOBALS_REGS_START));
reg &= ~SUNXI_GCTL_CORESOFTRESET;
USBC_Writel(reg, regs + (SUNXI_GLOBALS_REGS_GCTL - SUNXI_GLOBALS_REGS_START));
}
static int sunxi_core_open_phy(void __iomem *regs)
{
int reg_val = 0;
reg_val = USBC_Readl(regs + (SUNXI_PHY_EXTERNAL_CONTROL - SUNXI_GLOBALS_REGS_START));
reg_val |= SUNXI_PEC_EXTERN_VBUS; /* Use extern vbus to phy */
reg_val |= SUNXI_PEC_SSC_EN; /* SSC_EN */
reg_val |= SUNXI_PEC_REF_SSP_EN; /*REF_SSP_EN */
USBC_Writel(reg_val, regs + (SUNXI_PHY_EXTERNAL_CONTROL - SUNXI_GLOBALS_REGS_START));
reg_val = USBC_Readl(regs + (SUNXI_PIPE_CLOCK_CONTROL - SUNXI_GLOBALS_REGS_START));
reg_val |= SUNXI_PPC_PIPE_CLK_OPEN; /* open PIPE clock */
USBC_Writel(reg_val, regs + (SUNXI_PIPE_CLOCK_CONTROL - SUNXI_GLOBALS_REGS_START));
reg_val = USBC_Readl(regs + (SUNXI_APP - SUNXI_GLOBALS_REGS_START));
reg_val |= SUNXI_APP_FOCE_VBUS; /* open PIPE clock */
USBC_Writel(reg_val, regs + (SUNXI_APP - SUNXI_GLOBALS_REGS_START));
/* It is set 0x0047fc87 on bare-metal. */
USBC_Writel(0x0047fc87, regs + (SUNXI_PHY_TUNE_LOW - SUNXI_GLOBALS_REGS_START));
reg_val = USBC_Readl(regs + (SUNXI_PHY_TUNE_HIGH - SUNXI_GLOBALS_REGS_START));
reg_val |= SUNXI_TXVBOOSTLVL(0x7);
reg_val |= SUNXI_LOS_BIAS(0x7);
reg_val &= ~(SUNXI_TX_SWING_FULL(0x7f));
reg_val |= SUNXI_TX_SWING_FULL(0x55);
reg_val &= ~(SUNXI_TX_DEEMPH_6DB(0x3f));
reg_val |= SUNXI_TX_DEEMPH_6DB(0x20);
reg_val &= ~(SUNXI_TX_DEEMPH_3P5DB(0x3f));
reg_val |= SUNXI_TX_DEEMPH_3P5DB(0x15);
USBC_Writel(reg_val, regs + (SUNXI_PHY_TUNE_HIGH - SUNXI_GLOBALS_REGS_START));
/* Enable USB2.0 PHY Suspend mode. */
reg_val = USBC_Readl(regs + (SUNXI_GUSB2PHYCFG(0) - SUNXI_GLOBALS_REGS_START));
reg_val |= SUNXI_USB2PHYCFG_SUSPHY;
USBC_Writel(reg_val, regs + (SUNXI_GUSB2PHYCFG(0) - SUNXI_GLOBALS_REGS_START));
/* Enable SOFITPSYNC for suspend. */
reg_val = USBC_Readl(regs + (SUNXI_GLOBALS_REGS_GCTL - SUNXI_GLOBALS_REGS_START));
reg_val |= SUNXI_GCTL_SOFITPSYNC;
USBC_Writel(reg_val, regs + (SUNXI_GLOBALS_REGS_GCTL - SUNXI_GLOBALS_REGS_START));
return 0;
}
static void sunxi_xhci_open_clock(struct sunxi_hci_hcd *sunxi_xhci)
{
sunxi_xhci->open_clock(sunxi_xhci, 0);
}
static void sunxi_xhci_set_vbus(struct sunxi_hci_hcd *sunxi_xhci, int is_on)
{
sunxi_xhci->set_power(sunxi_xhci, is_on);
}
static void sunxi_start_xhci(struct sunxi_hci_hcd *sunxi_xhci)
{
sunxi_xhci->open_clock(sunxi_xhci, 1);
sunxi_xhci->set_power(sunxi_xhci, 1);
}
static void sunxi_stop_xhci(struct sunxi_hci_hcd *sunxi_xhci)
{
sunxi_xhci->set_power(sunxi_xhci, 0);
sunxi_xhci->close_clock(sunxi_xhci, 0);
}
static void sunxi_set_mode(struct sunxi_hci_hcd *sunxi_xhci, u32 mode)
{
u32 reg;
reg = USBC_Readl(sunxi_xhci->regs + (SUNXI_GLOBALS_REGS_GCTL - SUNXI_GLOBALS_REGS_START));
reg &= ~(SUNXI_GCTL_PRTCAPDIR(SUNXI_GCTL_PRTCAP_OTG));
reg |= SUNXI_GCTL_PRTCAPDIR(mode);
USBC_Writel(reg, sunxi_xhci->regs + (SUNXI_GLOBALS_REGS_GCTL - SUNXI_GLOBALS_REGS_START));
}
int xhci_host_init(struct sunxi_hci_hcd *sunxi_xhci)
{
struct platform_device *xhci;
int ret;
xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
if (!xhci) {
dev_err(sunxi_xhci->dev, "couldn't allocate xHCI device\n");
ret = -ENOMEM;
goto err0;
}
dma_set_coherent_mask(&xhci->dev, sunxi_xhci->dev->coherent_dma_mask);
xhci->dev.parent = sunxi_xhci->dev;
xhci->dev.dma_mask = sunxi_xhci->dev->dma_mask;
xhci->dev.dma_parms = sunxi_xhci->dev->dma_parms;
xhci->dev.archdata.dma_ops = sunxi_xhci->dev->archdata.dma_ops;
xhci->dev.archdata.dma_coherent = sunxi_xhci->dev->archdata.dma_coherent;
sunxi_xhci->pdev = xhci;
ret = platform_device_add_resources(xhci, sunxi_xhci->xhci_resources,
XHCI_RESOURCES_NUM);
if (ret) {
dev_err(sunxi_xhci->dev, "couldn't add resources to xHCI device\n");
goto err1;
}
ret = platform_device_add(xhci);
if (ret) {
dev_err(sunxi_xhci->dev, "failed to register xHCI device\n");
goto err1;
}
return 0;
err1:
platform_device_put(xhci);
err0:
return ret;
}
void xhci_host_exit(struct sunxi_hci_hcd *sunxi_xhci)
{
platform_device_unregister(sunxi_xhci->pdev);
}
static int sunxi_xhci_hcd_probe(struct platform_device *pdev)
{
int ret = 0;
struct sunxi_hci_hcd *sunxi_xhci = NULL;
void *mem;
struct device *dev = &pdev->dev;
struct resource *res;
void __iomem *regs;
if (pdev == NULL) {
DMSG_PANIC("%s, Argment is invalid\n", __func__);
return -1;
}
/* if usb is disabled, can not probe */
if (usb_disabled()) {
DMSG_PANIC("usb hcd is disabled\n");
return -ENODEV;
}
mem = devm_kzalloc(dev, sizeof(*sunxi_xhci) + SUNXI_ALIGN_MASK, GFP_KERNEL);
if (!mem) {
dev_err(dev, "not enough memory\n");
return -ENOMEM;
}
sunxi_xhci = PTR_ALIGN(mem, SUNXI_ALIGN_MASK + 1);
sunxi_xhci->mem = mem;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(dev, "missing IRQ\n");
return -ENODEV;
}
sunxi_xhci->xhci_resources[1].start = res->start;
sunxi_xhci->xhci_resources[1].end = res->end;
sunxi_xhci->xhci_resources[1].flags = res->flags;
sunxi_xhci->xhci_resources[1].name = res->name;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "missing memory resource\n");
return -ENODEV;
}
sunxi_xhci->xhci_resources[0].start = res->start;
sunxi_xhci->xhci_resources[0].end = sunxi_xhci->xhci_resources[0].start +
XHCI_REGS_END;
sunxi_xhci->xhci_resources[0].flags = res->flags;
sunxi_xhci->xhci_resources[0].name = res->name;
/*
* Request memory region but exclude xHCI regs,
* since it will be requested by the xhci-plat driver.
*/
res = devm_request_mem_region(dev, res->start + SUNXI_GLOBALS_REGS_START,
resource_size(res) - SUNXI_GLOBALS_REGS_START,
dev_name(dev));
if (!res) {
dev_err(dev, "can't request mem region\n");
return -ENOMEM;
}
regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
if (!regs) {
dev_err(dev, "ioremap failed\n");
return -ENOMEM;
}
spin_lock_init(&sunxi_xhci->lock);
sunxi_xhci->regs = regs;
sunxi_xhci->regs_size = resource_size(res);
sunxi_xhci->dev = dev;
dev->dma_mask = dev->parent->dma_mask;
dev->dma_parms = dev->parent->dma_parms;
dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask);
ret = init_sunxi_hci(pdev, SUNXI_USB_XHCI);
if (ret != 0) {
dev_err(&pdev->dev, "init_sunxi_hci is fail\n");
return 0;
}
platform_set_drvdata(pdev, sunxi_xhci);
sunxi_start_xhci(pdev->dev.platform_data);
sunxi_core_open_phy(sunxi_xhci->regs);
sunxi_set_mode(sunxi_xhci, SUNXI_GCTL_PRTCAP_HOST);
xhci_host_init(sunxi_xhci);
device_create_file(&pdev->dev, &dev_attr_xhci_enable);
device_create_file(&pdev->dev, &dev_attr_ed_test);
g_sunxi_xhci = sunxi_xhci;
g_dev_data = pdev->dev.platform_data;
g_dev_data->probe = 1;
#ifdef CONFIG_PM
if (!g_dev_data->wakeup_suspend)
INIT_WORK(&g_dev_data->resume_work, sunxi_xhci_resume_work);
#endif
return 0;
}
static int sunxi_xhci_hcd_remove(struct platform_device *pdev)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
struct sunxi_hci_hcd *dev_data = NULL;
if (pdev == NULL) {
DMSG_PANIC("%s, Argment is invalid\n", __func__);
return -1;
}
sunxi_xhci = g_sunxi_xhci;
dev_data = g_dev_data;
if (sunxi_xhci == NULL) {
DMSG_PANIC("%s, sunxi_xhci is null\n", __func__);
return -1;
}
device_remove_file(&pdev->dev, &dev_attr_xhci_enable);
xhci_host_exit(sunxi_xhci);
sunxi_stop_xhci(dev_data);
dev_data->probe = 0;
return 0;
}
static void sunxi_xhci_hcd_shutdown(struct platform_device *pdev)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
struct sunxi_hci_hcd *dev_data = NULL;
if (pdev == NULL) {
DMSG_PANIC("%s, Argment is invalid\n", __func__);
return;
}
sunxi_xhci = g_sunxi_xhci;
dev_data = g_dev_data;
if (sunxi_xhci == NULL) {
DMSG_PANIC("%s, is null\n", __func__);
return;
}
if (dev_data->probe == 0) {
DMSG_INFO("%s, %s is disable, need not shutdown\n", __func__, sunxi_xhci->hci_name);
return;
}
DMSG_INFO("[%s]: xhci shutdown start\n", sunxi_xhci->hci_name);
usb_hcd_platform_shutdown(sunxi_xhci->pdev);
DMSG_INFO("[%s]: xhci shutdown end\n", sunxi_xhci->hci_name);
return;
}
int sunxi_usb_disable_xhci(void)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
struct sunxi_hci_hcd *dev_data = NULL;
sunxi_xhci = g_sunxi_xhci;
dev_data = g_dev_data;
if (sunxi_xhci == NULL || dev_data == NULL) {
DMSG_PANIC("sunxi_xhci is null\n");
return -1;
}
if (dev_data->probe == 0) {
DMSG_PANIC("sunxi_xhci is disable, can not disable again\n");
return -1;
}
dev_data->probe = 0;
DMSG_INFO("[%s]: sunxi_usb_disable_xhci\n", sunxi_xhci->hci_name);
xhci_host_exit(sunxi_xhci);
sunxi_stop_xhci(dev_data);
return 0;
}
EXPORT_SYMBOL(sunxi_usb_disable_xhci);
int sunxi_usb_enable_xhci(void)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
struct sunxi_hci_hcd *dev_data = NULL;
sunxi_xhci = g_sunxi_xhci;
dev_data = g_dev_data;
if (sunxi_xhci == NULL || dev_data == NULL) {
DMSG_PANIC("sunxi_xhci is null\n");
return -1;
}
if (dev_data->probe == 1) {
DMSG_PANIC("sunxi_xhci is already enable, can not enable again\n");
return -1;
}
dev_data->probe = 1;
DMSG_INFO("[%s]: sunxi_usb_enable_xhci\n", sunxi_xhci->hci_name);
sunxi_start_xhci(dev_data);
sunxi_core_open_phy(sunxi_xhci->regs);
sunxi_set_mode(sunxi_xhci, SUNXI_GCTL_PRTCAP_HOST);
xhci_host_init(sunxi_xhci);
return 0;
}
EXPORT_SYMBOL(sunxi_usb_enable_xhci);
#ifdef CONFIG_PM
static int sunxi_xhci_hcd_suspend(struct device *dev)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
struct sunxi_hci_hcd *dev_data = NULL;
struct usb_hcd *hcd = NULL;
struct xhci_hcd *xhci = NULL;
if (dev == NULL) {
DMSG_PANIC("%s, Argment is invalid\n", __func__);
return 0;
}
sunxi_xhci = g_sunxi_xhci;
dev_data = g_dev_data;
if (sunxi_xhci == NULL) {
DMSG_PANIC("sunxi_xhci is null\n");
return 0;
}
if (dev_data->probe == 0) {
DMSG_INFO("[%s]: is disable, need not suspend\n",
dev_data->hci_name);
return 0;
}
hcd = dev_get_drvdata(&sunxi_xhci->pdev->dev);
if (hcd == NULL) {
DMSG_PANIC("xhci hcd is null\n");
return 0;
}
xhci = hcd_to_xhci(hcd);
if (xhci == NULL) {
DMSG_PANIC("xhci is null\n");
return 0;
}
if (dev_data->wakeup_suspend) {
DMSG_INFO("[%s]: not suspend\n", dev_data->hci_name);
} else {
DMSG_INFO("[%s]: sunxi_xhci_hcd_suspend\n", dev_data->hci_name);
atomic_add(1, &g_sunxi_usb_super_standby);
xhci_suspend(xhci, false);
sunxi_stop_xhci(dev_data);
cancel_work_sync(&dev_data->resume_work);
}
return 0;
}
static void sunxi_xhci_resume_work(struct work_struct *work)
{
struct sunxi_hci_hcd *dev_data = NULL;
dev_data = container_of(work, struct sunxi_hci_hcd, resume_work);
/* Waiting hci to resume. */
msleep(5000);
sunxi_xhci_set_vbus(dev_data, 1);
atomic_sub(1, &g_sunxi_usb_super_standby);
}
static int sunxi_xhci_hcd_resume(struct device *dev)
{
struct sunxi_hci_hcd *sunxi_xhci = NULL;
struct sunxi_hci_hcd *dev_data = NULL;
struct usb_hcd *hcd = NULL;
struct xhci_hcd *xhci = NULL;
if (dev == NULL) {
DMSG_PANIC("Argment is invalid\n");
return 0;
}
sunxi_xhci = g_sunxi_xhci;
dev_data = g_dev_data;
if (sunxi_xhci == NULL) {
DMSG_PANIC("sunxi_xhci is null\n");
return 0;
}
if (dev_data->probe == 0) {
DMSG_INFO("[%s]: is disable, need not resume\n",
dev_data->hci_name);
return 0;
}
hcd = dev_get_drvdata(&sunxi_xhci->pdev->dev);
if (hcd == NULL) {
DMSG_PANIC("xhci hcd is null\n");
return 0;
}
xhci = hcd_to_xhci(hcd);
if (xhci == NULL) {
DMSG_PANIC("xhci is null\n");
return 0;
}
if (dev_data->wakeup_suspend) {
DMSG_INFO("[%s]: controller not suspend, need not resume\n",
dev_data->hci_name);
} else {
DMSG_INFO("[%s]: sunxi_xhci_hcd_resume\n", dev_data->hci_name);
sunxi_xhci_open_clock(dev_data);
sunxi_core_open_phy(sunxi_xhci->regs);
sunxi_set_mode(sunxi_xhci, SUNXI_GCTL_PRTCAP_HOST);
xhci_resume(xhci, false);
schedule_work(&dev_data->resume_work);
}
return 0;
}
static const struct dev_pm_ops xhci_pmops = {
.suspend = sunxi_xhci_hcd_suspend,
.resume = sunxi_xhci_hcd_resume,
};
#endif
static const struct of_device_id sunxi_xhci_match[] = {
{.compatible = SUNXI_XHCI_OF_MATCH, },
{},
};
MODULE_DEVICE_TABLE(of, sunxi_xhci_match);
static struct platform_driver sunxi_xhci_hcd_driver = {
.probe = sunxi_xhci_hcd_probe,
.remove = sunxi_xhci_hcd_remove,
.shutdown = sunxi_xhci_hcd_shutdown,
.driver = {
.name = xhci_name,
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &xhci_pmops,
#endif
.of_match_table = sunxi_xhci_match,
}
};
module_platform_driver(sunxi_xhci_hcd_driver);
MODULE_ALIAS("platform:sunxi xhci");
MODULE_AUTHOR("wangjx <wangjx@allwinnertech.com>");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Allwinnertech Xhci Controller Driver");