| /* |
| * Copyright (c) 2017 NXP. |
| * |
| * 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. |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/phy/phy.h> |
| #include <linux/io.h> |
| |
| #define PHY_CTRL0 0x0 |
| #define PHY_CTRL0_REF_SSP_EN BIT(2) |
| #define PHY_CTRL0_SSC_RANGE_MASK (7 << 21) |
| #define PHY_CTRL0_SSC_RANGE_4003PPM (0x2 << 21) |
| |
| #define PHY_CTRL1 0x4 |
| #define PHY_CTRL1_RESET BIT(0) |
| #define PHY_CTRL1_COMMONONN BIT(1) |
| #define PHY_CTRL1_ATERESET BIT(3) |
| #define PHY_CTRL1_VDATSRCENB0 BIT(19) |
| #define PHY_CTRL1_VDATDETENB0 BIT(20) |
| |
| #define PHY_CTRL2 0x8 |
| #define PHY_CTRL2_TXENABLEN0 BIT(8) |
| |
| struct imx8mq_usb_phy { |
| struct phy *phy; |
| struct clk *clk; |
| void __iomem *base; |
| }; |
| |
| static int imx8mq_phy_start(struct phy *_phy) |
| { |
| struct imx8mq_usb_phy *phy = phy_get_drvdata(_phy); |
| |
| return clk_prepare_enable(phy->clk); |
| } |
| |
| static int imx8mq_phy_exit(struct phy *_phy) |
| { |
| struct imx8mq_usb_phy *phy = phy_get_drvdata(_phy); |
| |
| clk_disable_unprepare(phy->clk); |
| |
| return 0; |
| } |
| |
| static struct phy_ops imx8mq_usb_phy_ops = { |
| .init = imx8mq_phy_start, |
| .exit = imx8mq_phy_exit, |
| .owner = THIS_MODULE, |
| }; |
| |
| static void imx8mq_usb_phy_init(struct imx8mq_usb_phy *phy) |
| { |
| u32 value; |
| |
| value = readl(phy->base + PHY_CTRL1); |
| value &= ~(PHY_CTRL1_VDATSRCENB0 | PHY_CTRL1_VDATDETENB0 | |
| PHY_CTRL1_COMMONONN); |
| value |= PHY_CTRL1_RESET | PHY_CTRL1_ATERESET; |
| writel(value, phy->base + PHY_CTRL1); |
| |
| value = readl(phy->base + PHY_CTRL0); |
| value |= PHY_CTRL0_REF_SSP_EN; |
| value &= ~PHY_CTRL0_SSC_RANGE_MASK; |
| value |= PHY_CTRL0_SSC_RANGE_4003PPM; |
| writel(value, phy->base + PHY_CTRL0); |
| |
| value = readl(phy->base + PHY_CTRL2); |
| value |= PHY_CTRL2_TXENABLEN0; |
| writel(value, phy->base + PHY_CTRL2); |
| |
| value = readl(phy->base + PHY_CTRL1); |
| value &= ~(PHY_CTRL1_RESET | PHY_CTRL1_ATERESET); |
| writel(value, phy->base + PHY_CTRL1); |
| } |
| |
| static int imx8mq_usb_phy_probe(struct platform_device *pdev) |
| { |
| struct phy_provider *phy_provider; |
| struct device *dev = &pdev->dev; |
| struct imx8mq_usb_phy *imx_phy; |
| struct resource *res; |
| |
| imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL); |
| if (!imx_phy) |
| return -ENOMEM; |
| |
| imx_phy->clk = devm_clk_get(dev, "usb_phy_root_clk"); |
| if (IS_ERR(imx_phy->clk)) { |
| dev_err(dev, "failed to get imx8mq usb phy clock\n"); |
| return PTR_ERR(imx_phy->clk); |
| } |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| imx_phy->base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(imx_phy->base)) |
| return PTR_ERR(imx_phy->base); |
| |
| imx_phy->phy = devm_phy_create(dev, NULL, &imx8mq_usb_phy_ops); |
| if (IS_ERR(imx_phy->phy)) |
| return PTR_ERR(imx_phy->phy); |
| |
| phy_set_drvdata(imx_phy->phy, imx_phy); |
| |
| imx8mq_usb_phy_init(imx_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 imx8mq_usb_phy_of_match[] = { |
| {.compatible = "fsl,imx8mq-usb-phy",}, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match); |
| |
| static struct platform_driver imx8mq_usb_phy_driver = { |
| .probe = imx8mq_usb_phy_probe, |
| .driver = { |
| .name = "imx8mq-usb-phy", |
| .of_match_table = imx8mq_usb_phy_of_match, |
| } |
| }; |
| module_platform_driver(imx8mq_usb_phy_driver); |
| |
| MODULE_DESCRIPTION("FSL IMX8MQ USB PHY driver"); |
| MODULE_ALIAS("platform:imx8mq-usb-phy"); |
| MODULE_LICENSE("GPL"); |