Loading Documentation/devicetree/bindings/usb/msm-phy.txt +33 −0 Original line number Diff line number Diff line Loading @@ -113,3 +113,36 @@ Example: qcom,vdd-voltage-level = <0 900000 1050000>; qcom,vbus-valid-override; }; QUSB2 High-Speed PHY Required properties: - compatible: Should be "qcom,qusb2phy" - reg: Address and length of the register set for the device - <supply-name>-supply: phandle to the regulator device tree node Required supplies are: "vdd" : vdd supply for digital circuit operation "vdda18" : 1.8v high-voltage analog supply "vdda33" : 3.3v high-voltage analog supply - qcom,vdd-voltage-level: This property must be a list of three integer values (no, min, max) where each value represents either a voltage in microvolts or a value corresponding to voltage corner - clocks: a list of phandles to the PHY clocks. Use as per Documentation/devicetree/bindings/clock/clock-bindings.txt - clock-names: Names of the clocks in 1-1 correspondence with the "clocks" property. Required clocks are "ref_clk", "cfg_ahb_clk" and "phy_reset". Example: qusb_phy: qusb@f9b39000 { compatible = "qcom,qusb2phy"; reg = <0xf9b39000 0x17f>; vdd-supply = <&pm8994_s2_corner>; vdda18-supply = <&pm8994_l6>; vdda33-supply = <&pm8994_l24>; qcom,vdd-voltage-level = <1 5 7>; clocks = <&clock_rpm clk_ln_bb_clk>, <&clock_gcc clk_gcc_usb_phy_cfg_ahb2phy_clk>, <&clock_gcc clk_gcc_qusb2_phy_reset>; clock-names = "ref_clk", "cfg_ahb_clk", "phy_reset"; }; drivers/usb/phy/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -250,6 +250,15 @@ config USB_MSM_SSPHY_QMP set for its control sequences, normally paired with newer DWC3-based SuperSpeed controllers. config MSM_QUSB_PHY tristate "MSM QUSB2 PHY Driver" default n help Enable this to support the QUSB2 PHY on MSM chips. This driver supports the high-speed PHY which is usually paired with either the ChipIdea or Synopsys DWC3 USB IPs on MSM SOCs. This driver expects to configure the PHY with a dedicated register I/O memory region. config USB_MV_OTG tristate "Marvell USB OTG support" depends on USB_EHCI_MV && USB_MV_UDC && PM_RUNTIME Loading drivers/usb/phy/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o obj-$(CONFIG_USB_MSM_HSPHY) += phy-msm-hsusb.o obj-$(CONFIG_USB_MSM_SSPHY) += phy-msm-ssusb.o obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o obj-$(CONFIG_MSM_QUSB_PHY) += phy-msm-qusb.o obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o Loading drivers/usb/phy/phy-msm-qusb.c 0 → 100644 +276 −0 Original line number Diff line number Diff line /* * Copyright (c) 2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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/module.h> #include <linux/kernel.h> #include <linux/err.h> #include <linux/slab.h> #include <linux/clk.h> #include <linux/clk/msm-clk.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/usb/phy.h> #define QUSB2PHY_PORT_POWERDOWN 0xB4 #define CLAMP_N_EN BIT(5) #define FREEZIO_N BIT(1) #define POWER_DOWN BIT(0) #define QUSB2PHY_PORT_UTMI_CTRL2 0xC4 struct qusb_phy { struct usb_phy phy; void __iomem *base; struct clk *ref_clk; struct clk *cfg_ahb_clk; struct clk *phy_reset; struct regulator *vdd; struct regulator *vdda33; struct regulator *vdda18; int vdd_levels[3]; /* none, low, high */ bool power_enabled; bool clocks_enabled; bool suspended; }; static int qusb_phy_reset(struct usb_phy *phy) { struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); dev_dbg(phy->dev, "%s\n", __func__); clk_reset(qphy->phy_reset, CLK_RESET_ASSERT); usleep(100); clk_reset(qphy->phy_reset, CLK_RESET_DEASSERT); return 0; } static int qusb_phy_enable_power(struct qusb_phy *qphy, bool on) { int ret = 0; dev_dbg(qphy->phy.dev, "%s turn %s regulators\n", __func__, on ? "on" : "off"); if (qphy->power_enabled == on) return 0; if (!on) goto disable_vdda33; ret = regulator_enable(qphy->vdd); if (ret) { dev_err(qphy->phy.dev, "unable to enable vdd\n"); return ret; } ret = regulator_enable(qphy->vdda18); if (ret) { dev_err(qphy->phy.dev, "unable to enable vdda18\n"); goto disable_vdd; } ret = regulator_enable(qphy->vdda33); if (ret) { dev_err(qphy->phy.dev, "unable to enable vdda33\n"); goto disable_vdda18; } qphy->power_enabled = true; return 0; disable_vdda33: regulator_disable(qphy->vdda33); disable_vdda18: regulator_disable(qphy->vdda18); disable_vdd: regulator_disable(qphy->vdd); qphy->power_enabled = false; return ret; } static int qusb_phy_init(struct usb_phy *phy) { struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); dev_dbg(phy->dev, "%s\n", __func__); if (!qphy->clocks_enabled) { clk_prepare_enable(qphy->ref_clk); clk_prepare_enable(qphy->cfg_ahb_clk); qphy->clocks_enabled = true; } /* Disable the PHY */ writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN, qphy->base + QUSB2PHY_PORT_POWERDOWN); /* configure for ULPI mode */ writel_relaxed(0x0, qphy->base + QUSB2PHY_PORT_UTMI_CTRL2); /* ensure above writes are completed before re-enabling PHY */ wmb(); /* Enable the PHY */ writel_relaxed(CLAMP_N_EN | FREEZIO_N, qphy->base + QUSB2PHY_PORT_POWERDOWN); return 0; } static void qusb_phy_shutdown(struct usb_phy *phy) { struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); dev_dbg(phy->dev, "%s\n", __func__); /* clocks need to be on to access register */ if (!qphy->clocks_enabled) { clk_prepare_enable(qphy->ref_clk); clk_prepare_enable(qphy->cfg_ahb_clk); qphy->clocks_enabled = true; } /* Disable the PHY */ writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN, qphy->base + QUSB2PHY_PORT_POWERDOWN); wmb(); clk_disable_unprepare(qphy->cfg_ahb_clk); clk_disable_unprepare(qphy->ref_clk); qphy->clocks_enabled = false; } static int qusb_phy_probe(struct platform_device *pdev) { struct qusb_phy *qphy; struct device *dev = &pdev->dev; struct resource *res; int ret = 0; qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); if (!qphy) return -ENOMEM; qphy->phy.dev = dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); qphy->base = devm_ioremap_resource(dev, res); if (IS_ERR(qphy->base)) return PTR_ERR(qphy->base); qphy->ref_clk = devm_clk_get(dev, "ref_clk"); if (IS_ERR(qphy->ref_clk)) return PTR_ERR(qphy->ref_clk); clk_set_rate(qphy->ref_clk, 19200000); qphy->phy_reset = devm_clk_get(dev, "cfg_ahb_clk"); if (IS_ERR(qphy->cfg_ahb_clk)) return PTR_ERR(qphy->cfg_ahb_clk); qphy->phy_reset = devm_clk_get(dev, "phy_reset"); if (IS_ERR(qphy->phy_reset)) return PTR_ERR(qphy->phy_reset); ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", (u32 *) qphy->vdd_levels, ARRAY_SIZE(qphy->vdd_levels)); if (ret) { dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); return ret; } qphy->vdd = devm_regulator_get(dev, "vdd"); if (IS_ERR(qphy->vdd)) { dev_err(dev, "unable to get vdd supply\n"); return PTR_ERR(qphy->vdd); } qphy->vdda33 = devm_regulator_get(dev, "vdda33"); if (IS_ERR(qphy->vdda33)) { dev_err(dev, "unable to get vdda33 supply\n"); return PTR_ERR(qphy->vdda33); } qphy->vdda18 = devm_regulator_get(dev, "vdda18"); if (IS_ERR(qphy->vdda18)) { dev_err(dev, "unable to get vdda18 supply\n"); return PTR_ERR(qphy->vdda18); } ret = qusb_phy_enable_power(qphy, true); if (ret) return ret; clk_prepare_enable(qphy->ref_clk); clk_prepare_enable(qphy->cfg_ahb_clk); qphy->clocks_enabled = true; platform_set_drvdata(pdev, qphy); qphy->phy.label = "msm-qusb-phy"; qphy->phy.init = qusb_phy_init; qphy->phy.shutdown = qusb_phy_shutdown; qphy->phy.reset = qusb_phy_reset; qphy->phy.type = USB_PHY_TYPE_USB2; qusb_phy_reset(&qphy->phy); ret = usb_add_phy_dev(&qphy->phy); return ret; } static int qusb_phy_remove(struct platform_device *pdev) { struct qusb_phy *qphy = platform_get_drvdata(pdev); usb_remove_phy(&qphy->phy); if (qphy->clocks_enabled) { clk_disable_unprepare(qphy->cfg_ahb_clk); clk_disable_unprepare(qphy->ref_clk); qphy->clocks_enabled = false; } qusb_phy_enable_power(qphy, false); return 0; } static const struct of_device_id qusb_phy_id_table[] = { { .compatible = "qcom,qusb2phy", }, { }, }; MODULE_DEVICE_TABLE(of, qusb_phy_id_table); static struct platform_driver qusb_phy_driver = { .probe = qusb_phy_probe, .remove = qusb_phy_remove, .driver = { .name = "msm-qusb-phy", .of_match_table = of_match_ptr(qusb_phy_id_table), }, }; module_platform_driver(qusb_phy_driver); MODULE_DESCRIPTION("MSM QUSB2 PHY driver"); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/usb/msm-phy.txt +33 −0 Original line number Diff line number Diff line Loading @@ -113,3 +113,36 @@ Example: qcom,vdd-voltage-level = <0 900000 1050000>; qcom,vbus-valid-override; }; QUSB2 High-Speed PHY Required properties: - compatible: Should be "qcom,qusb2phy" - reg: Address and length of the register set for the device - <supply-name>-supply: phandle to the regulator device tree node Required supplies are: "vdd" : vdd supply for digital circuit operation "vdda18" : 1.8v high-voltage analog supply "vdda33" : 3.3v high-voltage analog supply - qcom,vdd-voltage-level: This property must be a list of three integer values (no, min, max) where each value represents either a voltage in microvolts or a value corresponding to voltage corner - clocks: a list of phandles to the PHY clocks. Use as per Documentation/devicetree/bindings/clock/clock-bindings.txt - clock-names: Names of the clocks in 1-1 correspondence with the "clocks" property. Required clocks are "ref_clk", "cfg_ahb_clk" and "phy_reset". Example: qusb_phy: qusb@f9b39000 { compatible = "qcom,qusb2phy"; reg = <0xf9b39000 0x17f>; vdd-supply = <&pm8994_s2_corner>; vdda18-supply = <&pm8994_l6>; vdda33-supply = <&pm8994_l24>; qcom,vdd-voltage-level = <1 5 7>; clocks = <&clock_rpm clk_ln_bb_clk>, <&clock_gcc clk_gcc_usb_phy_cfg_ahb2phy_clk>, <&clock_gcc clk_gcc_qusb2_phy_reset>; clock-names = "ref_clk", "cfg_ahb_clk", "phy_reset"; };
drivers/usb/phy/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -250,6 +250,15 @@ config USB_MSM_SSPHY_QMP set for its control sequences, normally paired with newer DWC3-based SuperSpeed controllers. config MSM_QUSB_PHY tristate "MSM QUSB2 PHY Driver" default n help Enable this to support the QUSB2 PHY on MSM chips. This driver supports the high-speed PHY which is usually paired with either the ChipIdea or Synopsys DWC3 USB IPs on MSM SOCs. This driver expects to configure the PHY with a dedicated register I/O memory region. config USB_MV_OTG tristate "Marvell USB OTG support" depends on USB_EHCI_MV && USB_MV_UDC && PM_RUNTIME Loading
drivers/usb/phy/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o obj-$(CONFIG_USB_MSM_HSPHY) += phy-msm-hsusb.o obj-$(CONFIG_USB_MSM_SSPHY) += phy-msm-ssusb.o obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o obj-$(CONFIG_MSM_QUSB_PHY) += phy-msm-qusb.o obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o Loading
drivers/usb/phy/phy-msm-qusb.c 0 → 100644 +276 −0 Original line number Diff line number Diff line /* * Copyright (c) 2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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/module.h> #include <linux/kernel.h> #include <linux/err.h> #include <linux/slab.h> #include <linux/clk.h> #include <linux/clk/msm-clk.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/usb/phy.h> #define QUSB2PHY_PORT_POWERDOWN 0xB4 #define CLAMP_N_EN BIT(5) #define FREEZIO_N BIT(1) #define POWER_DOWN BIT(0) #define QUSB2PHY_PORT_UTMI_CTRL2 0xC4 struct qusb_phy { struct usb_phy phy; void __iomem *base; struct clk *ref_clk; struct clk *cfg_ahb_clk; struct clk *phy_reset; struct regulator *vdd; struct regulator *vdda33; struct regulator *vdda18; int vdd_levels[3]; /* none, low, high */ bool power_enabled; bool clocks_enabled; bool suspended; }; static int qusb_phy_reset(struct usb_phy *phy) { struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); dev_dbg(phy->dev, "%s\n", __func__); clk_reset(qphy->phy_reset, CLK_RESET_ASSERT); usleep(100); clk_reset(qphy->phy_reset, CLK_RESET_DEASSERT); return 0; } static int qusb_phy_enable_power(struct qusb_phy *qphy, bool on) { int ret = 0; dev_dbg(qphy->phy.dev, "%s turn %s regulators\n", __func__, on ? "on" : "off"); if (qphy->power_enabled == on) return 0; if (!on) goto disable_vdda33; ret = regulator_enable(qphy->vdd); if (ret) { dev_err(qphy->phy.dev, "unable to enable vdd\n"); return ret; } ret = regulator_enable(qphy->vdda18); if (ret) { dev_err(qphy->phy.dev, "unable to enable vdda18\n"); goto disable_vdd; } ret = regulator_enable(qphy->vdda33); if (ret) { dev_err(qphy->phy.dev, "unable to enable vdda33\n"); goto disable_vdda18; } qphy->power_enabled = true; return 0; disable_vdda33: regulator_disable(qphy->vdda33); disable_vdda18: regulator_disable(qphy->vdda18); disable_vdd: regulator_disable(qphy->vdd); qphy->power_enabled = false; return ret; } static int qusb_phy_init(struct usb_phy *phy) { struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); dev_dbg(phy->dev, "%s\n", __func__); if (!qphy->clocks_enabled) { clk_prepare_enable(qphy->ref_clk); clk_prepare_enable(qphy->cfg_ahb_clk); qphy->clocks_enabled = true; } /* Disable the PHY */ writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN, qphy->base + QUSB2PHY_PORT_POWERDOWN); /* configure for ULPI mode */ writel_relaxed(0x0, qphy->base + QUSB2PHY_PORT_UTMI_CTRL2); /* ensure above writes are completed before re-enabling PHY */ wmb(); /* Enable the PHY */ writel_relaxed(CLAMP_N_EN | FREEZIO_N, qphy->base + QUSB2PHY_PORT_POWERDOWN); return 0; } static void qusb_phy_shutdown(struct usb_phy *phy) { struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy); dev_dbg(phy->dev, "%s\n", __func__); /* clocks need to be on to access register */ if (!qphy->clocks_enabled) { clk_prepare_enable(qphy->ref_clk); clk_prepare_enable(qphy->cfg_ahb_clk); qphy->clocks_enabled = true; } /* Disable the PHY */ writel_relaxed(CLAMP_N_EN | FREEZIO_N | POWER_DOWN, qphy->base + QUSB2PHY_PORT_POWERDOWN); wmb(); clk_disable_unprepare(qphy->cfg_ahb_clk); clk_disable_unprepare(qphy->ref_clk); qphy->clocks_enabled = false; } static int qusb_phy_probe(struct platform_device *pdev) { struct qusb_phy *qphy; struct device *dev = &pdev->dev; struct resource *res; int ret = 0; qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); if (!qphy) return -ENOMEM; qphy->phy.dev = dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); qphy->base = devm_ioremap_resource(dev, res); if (IS_ERR(qphy->base)) return PTR_ERR(qphy->base); qphy->ref_clk = devm_clk_get(dev, "ref_clk"); if (IS_ERR(qphy->ref_clk)) return PTR_ERR(qphy->ref_clk); clk_set_rate(qphy->ref_clk, 19200000); qphy->phy_reset = devm_clk_get(dev, "cfg_ahb_clk"); if (IS_ERR(qphy->cfg_ahb_clk)) return PTR_ERR(qphy->cfg_ahb_clk); qphy->phy_reset = devm_clk_get(dev, "phy_reset"); if (IS_ERR(qphy->phy_reset)) return PTR_ERR(qphy->phy_reset); ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", (u32 *) qphy->vdd_levels, ARRAY_SIZE(qphy->vdd_levels)); if (ret) { dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); return ret; } qphy->vdd = devm_regulator_get(dev, "vdd"); if (IS_ERR(qphy->vdd)) { dev_err(dev, "unable to get vdd supply\n"); return PTR_ERR(qphy->vdd); } qphy->vdda33 = devm_regulator_get(dev, "vdda33"); if (IS_ERR(qphy->vdda33)) { dev_err(dev, "unable to get vdda33 supply\n"); return PTR_ERR(qphy->vdda33); } qphy->vdda18 = devm_regulator_get(dev, "vdda18"); if (IS_ERR(qphy->vdda18)) { dev_err(dev, "unable to get vdda18 supply\n"); return PTR_ERR(qphy->vdda18); } ret = qusb_phy_enable_power(qphy, true); if (ret) return ret; clk_prepare_enable(qphy->ref_clk); clk_prepare_enable(qphy->cfg_ahb_clk); qphy->clocks_enabled = true; platform_set_drvdata(pdev, qphy); qphy->phy.label = "msm-qusb-phy"; qphy->phy.init = qusb_phy_init; qphy->phy.shutdown = qusb_phy_shutdown; qphy->phy.reset = qusb_phy_reset; qphy->phy.type = USB_PHY_TYPE_USB2; qusb_phy_reset(&qphy->phy); ret = usb_add_phy_dev(&qphy->phy); return ret; } static int qusb_phy_remove(struct platform_device *pdev) { struct qusb_phy *qphy = platform_get_drvdata(pdev); usb_remove_phy(&qphy->phy); if (qphy->clocks_enabled) { clk_disable_unprepare(qphy->cfg_ahb_clk); clk_disable_unprepare(qphy->ref_clk); qphy->clocks_enabled = false; } qusb_phy_enable_power(qphy, false); return 0; } static const struct of_device_id qusb_phy_id_table[] = { { .compatible = "qcom,qusb2phy", }, { }, }; MODULE_DEVICE_TABLE(of, qusb_phy_id_table); static struct platform_driver qusb_phy_driver = { .probe = qusb_phy_probe, .remove = qusb_phy_remove, .driver = { .name = "msm-qusb-phy", .of_match_table = of_match_ptr(qusb_phy_id_table), }, }; module_platform_driver(qusb_phy_driver); MODULE_DESCRIPTION("MSM QUSB2 PHY driver"); MODULE_LICENSE("GPL v2");