mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 17:59:32 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 3 normalized pattern(s): 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 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 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 [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] 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 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 [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
193 lines
4.5 KiB
C
193 lines
4.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Allwinner sun9i USB phy driver
|
|
*
|
|
* Copyright (C) 2014-2015 Chen-Yu Tsai <wens@csie.org>
|
|
*
|
|
* Based on phy-sun4i-usb.c from
|
|
* Hans de Goede <hdegoede@redhat.com>
|
|
*
|
|
* and code from
|
|
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/usb/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/reset.h>
|
|
|
|
#define SUNXI_AHB_INCR16_BURST_EN BIT(11)
|
|
#define SUNXI_AHB_INCR8_BURST_EN BIT(10)
|
|
#define SUNXI_AHB_INCR4_BURST_EN BIT(9)
|
|
#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8)
|
|
#define SUNXI_ULPI_BYPASS_EN BIT(0)
|
|
|
|
/* usb1 HSIC specific bits */
|
|
#define SUNXI_EHCI_HS_FORCE BIT(20)
|
|
#define SUNXI_HSIC_CONNECT_DET BIT(17)
|
|
#define SUNXI_HSIC_CONNECT_INT BIT(16)
|
|
#define SUNXI_HSIC BIT(1)
|
|
|
|
struct sun9i_usb_phy {
|
|
struct phy *phy;
|
|
void __iomem *pmu;
|
|
struct reset_control *reset;
|
|
struct clk *clk;
|
|
struct clk *hsic_clk;
|
|
enum usb_phy_interface type;
|
|
};
|
|
|
|
static void sun9i_usb_phy_passby(struct sun9i_usb_phy *phy, int enable)
|
|
{
|
|
u32 bits, reg_value;
|
|
|
|
bits = SUNXI_AHB_INCR16_BURST_EN | SUNXI_AHB_INCR8_BURST_EN |
|
|
SUNXI_AHB_INCR4_BURST_EN | SUNXI_AHB_INCRX_ALIGN_EN |
|
|
SUNXI_ULPI_BYPASS_EN;
|
|
|
|
if (phy->type == USBPHY_INTERFACE_MODE_HSIC)
|
|
bits |= SUNXI_HSIC | SUNXI_EHCI_HS_FORCE |
|
|
SUNXI_HSIC_CONNECT_DET | SUNXI_HSIC_CONNECT_INT;
|
|
|
|
reg_value = readl(phy->pmu);
|
|
|
|
if (enable)
|
|
reg_value |= bits;
|
|
else
|
|
reg_value &= ~bits;
|
|
|
|
writel(reg_value, phy->pmu);
|
|
}
|
|
|
|
static int sun9i_usb_phy_init(struct phy *_phy)
|
|
{
|
|
struct sun9i_usb_phy *phy = phy_get_drvdata(_phy);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(phy->clk);
|
|
if (ret)
|
|
goto err_clk;
|
|
|
|
ret = clk_prepare_enable(phy->hsic_clk);
|
|
if (ret)
|
|
goto err_hsic_clk;
|
|
|
|
ret = reset_control_deassert(phy->reset);
|
|
if (ret)
|
|
goto err_reset;
|
|
|
|
sun9i_usb_phy_passby(phy, 1);
|
|
return 0;
|
|
|
|
err_reset:
|
|
clk_disable_unprepare(phy->hsic_clk);
|
|
|
|
err_hsic_clk:
|
|
clk_disable_unprepare(phy->clk);
|
|
|
|
err_clk:
|
|
return ret;
|
|
}
|
|
|
|
static int sun9i_usb_phy_exit(struct phy *_phy)
|
|
{
|
|
struct sun9i_usb_phy *phy = phy_get_drvdata(_phy);
|
|
|
|
sun9i_usb_phy_passby(phy, 0);
|
|
reset_control_assert(phy->reset);
|
|
clk_disable_unprepare(phy->hsic_clk);
|
|
clk_disable_unprepare(phy->clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct phy_ops sun9i_usb_phy_ops = {
|
|
.init = sun9i_usb_phy_init,
|
|
.exit = sun9i_usb_phy_exit,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static int sun9i_usb_phy_probe(struct platform_device *pdev)
|
|
{
|
|
struct sun9i_usb_phy *phy;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct phy_provider *phy_provider;
|
|
struct resource *res;
|
|
|
|
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
|
|
if (!phy)
|
|
return -ENOMEM;
|
|
|
|
phy->type = of_usb_get_phy_mode(np);
|
|
if (phy->type == USBPHY_INTERFACE_MODE_HSIC) {
|
|
phy->clk = devm_clk_get(dev, "hsic_480M");
|
|
if (IS_ERR(phy->clk)) {
|
|
dev_err(dev, "failed to get hsic_480M clock\n");
|
|
return PTR_ERR(phy->clk);
|
|
}
|
|
|
|
phy->hsic_clk = devm_clk_get(dev, "hsic_12M");
|
|
if (IS_ERR(phy->hsic_clk)) {
|
|
dev_err(dev, "failed to get hsic_12M clock\n");
|
|
return PTR_ERR(phy->hsic_clk);
|
|
}
|
|
|
|
phy->reset = devm_reset_control_get(dev, "hsic");
|
|
if (IS_ERR(phy->reset)) {
|
|
dev_err(dev, "failed to get reset control\n");
|
|
return PTR_ERR(phy->reset);
|
|
}
|
|
} else {
|
|
phy->clk = devm_clk_get(dev, "phy");
|
|
if (IS_ERR(phy->clk)) {
|
|
dev_err(dev, "failed to get phy clock\n");
|
|
return PTR_ERR(phy->clk);
|
|
}
|
|
|
|
phy->reset = devm_reset_control_get(dev, "phy");
|
|
if (IS_ERR(phy->reset)) {
|
|
dev_err(dev, "failed to get reset control\n");
|
|
return PTR_ERR(phy->reset);
|
|
}
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
phy->pmu = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(phy->pmu))
|
|
return PTR_ERR(phy->pmu);
|
|
|
|
phy->phy = devm_phy_create(dev, NULL, &sun9i_usb_phy_ops);
|
|
if (IS_ERR(phy->phy)) {
|
|
dev_err(dev, "failed to create PHY\n");
|
|
return PTR_ERR(phy->phy);
|
|
}
|
|
|
|
phy_set_drvdata(phy->phy, phy);
|
|
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
|
|
|
return PTR_ERR_OR_ZERO(phy_provider);
|
|
}
|
|
|
|
static const struct of_device_id sun9i_usb_phy_of_match[] = {
|
|
{ .compatible = "allwinner,sun9i-a80-usb-phy" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sun9i_usb_phy_of_match);
|
|
|
|
static struct platform_driver sun9i_usb_phy_driver = {
|
|
.probe = sun9i_usb_phy_probe,
|
|
.driver = {
|
|
.of_match_table = sun9i_usb_phy_of_match,
|
|
.name = "sun9i-usb-phy",
|
|
}
|
|
};
|
|
module_platform_driver(sun9i_usb_phy_driver);
|
|
|
|
MODULE_DESCRIPTION("Allwinner sun9i USB phy driver");
|
|
MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
|
|
MODULE_LICENSE("GPL");
|