Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e3da0274 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "usb: phy: qusb: Add MSM QUSB2 PHY driver"

parents e609b102 804b1425
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -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";
	};
+9 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+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");