/* * 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 #include #include #include #include #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");