blob: 39d4ac669f63e53ee91c6a14d9b532afcf3006f7 [file] [log] [blame]
/*
* Base driver for ASR USB
*
* Copyright 2021 ASR Microelectronics (Shanghai) Co., Ltd.
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file "COPYING" in the main directory of this
* archive for more details.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/platform_data/mv_usb.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <soc/asr/regs-addr.h>
#include <linux/delay.h>
#include <linux/cputype.h>
#include <linux/usb/phy.h>
#include "core.h"
#define USB_HANDLE_TIME_MSEC (5000)
#ifdef CONFIG_CPU_ASR1901
#define APMU_USB_WAKE_CLR (0x07c)
#define USB_WAKE_INT_EN (0x1 << 15)
#define USB_WAKE_PMU_EN (0x1 << 14)
#define USB_VBUS_WAKE_EN (0x1 << 10)
#define USB_ID_WAKE_EN (0x1 << 12)
#define USB_LINEST_WAKE_EN ((0x1 << 8) | (0x1 << 9))
#define USB_RXELEC_WAKE_EN (0x1 << 11)
#define USB_VBUS_WAKE_CLR (0x1 << 18)
#define USB_ID_WAKE_CLR (0x1 << 20)
#define USB_LINEST_WAKE_CLR ((0x1 << 16) | (0x1 << 17))
#define USB_RXELEC_WAKE_CLR (0x1 << 19)
#elif defined(CONFIG_CPU_ASR1903)
#define APMU_USB_WAKE_CLR (0x07c)
#define USB_WAKE_INT_EN (0x1 << 29)
#define USB_WAKE_PMU_EN (0x1 << 29)
#define USB_VBUS_WAKE_EN (0x1 << 11)
#define USB_ID_WAKE_EN (0x1 << 22)
#define USB_LINEST_WAKE_EN ((0x1 << 9) | (0x1 << 10))
#define USB_RXELEC_WAKE_EN ((0x1 << 9) | (0x1 << 10))
#define USB_VBUS_WAKE_CLR (0x1 << 4)
#define USB_ID_WAKE_CLR (0x1 << 23)
#define USB_LINEST_WAKE_CLR (0x1 << 7)
#define USB_RXELEC_WAKE_CLR (0x1 << 7)
#else
#define APMU_USB_WAKE_CLR (0x07c)
#define USB_WAKE_INT_EN (0x1 << 16)
#define USB_WAKE_PMU_EN (0x1 << 16)
#define USB_VBUS_WAKE_EN (0x1 << 11)
#define USB_ID_WAKE_EN (0x1 << 12)
#define USB_LINEST_WAKE_EN ((0x1 << 9) | (0x1 << 10))
#define USB_RXELEC_WAKE_EN (0x1 << 28)
#define USB_VBUS_WAKE_CLR (0x1 << 4)
#define USB_ID_WAKE_CLR (0x1 << 23)
#define USB_LINEST_WAKE_CLR (0x1 << 7)
#define USB_RXELEC_WAKE_CLR (0x1 << 29)
#endif
struct dwc3_asr {
struct device *dev;
struct clk *usb_clk;
int vbus_irq;
#ifdef CONFIG_DWC3_HWSULOG
int sulog_irq;
#endif
spinlock_t lock;
};
extern void dwc3_release_pm_qos(void);
extern void dwc3_release_pm_qos_timeout(u32 sec);
extern void dwc3_acquire_pm_qos(void);
extern void dwc3_acquire_wakeup_event(void);
extern struct dwc3 *dwc3_get_controller(void);
#ifdef CONFIG_DWC3_HWSULOG
extern void dwc3_hwsulog_clear_int(void);
#endif
static irqreturn_t vbus_irq(int irq, void *dev)
{
struct dwc3_asr *adwc = (struct dwc3_asr *)dev;
void __iomem *apmu_base = regs_addr_get_va(REGS_ADDR_APMU);
#ifdef CONFIG_DWC3_HWSULOG
dwc3_hwsulog_clear_int();
#endif
/* wait 50ms for vbus to be stable */
msleep(50);
if (cpu_is_asr1901() || cpu_is_asr1906())
writel(readl(apmu_base + APMU_USB_WAKE_CLR)
| USB_VBUS_WAKE_CLR | USB_LINEST_WAKE_CLR
| USB_ID_WAKE_CLR | USB_RXELEC_WAKE_CLR,
apmu_base + APMU_USB_WAKE_CLR);
else
writel(readl(apmu_base + APMU_USB_WAKE_CLR)
| (USB_VBUS_WAKE_CLR | USB_LINEST_WAKE_CLR | USB_RXELEC_WAKE_CLR
| USB_ID_WAKE_CLR | USB_WAKE_INT_EN),
apmu_base + APMU_USB_WAKE_CLR);
pm_wakeup_event(adwc->dev, USB_HANDLE_TIME_MSEC);
pxa_usb_notify(PXA_USB_DEV_OTG, EVENT_VBUS, 0);
dev_info(adwc->dev, "asr-usb vbus interrupt is served..\n");
return IRQ_HANDLED;
}
#ifdef CONFIG_DWC3_HWSULOG
static irqreturn_t sulog_irq_handler(int irq, void *dev)
{
hwsulog_error_handler();
return IRQ_HANDLED;
}
#endif
static int dwc3_asr_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct dwc3_asr *adwc;
int ret;
void __iomem *apmu_base;
adwc = devm_kzalloc(&pdev->dev, sizeof(*adwc), GFP_KERNEL);
if (!adwc)
return -ENOMEM;
platform_set_drvdata(pdev, adwc);
adwc->dev = &pdev->dev;
adwc->usb_clk = devm_clk_get(adwc->dev, NULL);
if (IS_ERR(adwc->usb_clk)) {
dev_err(adwc->dev, "failed to get core clock\n");
return PTR_ERR(adwc->usb_clk);
}
ret = clk_prepare_enable(adwc->usb_clk);
if (ret) {
dev_err(adwc->dev, "failed to enable core clock\n");
goto err_core;
}
ret = of_platform_populate(node, NULL, NULL, adwc->dev);
if (ret) {
dev_err(adwc->dev, "failed to register core - %d\n", ret);
goto err_iface;
}
adwc->vbus_irq = platform_get_irq(pdev, 0);
if (adwc->vbus_irq < 0) {
dev_err(&pdev->dev, "failed to get vbus irq\n");
ret = -ENXIO;
goto err_iface;
}
ret = devm_request_threaded_irq(&pdev->dev, adwc->vbus_irq,
NULL, vbus_irq,
IRQF_ONESHOT | IRQF_NO_SUSPEND,
"asr-usb-vbus", adwc);
if (ret) {
dev_info(&pdev->dev,
"Can not request irq for VBUS\n");
goto err_iface;
}
#ifdef CONFIG_DWC3_HWSULOG
adwc->sulog_irq = platform_get_irq(pdev, 1);
if (adwc->sulog_irq < 0) {
dev_info(&pdev->dev, "no sulog irq\n");
} else {
ret = devm_request_threaded_irq(&pdev->dev, adwc->sulog_irq,
NULL, sulog_irq_handler,
IRQF_ONESHOT | IRQF_SHARED,
"dwc3-sulog-irq", adwc);
if (ret) {
dev_info(&pdev->dev,
"Can not request irq for dwc3 sulog %d\n", ret);
goto err_iface;
}
}
#endif
apmu_base = regs_addr_get_va(REGS_ADDR_APMU);
writel(readl(apmu_base + APMU_USB_WAKE_CLR) | USB_WAKE_INT_EN | USB_VBUS_WAKE_EN,
apmu_base + APMU_USB_WAKE_CLR);
writel(readl(apmu_base + APMU_USB_WAKE_CLR)
| (USB_WAKE_INT_EN | USB_VBUS_WAKE_CLR | USB_LINEST_WAKE_CLR | USB_ID_WAKE_CLR),
apmu_base + APMU_USB_WAKE_CLR);
platform_set_drvdata(pdev, adwc);
device_init_wakeup(&pdev->dev, 1);
dev_info(&pdev->dev, "%s done\n", __func__);
return 0;
err_iface:
clk_disable_unprepare(adwc->usb_clk);
err_core:
return ret;
}
static int dwc3_asr_remove(struct platform_device *pdev)
{
struct dwc3_asr *adwc = platform_get_drvdata(pdev);
clk_disable_unprepare(adwc->usb_clk);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int dwc3_asr_suspend_noirq(struct device *dev)
{
struct dwc3_asr *adwc = dev_get_drvdata(dev);
void __iomem *apmu_base = regs_addr_get_va(REGS_ADDR_APMU);
struct dwc3 *dwc = dwc3_get_controller();
writel(readl(apmu_base + APMU_USB_WAKE_CLR)
| (USB_WAKE_INT_EN | USB_WAKE_PMU_EN
| USB_LINEST_WAKE_EN | USB_ID_WAKE_EN | USB_RXELEC_WAKE_EN
| USB_VBUS_WAKE_CLR | USB_LINEST_WAKE_CLR
| USB_ID_WAKE_CLR | USB_RXELEC_WAKE_CLR),
apmu_base + APMU_USB_WAKE_CLR);
if (dwc->allow_suspend) {
if (dwc->link_state == DWC3_LINK_STATE_U3)
usb_phy_set_suspend2(dwc->usb2_phy, 1);
else
pr_info("dwc3 linkst: %d\n", dwc->link_state);
}
enable_irq_wake(adwc->vbus_irq);
dwc3_release_pm_qos();
return 0;
}
static int dwc3_asr_resume_noirq(struct device *dev)
{
volatile u32 value;
void __iomem *apmu_base = regs_addr_get_va(REGS_ADDR_APMU);
struct dwc3_asr *adwc = dev_get_drvdata(dev);
struct dwc3 *dwc = dwc3_get_controller();
if (dwc->allow_suspend) {
if (dwc->vbus_active) {
dwc3_acquire_pm_qos();
usb_phy_set_suspend2(dwc->usb2_phy, 0);
} else {
pr_info("dwc3 vbus off, linkst: %d\n", dwc->link_state);
}
}
/* clear linestat wakeup and disable linestat/pmu wake en */
value = readl(apmu_base + APMU_USB_WAKE_CLR);
value |= (USB_WAKE_INT_EN | USB_LINEST_WAKE_CLR | USB_RXELEC_WAKE_CLR);
writel(value, apmu_base + APMU_USB_WAKE_CLR);
udelay(50);
value = readl(apmu_base + APMU_USB_WAKE_CLR);
value &= ~(USB_WAKE_PMU_EN | USB_LINEST_WAKE_EN | USB_RXELEC_WAKE_EN);
value |= (USB_VBUS_WAKE_EN | USB_ID_WAKE_EN | USB_WAKE_INT_EN);
writel(value, apmu_base + APMU_USB_WAKE_CLR);
disable_irq_wake(adwc->vbus_irq);
return 0;
}
static const struct dev_pm_ops dwc3_asr_pm_ops = {
.suspend_noirq = dwc3_asr_suspend_noirq,
.resume_noirq = dwc3_asr_resume_noirq,
};
#endif
static const struct of_device_id of_dwc3_match[] = {
{ .compatible = "asr,dwc3" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_dwc3_match);
static struct platform_driver dwc3_asr_driver = {
.probe = dwc3_asr_probe,
.remove = dwc3_asr_remove,
.driver = {
.name = "asr-dwc3",
.of_match_table = of_dwc3_match,
#ifdef CONFIG_PM_SLEEP
.pm = &dwc3_asr_pm_ops,
#endif
},
};
module_platform_driver(dwc3_asr_driver);
MODULE_ALIAS("platform:asr-dwc3");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ASR USB Glue Layer");