/* * 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> * * 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. */ #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->clk)) { dev_err(dev, "failed to get hsic_12M clock\n"); return PTR_ERR(phy->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");