| /* |
| * Copyright (C) 2018 Synaptics Incorporated |
| * |
| * Benson Gui <begu@synaptics.com> |
| * |
| * This file is licensed under the terms of the GNU General Public |
| * License version 2. This program is licensed "as is" without any |
| * warranty of any kind, whether express or implied. |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/of_device.h> |
| #include <linux/phy/phy.h> |
| #include <linux/platform_device.h> |
| #include <linux/reset.h> |
| |
| struct phy_as370_usb2_priv { |
| void __iomem *base; |
| struct reset_control *rst; |
| enum phy_mode mode; |
| }; |
| |
| #define AS370_USB_PHY_CTRL0 0x0 |
| #define AS370_USB_PHY_CTRL1 0x4 |
| #define AS370_USB_PHY_RB 0x10 |
| #define AS370_USB_PHY_RB_CLK_RDY (1 << 0) |
| |
| static int phy_as370_usb_power_on(struct phy *phy) |
| { |
| struct phy_as370_usb2_priv *priv = phy_get_drvdata(phy); |
| u32 val, count; |
| |
| /* setup USB_PHY_CTRL0 */ |
| writel(0x0EB35E84, priv->base + AS370_USB_PHY_CTRL0); |
| /* setup USB_PHY_CTRL1 */ |
| writel(0x80E9F004, priv->base + AS370_USB_PHY_CTRL1); |
| reset_control_deassert(priv->rst); |
| udelay(100); |
| |
| count = 10000; |
| while (count) { |
| val = readl(priv->base + AS370_USB_PHY_RB); |
| if (val & AS370_USB_PHY_RB_CLK_RDY) |
| break; |
| udelay(1); |
| count--; |
| } |
| |
| return count ? 0 : -ETIMEDOUT; |
| } |
| |
| static const struct phy_ops phy_as370_usb_ops = { |
| .power_on = phy_as370_usb_power_on, |
| .owner = THIS_MODULE, |
| }; |
| |
| #define AS390_USB_PHY_CTRL0 0x0 |
| #define AS390_USB_PHY_CTRL1 0x4 |
| #define AS390_USB_PHY_CTRL2 0x8 |
| #define AS390_USB_PHY_CTRL2_BYPASS (1 << 7) |
| #define AS390_USB_PHY_CTRL3 0xc |
| #define AS390_USB_PHY_RB 0x28 |
| #define AS390_USB_PHY_RB_CLK_RDY (1 << 0) |
| |
| static int phy_as390_usb_power_on(struct phy *phy) |
| { |
| struct phy_as370_usb2_priv *priv = phy_get_drvdata(phy); |
| u32 val, count; |
| |
| val = readl(priv->base + AS390_USB_PHY_CTRL2); |
| val &= ~AS390_USB_PHY_CTRL2_BYPASS; |
| writel(val, priv->base + AS390_USB_PHY_CTRL2); |
| |
| writel(0x0EB35E84, priv->base + AS390_USB_PHY_CTRL0); |
| val = (priv->mode == PHY_MODE_USB_HOST) ? 0x00000380 : 0x00000080; |
| writel(val, priv->base + AS390_USB_PHY_CTRL1); |
| writel(0x0029F004, priv->base + AS390_USB_PHY_CTRL3); |
| |
| reset_control_deassert(priv->rst); |
| udelay(100); |
| |
| count = 10000; |
| while (count) { |
| val = readl(priv->base + AS390_USB_PHY_RB); |
| if (val & AS390_USB_PHY_RB_CLK_RDY) |
| break; |
| udelay(1); |
| count--; |
| } |
| |
| val = readl(priv->base + AS390_USB_PHY_CTRL2); |
| val |= AS390_USB_PHY_CTRL2_BYPASS; |
| writel(val, priv->base + AS390_USB_PHY_CTRL2); |
| |
| return count ? 0 : -ETIMEDOUT; |
| } |
| |
| static int phy_as390_usb_set_mode(struct phy *phy, enum phy_mode mode) |
| { |
| struct phy_as370_usb2_priv *priv = phy_get_drvdata(phy); |
| u32 val; |
| |
| val = readl(priv->base + AS390_USB_PHY_CTRL1); |
| |
| switch (mode) { |
| case PHY_MODE_USB_HOST: |
| priv->mode = mode; |
| val |= 0x300; |
| writel(val, priv->base + AS390_USB_PHY_CTRL1); |
| break; |
| case PHY_MODE_USB_DEVICE: |
| priv->mode = mode; |
| val &= ~0x300; |
| writel(val, priv->base + AS390_USB_PHY_CTRL1); |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| |
| static const struct phy_ops phy_as390_usb_ops = { |
| .power_on = phy_as390_usb_power_on, |
| .set_mode = phy_as390_usb_set_mode, |
| .owner = THIS_MODULE, |
| }; |
| |
| static const struct of_device_id phy_as370_usb_of_match[] = { |
| { |
| .compatible = "syna,as370-usb2-phy", |
| .data = &phy_as370_usb_ops, |
| }, |
| { |
| .compatible = "syna,as390-usb2-phy", |
| .data = &phy_as390_usb_ops, |
| }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, phy_as370_usb_of_match); |
| |
| static int phy_as370_usb_probe(struct platform_device *pdev) |
| { |
| struct phy_as370_usb2_priv *priv; |
| struct resource *res; |
| struct phy *phy; |
| struct phy_provider *phy_provider; |
| const struct phy_ops *ops; |
| struct device *dev = &pdev->dev; |
| |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| priv->base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(priv->base)) |
| return PTR_ERR(priv->base); |
| |
| priv->rst = devm_reset_control_get(dev, NULL); |
| if (IS_ERR(priv->rst)) { |
| return PTR_ERR(priv->rst); |
| } |
| |
| ops = of_device_get_match_data(dev); |
| phy = devm_phy_create(dev, NULL, ops); |
| if (IS_ERR(phy)) { |
| dev_err(dev, "failed to create PHY\n"); |
| return PTR_ERR(phy); |
| } |
| |
| priv->mode = PHY_MODE_USB_HOST; |
| phy_set_drvdata(phy, priv); |
| |
| phy_provider = |
| devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
| return PTR_ERR_OR_ZERO(phy_provider); |
| } |
| |
| static struct platform_driver phy_as370_usb_driver = { |
| .probe = phy_as370_usb_probe, |
| .driver = { |
| .name = "phy-as370-usb2", |
| .of_match_table = phy_as370_usb_of_match, |
| }, |
| }; |
| module_platform_driver(phy_as370_usb_driver); |
| |
| MODULE_AUTHOR("Benson Gui <begu@synaptics.com>"); |
| MODULE_DESCRIPTION("Synaptics AS370 USB PHY driver"); |
| MODULE_LICENSE("GPL"); |