mirror of
https://github.com/torvalds/linux.git
synced 2024-12-28 05:41:55 +00:00
a4333c3a6b
DWC3 controller on Qualcomm SOCs has a Qscratch wrapper. Some of its uses are described below resulting in need to have a separate glue driver instead of using dwc3-of-simple: - It exposes register interface to override vbus-override and lane0-pwr-present signals going to hardware. These must be updated in peripheral mode for DWC3 if vbus lines are not connected to hardware block. Otherwise RX termination in SS mode or DP pull-up is not applied by device controller. - pwr_events_irq_stat support to check if USB2 PHY is in L2 state before glue driver proceeds with suspend. - Support for wakeup interrupts lines that are asserted whenever there is any wakeup event on USB3 or USB2 bus. - Support to replace pip3 clock going to DWC3 with utmi clock for hardware configuration where SSPHY is not used with DWC3. Signed-off-by: Manu Gautam <mgautam@codeaurora.org> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
236 lines
5.3 KiB
C
236 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/**
|
|
* dwc3-of-simple.c - OF glue layer for simple integrations
|
|
*
|
|
* Copyright (c) 2015 Texas Instruments Incorporated - http://www.ti.com
|
|
*
|
|
* Author: Felipe Balbi <balbi@ti.com>
|
|
*
|
|
* This is a combination of the old dwc3-qcom.c by Ivan T. Ivanov
|
|
* <iivanov@mm-sol.com> and the original patch adding support for Xilinx' SoC
|
|
* by Subbaraya Sundeep Bhatta <subbaraya.sundeep.bhatta@xilinx.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/reset.h>
|
|
|
|
struct dwc3_of_simple {
|
|
struct device *dev;
|
|
struct clk **clks;
|
|
int num_clocks;
|
|
struct reset_control *resets;
|
|
bool pulse_resets;
|
|
};
|
|
|
|
static int dwc3_of_simple_clk_init(struct dwc3_of_simple *simple, int count)
|
|
{
|
|
struct device *dev = simple->dev;
|
|
struct device_node *np = dev->of_node;
|
|
int i;
|
|
|
|
simple->num_clocks = count;
|
|
|
|
if (!count)
|
|
return 0;
|
|
|
|
simple->clks = devm_kcalloc(dev, simple->num_clocks,
|
|
sizeof(struct clk *), GFP_KERNEL);
|
|
if (!simple->clks)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < simple->num_clocks; i++) {
|
|
struct clk *clk;
|
|
int ret;
|
|
|
|
clk = of_clk_get(np, i);
|
|
if (IS_ERR(clk)) {
|
|
while (--i >= 0) {
|
|
clk_disable_unprepare(simple->clks[i]);
|
|
clk_put(simple->clks[i]);
|
|
}
|
|
return PTR_ERR(clk);
|
|
}
|
|
|
|
ret = clk_prepare_enable(clk);
|
|
if (ret < 0) {
|
|
while (--i >= 0) {
|
|
clk_disable_unprepare(simple->clks[i]);
|
|
clk_put(simple->clks[i]);
|
|
}
|
|
clk_put(clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
simple->clks[i] = clk;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_of_simple_probe(struct platform_device *pdev)
|
|
{
|
|
struct dwc3_of_simple *simple;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
|
|
int ret;
|
|
int i;
|
|
bool shared_resets = false;
|
|
|
|
simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL);
|
|
if (!simple)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, simple);
|
|
simple->dev = dev;
|
|
|
|
if (of_device_is_compatible(np, "amlogic,meson-axg-dwc3") ||
|
|
of_device_is_compatible(np, "amlogic,meson-gxl-dwc3")) {
|
|
shared_resets = true;
|
|
simple->pulse_resets = true;
|
|
}
|
|
|
|
simple->resets = of_reset_control_array_get(np, shared_resets, true);
|
|
if (IS_ERR(simple->resets)) {
|
|
ret = PTR_ERR(simple->resets);
|
|
dev_err(dev, "failed to get device resets, err=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (simple->pulse_resets) {
|
|
ret = reset_control_reset(simple->resets);
|
|
if (ret)
|
|
goto err_resetc_put;
|
|
} else {
|
|
ret = reset_control_deassert(simple->resets);
|
|
if (ret)
|
|
goto err_resetc_put;
|
|
}
|
|
|
|
ret = dwc3_of_simple_clk_init(simple, of_count_phandle_with_args(np,
|
|
"clocks", "#clock-cells"));
|
|
if (ret)
|
|
goto err_resetc_assert;
|
|
|
|
ret = of_platform_populate(np, NULL, NULL, dev);
|
|
if (ret) {
|
|
for (i = 0; i < simple->num_clocks; i++) {
|
|
clk_disable_unprepare(simple->clks[i]);
|
|
clk_put(simple->clks[i]);
|
|
}
|
|
|
|
goto err_resetc_assert;
|
|
}
|
|
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
pm_runtime_get_sync(dev);
|
|
|
|
return 0;
|
|
|
|
err_resetc_assert:
|
|
if (!simple->pulse_resets)
|
|
reset_control_assert(simple->resets);
|
|
|
|
err_resetc_put:
|
|
reset_control_put(simple->resets);
|
|
return ret;
|
|
}
|
|
|
|
static int dwc3_of_simple_remove(struct platform_device *pdev)
|
|
{
|
|
struct dwc3_of_simple *simple = platform_get_drvdata(pdev);
|
|
struct device *dev = &pdev->dev;
|
|
int i;
|
|
|
|
of_platform_depopulate(dev);
|
|
|
|
for (i = 0; i < simple->num_clocks; i++) {
|
|
clk_disable_unprepare(simple->clks[i]);
|
|
clk_put(simple->clks[i]);
|
|
}
|
|
simple->num_clocks = 0;
|
|
|
|
if (!simple->pulse_resets)
|
|
reset_control_assert(simple->resets);
|
|
|
|
reset_control_put(simple->resets);
|
|
|
|
pm_runtime_put_sync(dev);
|
|
pm_runtime_disable(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int dwc3_of_simple_runtime_suspend(struct device *dev)
|
|
{
|
|
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
|
|
int i;
|
|
|
|
for (i = 0; i < simple->num_clocks; i++)
|
|
clk_disable(simple->clks[i]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_of_simple_runtime_resume(struct device *dev)
|
|
{
|
|
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
|
|
int ret;
|
|
int i;
|
|
|
|
for (i = 0; i < simple->num_clocks; i++) {
|
|
ret = clk_enable(simple->clks[i]);
|
|
if (ret < 0) {
|
|
while (--i >= 0)
|
|
clk_disable(simple->clks[i]);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend,
|
|
dwc3_of_simple_runtime_resume, NULL)
|
|
};
|
|
|
|
static const struct of_device_id of_dwc3_simple_match[] = {
|
|
{ .compatible = "rockchip,rk3399-dwc3" },
|
|
{ .compatible = "xlnx,zynqmp-dwc3" },
|
|
{ .compatible = "cavium,octeon-7130-usb-uctl" },
|
|
{ .compatible = "sprd,sc9860-dwc3" },
|
|
{ .compatible = "amlogic,meson-axg-dwc3" },
|
|
{ .compatible = "amlogic,meson-gxl-dwc3" },
|
|
{ .compatible = "allwinner,sun50i-h6-dwc3" },
|
|
{ /* Sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, of_dwc3_simple_match);
|
|
|
|
static struct platform_driver dwc3_of_simple_driver = {
|
|
.probe = dwc3_of_simple_probe,
|
|
.remove = dwc3_of_simple_remove,
|
|
.driver = {
|
|
.name = "dwc3-of-simple",
|
|
.of_match_table = of_dwc3_simple_match,
|
|
.pm = &dwc3_of_simple_dev_pm_ops,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(dwc3_of_simple_driver);
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("DesignWare USB3 OF Simple Glue Layer");
|
|
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
|