diff mbox

[v4,22/22] phy: Add support for Qualcomm's USB HS phy

Message ID 20160907213519.27340-23-stephen.boyd@linaro.org
State Superseded
Headers show

Commit Message

Stephen Boyd Sept. 7, 2016, 9:35 p.m. UTC
The high-speed phy on qcom SoCs is controlled via the ULPI
viewport.

Cc: Kishon Vijay Abraham I <kishon@ti.com>
Cc: <devicetree@vger.kernel.org>
Signed-off-by: Stephen Boyd <stephen.boyd@linaro.org>

---
 .../devicetree/bindings/phy/qcom,usb-hs-phy.txt    |  83 ++++++
 drivers/phy/Kconfig                                |   8 +
 drivers/phy/Makefile                               |   1 +
 drivers/phy/phy-qcom-usb-hs.c                      | 289 +++++++++++++++++++++
 4 files changed, 381 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt
 create mode 100644 drivers/phy/phy-qcom-usb-hs.c

-- 
2.9.0.rc2.8.ga28705d

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Kishon Vijay Abraham I Sept. 14, 2016, 5:29 a.m. UTC | #1
On Saturday 10 September 2016 05:48 PM, Kishon Vijay Abraham I wrote:
> 

> On Wed, Sep 07, 2016 at 02:35:19PM -0700, Stephen Boyd wrote:

>> The high-speed phy on qcom SoCs is controlled via the ULPI

>> viewport.

>>

>> Cc: Kishon Vijay Abraham I <kishon@ti.com>

>> Cc: <devicetree@vger.kernel.org>

>> Signed-off-by: Stephen Boyd <stephen.boyd@linaro.org>

> 

> merged this and the previous patch to linux-phy.


since there are pending discussions, I'll drop this patch for now.

Thanks
Kishon
> 

> Thanks

> Kishon

> 

>> ---

>>  .../devicetree/bindings/phy/qcom,usb-hs-phy.txt    |  83 ++++++

>>  drivers/phy/Kconfig                                |   8 +

>>  drivers/phy/Makefile                               |   1 +

>>  drivers/phy/phy-qcom-usb-hs.c                      | 289 +++++++++++++++++++++

>>  4 files changed, 381 insertions(+)

>>  create mode 100644 Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt

>>  create mode 100644 drivers/phy/phy-qcom-usb-hs.c

>>

>> diff --git a/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt b/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt

>> new file mode 100644

>> index 000000000000..d7eacd63d06b

>> --- /dev/null

>> +++ b/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt

>> @@ -0,0 +1,83 @@

>> +Qualcomm's USB HS PHY

>> +

>> +PROPERTIES

>> +

>> +- compatible:

>> +    Usage: required

>> +    Value type: <string>

>> +    Definition: Should contain "qcom,usb-hs-phy" and more specifically one of the

>> +                following:

>> +

>> +                        "qcom,usb-hs-phy-apq8064"

>> +                        "qcom,usb-hs-phy-msm8916"

>> +                        "qcom,usb-hs-phy-msm8974"

>> +

>> +- #phy-cells:

>> +    Usage: required

>> +    Value type: <u32>

>> +    Definition: Should contain 0

>> +

>> +- clocks:

>> +    Usage: required

>> +    Value type: <prop-encoded-array>

>> +    Definition: Should contain clock specifier for the reference and sleep

>> +                clocks

>> +

>> +- clock-names:

>> +    Usage: required

>> +    Value type: <stringlist>

>> +    Definition: Should contain "ref" and "sleep" for the reference and sleep

>> +                clocks respectively

>> +

>> +- resets:

>> +    Usage: required

>> +    Value type: <prop-encoded-array>

>> +    Definition: Should contain the phy and POR resets

>> +

>> +- reset-names:

>> +    Usage: required

>> +    Value type: <stringlist>

>> +    Definition: Should contain "phy" and "por" for the phy and POR resets

>> +                respectively

>> +

>> +- v3p3-supply:

>> +    Usage: required

>> +    Value type: <phandle>

>> +    Definition: Should contain a reference to the 3.3V supply

>> +

>> +- v1p8-supply:

>> +    Usage: required

>> +    Value type: <phandle>

>> +    Definition: Should contain a reference to the 1.8V supply

>> +

>> +- extcon:

>> +    Usage: optional

>> +    Value type: <prop-encoded-array>

>> +    Definition: Should contain the vbus and ID extcons in the first and second

>> +                cells respectively

>> +

>> +- qcom,init-seq:

>> +    Usage: optional

>> +    Value type: <u8 array>

>> +    Definition: Should contain a sequence of ULPI register and address pairs to

>> +                program into the ULPI_EXT_VENDOR_SPECIFIC area. This is related

>> +                to Device Mode Eye Diagram test.

>> +

>> +EXAMPLE

>> +

>> +otg: usb-controller {

>> +	ulpi {

>> +		phy {

>> +			compatible = "qcom,usb-hs-phy-msm8974", "qcom,usb-hs-phy";

>> +			#phy-cells = <0>;

>> +			clocks = <&xo_board>, <&gcc GCC_USB2A_PHY_SLEEP_CLK>;

>> +			clock-names = "ref", "sleep";

>> +			resets = <&gcc GCC_USB2A_PHY_BCR>, <&otg 0>;

>> +			reset-names = "phy", "por";

>> +			v3p3-supply = <&pm8941_l24>;

>> +			v1p8-supply = <&pm8941_l6>;

>> +			extcon = <&smbb>, <&usb_id>;

>> +			qcom,init-seq = /bits/ 8 <0x81 0x63>;

>> +		};

>> +	};

>> +};

>> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig

>> index 830c443eeabf..ee0ec021a98c 100644

>> --- a/drivers/phy/Kconfig

>> +++ b/drivers/phy/Kconfig

>> @@ -417,6 +417,14 @@ config PHY_QCOM_UFS

>>  	help

>>  	  Support for UFS PHY on QCOM chipsets.

>>  

>> +config PHY_QCOM_USB_HS

>> +	tristate "Qualcomm USB HS PHY module"

>> +	depends on USB_ULPI_BUS

>> +	select GENERIC_PHY

>> +	help

>> +	  Support for the USB high-speed ULPI compliant phy on Qualcomm

>> +	  chipsets.

>> +

>>  config PHY_QCOM_USB_HSIC

>>  	tristate "Qualcomm USB HSIC ULPI PHY module"

>>  	depends on USB_ULPI_BUS

>> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile

>> index 5422f543d17d..31c84faa07fa 100644

>> --- a/drivers/phy/Makefile

>> +++ b/drivers/phy/Makefile

>> @@ -50,6 +50,7 @@ obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o

>>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o

>>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o

>>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-14nm.o

>> +obj-$(CONFIG_PHY_QCOM_USB_HS) 		+= phy-qcom-usb-hs.o

>>  obj-$(CONFIG_PHY_QCOM_USB_HSIC) 	+= phy-qcom-usb-hsic.o

>>  obj-$(CONFIG_PHY_TUSB1210)		+= phy-tusb1210.o

>>  obj-$(CONFIG_PHY_BRCM_SATA)		+= phy-brcm-sata.o

>> diff --git a/drivers/phy/phy-qcom-usb-hs.c b/drivers/phy/phy-qcom-usb-hs.c

>> new file mode 100644

>> index 000000000000..73fb4b49a8e1

>> --- /dev/null

>> +++ b/drivers/phy/phy-qcom-usb-hs.c

>> @@ -0,0 +1,289 @@

>> +/**

>> + * Copyright (C) 2016 Linaro Ltd

>> + *

>> + * This program is free software; you can redistribute it and/or modify

>> + * it under the terms of the GNU General Public License version 2 as

>> + * published by the Free Software Foundation.

>> + */

>> +#include <linux/module.h>

>> +#include <linux/ulpi/driver.h>

>> +#include <linux/ulpi/regs.h>

>> +#include <linux/clk.h>

>> +#include <linux/regulator/consumer.h>

>> +#include <linux/of_device.h>

>> +#include <linux/reset.h>

>> +#include <linux/extcon.h>

>> +#include <linux/notifier.h>

>> +#include <linux/usb/of.h>

>> +

>> +#include "ulpi_phy.h"

>> +

>> +#define ULPI_PWR_CLK_MNG_REG		0x88

>> +# define ULPI_PWR_OTG_COMP_DISABLE	BIT(0)

>> +

>> +#define ULPI_MISC_A			0x96

>> +# define ULPI_MISC_A_VBUSVLDEXTSEL	BIT(1)

>> +# define ULPI_MISC_A_VBUSVLDEXT		BIT(0)

>> +

>> +

>> +struct ulpi_seq {

>> +	u8 addr;

>> +	u8 val;

>> +};

>> +

>> +struct qcom_usb_hs_phy {

>> +	struct ulpi *ulpi;

>> +	struct phy *phy;

>> +	struct clk *ref_clk;

>> +	struct clk *sleep_clk;

>> +	struct regulator *v1p8;

>> +	struct regulator *v3p3;

>> +	struct reset_control *reset;

>> +	struct ulpi_seq *init_seq;

>> +	struct notifier_block vbus_notify;

>> +	struct extcon_dev *vbus_edev;

>> +	struct extcon_dev *id_edev;

>> +	enum usb_dr_mode dr_mode;

>> +};

>> +

>> +static int

>> +qcom_usb_hs_phy_vbus_notifier(struct notifier_block *nb, unsigned long event,

>> +			      void *ptr)

>> +{

>> +	struct qcom_usb_hs_phy *uphy;

>> +	int is_host;

>> +	u8 addr;

>> +

>> +	uphy = container_of(nb, struct qcom_usb_hs_phy, vbus_notify);

>> +	is_host = extcon_get_cable_state_(uphy->id_edev, EXTCON_USB_HOST);

>> +	if (is_host < 0)

>> +		is_host = 0; /* No id event means always a peripheral */

>> +

>> +	if (event && !is_host)

>> +		addr = ULPI_SET(ULPI_MISC_A);

>> +	else

>> +		addr = ULPI_CLR(ULPI_MISC_A);

>> +

>> +	return ulpi_write(uphy->ulpi, addr,

>> +			  ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT);

>> +}

>> +

>> +static int qcom_usb_hs_phy_power_on(struct phy *phy)

>> +{

>> +	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);

>> +	struct ulpi *ulpi = uphy->ulpi;

>> +	const struct ulpi_seq *seq;

>> +	int ret, state;

>> +

>> +	ret = clk_prepare_enable(uphy->ref_clk);

>> +	if (ret)

>> +		return ret;

>> +

>> +	ret = clk_prepare_enable(uphy->sleep_clk);

>> +	if (ret)

>> +		goto err_sleep;

>> +

>> +	ret = regulator_set_voltage(uphy->v1p8, 1800000, 1800000);

>> +	if (ret)

>> +		goto err_1p8;

>> +

>> +	ret = regulator_set_load(uphy->v1p8, 50000);

>> +	if (ret < 0)

>> +		goto err_1p8;

>> +

>> +	ret = regulator_enable(uphy->v1p8);

>> +	if (ret)

>> +		goto err_1p8;

>> +

>> +	ret = regulator_set_voltage_triplet(uphy->v3p3, 3050000, 3300000,

>> +					    3300000);

>> +	if (ret)

>> +		goto err_3p3;

>> +

>> +	ret = regulator_set_load(uphy->v3p3, 50000);

>> +	if (ret < 0)

>> +		goto err_3p3;

>> +

>> +	ret = regulator_enable(uphy->v3p3);

>> +	if (ret)

>> +		goto err_3p3;

>> +

>> +	for (seq = uphy->init_seq; seq->addr; seq++) {

>> +		ret = ulpi_write(ulpi, seq->addr, seq->val);

>> +		if (ret)

>> +			goto err_ulpi;

>> +	}

>> +

>> +	if (uphy->reset) {

>> +		ret = reset_control_reset(uphy->reset);

>> +		if (ret)

>> +			goto err_ulpi;

>> +	}

>> +

>> +	if (uphy->vbus_edev) {

>> +		ulpi_write(ulpi, ULPI_SET(ULPI_PWR_CLK_MNG_REG),

>> +			   ULPI_PWR_OTG_COMP_DISABLE);

>> +		state = extcon_get_cable_state_(uphy->vbus_edev, EXTCON_USB);

>> +		/* setup initial state */

>> +		qcom_usb_hs_phy_vbus_notifier(&uphy->vbus_notify, state,

>> +					      uphy->vbus_edev);

>> +		ret = extcon_register_notifier(uphy->vbus_edev, EXTCON_USB,

>> +						&uphy->vbus_notify);

>> +		if (ret)

>> +			return ret;

>> +	} else {

>> +		u8 val;

>> +

>> +		switch (uphy->dr_mode) {

>> +		case USB_DR_MODE_OTG:

>> +			val = ULPI_INT_IDGRD;

>> +		case USB_DR_MODE_PERIPHERAL:

>> +			val |= ULPI_INT_SESS_VALID;

>> +			break;

>> +		default:

>> +			val = 0;

>> +		}

>> +

>> +		ret = ulpi_write(ulpi, ULPI_USB_INT_EN_RISE, val);

>> +		if (ret)

>> +			goto err_ulpi;

>> +		ret = ulpi_write(ulpi, ULPI_USB_INT_EN_FALL, val);

>> +		if (ret)

>> +			goto err_ulpi;

>> +	}

>> +

>> +	return 0;

>> +err_ulpi:

>> +	regulator_disable(uphy->v3p3);

>> +err_3p3:

>> +	regulator_disable(uphy->v1p8);

>> +err_1p8:

>> +	clk_disable_unprepare(uphy->sleep_clk);

>> +err_sleep:

>> +	clk_disable_unprepare(uphy->ref_clk);

>> +	return ret;

>> +}

>> +

>> +static int qcom_usb_hs_phy_power_off(struct phy *phy)

>> +{

>> +	int ret;

>> +	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);

>> +

>> +	if (uphy->vbus_edev) {

>> +		ret = extcon_unregister_notifier(uphy->vbus_edev, EXTCON_USB,

>> +						 &uphy->vbus_notify);

>> +		if (ret)

>> +			return ret;

>> +	}

>> +

>> +	regulator_disable(uphy->v3p3);

>> +	regulator_disable(uphy->v1p8);

>> +	clk_disable_unprepare(uphy->sleep_clk);

>> +	clk_disable_unprepare(uphy->ref_clk);

>> +

>> +	return 0;

>> +}

>> +

>> +static const struct phy_ops qcom_usb_hs_phy_ops = {

>> +	.power_on = qcom_usb_hs_phy_power_on,

>> +	.power_off = qcom_usb_hs_phy_power_off,

>> +	.owner = THIS_MODULE,

>> +};

>> +

>> +static int qcom_usb_hs_phy_probe(struct ulpi *ulpi)

>> +{

>> +	struct qcom_usb_hs_phy *uphy;

>> +	struct phy_provider *p;

>> +	struct clk *clk;

>> +	struct regulator *reg;

>> +	struct reset_control *reset;

>> +	int size;

>> +	int ret;

>> +

>> +	uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);

>> +	if (!uphy)

>> +		return -ENOMEM;

>> +	ulpi_set_drvdata(ulpi, uphy);

>> +	uphy->ulpi = ulpi;

>> +	uphy->dr_mode = of_usb_get_dr_mode_by_phy(ulpi->dev.of_node, -1);

>> +

>> +	size = of_property_count_u8_elems(ulpi->dev.of_node, "qcom,init-seq");

>> +	if (size < 0)

>> +		size = 0;

>> +	uphy->init_seq = devm_kmalloc_array(&ulpi->dev, (size / 2) + 1,

>> +					   sizeof(*uphy->init_seq), GFP_KERNEL);

>> +	if (!uphy->init_seq)

>> +		return -ENOMEM;

>> +	ret = of_property_read_u8_array(ulpi->dev.of_node, "qcom,init-seq",

>> +					(u8 *)uphy->init_seq, size);

>> +	if (ret && size)

>> +		return ret;

>> +	/* NUL terminate */

>> +	uphy->init_seq[size / 2].addr = uphy->init_seq[size / 2].val = 0;

>> +

>> +	uphy->ref_clk = clk = devm_clk_get(&ulpi->dev, "ref");

>> +	if (IS_ERR(clk))

>> +		return PTR_ERR(clk);

>> +

>> +	uphy->sleep_clk = clk = devm_clk_get(&ulpi->dev, "sleep");

>> +	if (IS_ERR(clk))

>> +		return PTR_ERR(clk);

>> +

>> +	uphy->v1p8 = reg = devm_regulator_get(&ulpi->dev, "v1p8");

>> +	if (IS_ERR(reg))

>> +		return PTR_ERR(reg);

>> +

>> +	uphy->v3p3 = reg = devm_regulator_get(&ulpi->dev, "v3p3");

>> +	if (IS_ERR(reg))

>> +		return PTR_ERR(reg);

>> +

>> +	uphy->reset = reset = devm_reset_control_get(&ulpi->dev, "por");

>> +	if (IS_ERR(reset)) {

>> +		if (PTR_ERR(reset) == -EPROBE_DEFER)

>> +			return PTR_ERR(reset);

>> +		uphy->reset = NULL;

>> +	}

>> +

>> +	uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,

>> +				    &qcom_usb_hs_phy_ops);

>> +	if (IS_ERR(uphy->phy))

>> +		return PTR_ERR(uphy->phy);

>> +

>> +	uphy->vbus_edev = extcon_get_edev_by_phandle(&ulpi->dev, 0);

>> +	if (IS_ERR(uphy->vbus_edev)) {

>> +		if (PTR_ERR(uphy->vbus_edev) != -ENODEV)

>> +			return PTR_ERR(uphy->vbus_edev);

>> +		uphy->vbus_edev = NULL;

>> +	}

>> +

>> +	uphy->id_edev = extcon_get_edev_by_phandle(&ulpi->dev, 1);

>> +	if (IS_ERR(uphy->id_edev)) {

>> +		if (PTR_ERR(uphy->id_edev) != -ENODEV)

>> +			return PTR_ERR(uphy->id_edev);

>> +		uphy->id_edev = NULL;

>> +	}

>> +

>> +	uphy->vbus_notify.notifier_call = qcom_usb_hs_phy_vbus_notifier;

>> +	phy_set_drvdata(uphy->phy, uphy);

>> +

>> +	p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate);

>> +	return PTR_ERR_OR_ZERO(p);

>> +}

>> +

>> +static const struct of_device_id qcom_usb_hs_phy_match[] = {

>> +	{ .compatible = "qcom,usb-hs-phy", },

>> +	{ }

>> +};

>> +MODULE_DEVICE_TABLE(of, qcom_usb_hs_phy_match);

>> +

>> +static struct ulpi_driver qcom_usb_hs_phy_driver = {

>> +	.probe = qcom_usb_hs_phy_probe,

>> +	.driver = {

>> +		.name = "qcom_usb_hs_phy",

>> +		.of_match_table = qcom_usb_hs_phy_match,

>> +	},

>> +};

>> +module_ulpi_driver(qcom_usb_hs_phy_driver);

>> +

>> +MODULE_DESCRIPTION("Qualcomm USB HS phy");

>> +MODULE_LICENSE("GPL v2");

>> -- 

>> 2.9.0.rc2.8.ga28705d

>>

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt b/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt
new file mode 100644
index 000000000000..d7eacd63d06b
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt
@@ -0,0 +1,83 @@ 
+Qualcomm's USB HS PHY
+
+PROPERTIES
+
+- compatible:
+    Usage: required
+    Value type: <string>
+    Definition: Should contain "qcom,usb-hs-phy" and more specifically one of the
+                following:
+
+                        "qcom,usb-hs-phy-apq8064"
+                        "qcom,usb-hs-phy-msm8916"
+                        "qcom,usb-hs-phy-msm8974"
+
+- #phy-cells:
+    Usage: required
+    Value type: <u32>
+    Definition: Should contain 0
+
+- clocks:
+    Usage: required
+    Value type: <prop-encoded-array>
+    Definition: Should contain clock specifier for the reference and sleep
+                clocks
+
+- clock-names:
+    Usage: required
+    Value type: <stringlist>
+    Definition: Should contain "ref" and "sleep" for the reference and sleep
+                clocks respectively
+
+- resets:
+    Usage: required
+    Value type: <prop-encoded-array>
+    Definition: Should contain the phy and POR resets
+
+- reset-names:
+    Usage: required
+    Value type: <stringlist>
+    Definition: Should contain "phy" and "por" for the phy and POR resets
+                respectively
+
+- v3p3-supply:
+    Usage: required
+    Value type: <phandle>
+    Definition: Should contain a reference to the 3.3V supply
+
+- v1p8-supply:
+    Usage: required
+    Value type: <phandle>
+    Definition: Should contain a reference to the 1.8V supply
+
+- extcon:
+    Usage: optional
+    Value type: <prop-encoded-array>
+    Definition: Should contain the vbus and ID extcons in the first and second
+                cells respectively
+
+- qcom,init-seq:
+    Usage: optional
+    Value type: <u8 array>
+    Definition: Should contain a sequence of ULPI register and address pairs to
+                program into the ULPI_EXT_VENDOR_SPECIFIC area. This is related
+                to Device Mode Eye Diagram test.
+
+EXAMPLE
+
+otg: usb-controller {
+	ulpi {
+		phy {
+			compatible = "qcom,usb-hs-phy-msm8974", "qcom,usb-hs-phy";
+			#phy-cells = <0>;
+			clocks = <&xo_board>, <&gcc GCC_USB2A_PHY_SLEEP_CLK>;
+			clock-names = "ref", "sleep";
+			resets = <&gcc GCC_USB2A_PHY_BCR>, <&otg 0>;
+			reset-names = "phy", "por";
+			v3p3-supply = <&pm8941_l24>;
+			v1p8-supply = <&pm8941_l6>;
+			extcon = <&smbb>, <&usb_id>;
+			qcom,init-seq = /bits/ 8 <0x81 0x63>;
+		};
+	};
+};
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 830c443eeabf..ee0ec021a98c 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -417,6 +417,14 @@  config PHY_QCOM_UFS
 	help
 	  Support for UFS PHY on QCOM chipsets.
 
+config PHY_QCOM_USB_HS
+	tristate "Qualcomm USB HS PHY module"
+	depends on USB_ULPI_BUS
+	select GENERIC_PHY
+	help
+	  Support for the USB high-speed ULPI compliant phy on Qualcomm
+	  chipsets.
+
 config PHY_QCOM_USB_HSIC
 	tristate "Qualcomm USB HSIC ULPI PHY module"
 	depends on USB_ULPI_BUS
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 5422f543d17d..31c84faa07fa 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -50,6 +50,7 @@  obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-14nm.o
+obj-$(CONFIG_PHY_QCOM_USB_HS) 		+= phy-qcom-usb-hs.o
 obj-$(CONFIG_PHY_QCOM_USB_HSIC) 	+= phy-qcom-usb-hsic.o
 obj-$(CONFIG_PHY_TUSB1210)		+= phy-tusb1210.o
 obj-$(CONFIG_PHY_BRCM_SATA)		+= phy-brcm-sata.o
diff --git a/drivers/phy/phy-qcom-usb-hs.c b/drivers/phy/phy-qcom-usb-hs.c
new file mode 100644
index 000000000000..73fb4b49a8e1
--- /dev/null
+++ b/drivers/phy/phy-qcom-usb-hs.c
@@ -0,0 +1,289 @@ 
+/**
+ * Copyright (C) 2016 Linaro Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/module.h>
+#include <linux/ulpi/driver.h>
+#include <linux/ulpi/regs.h>
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_device.h>
+#include <linux/reset.h>
+#include <linux/extcon.h>
+#include <linux/notifier.h>
+#include <linux/usb/of.h>
+
+#include "ulpi_phy.h"
+
+#define ULPI_PWR_CLK_MNG_REG		0x88
+# define ULPI_PWR_OTG_COMP_DISABLE	BIT(0)
+
+#define ULPI_MISC_A			0x96
+# define ULPI_MISC_A_VBUSVLDEXTSEL	BIT(1)
+# define ULPI_MISC_A_VBUSVLDEXT		BIT(0)
+
+
+struct ulpi_seq {
+	u8 addr;
+	u8 val;
+};
+
+struct qcom_usb_hs_phy {
+	struct ulpi *ulpi;
+	struct phy *phy;
+	struct clk *ref_clk;
+	struct clk *sleep_clk;
+	struct regulator *v1p8;
+	struct regulator *v3p3;
+	struct reset_control *reset;
+	struct ulpi_seq *init_seq;
+	struct notifier_block vbus_notify;
+	struct extcon_dev *vbus_edev;
+	struct extcon_dev *id_edev;
+	enum usb_dr_mode dr_mode;
+};
+
+static int
+qcom_usb_hs_phy_vbus_notifier(struct notifier_block *nb, unsigned long event,
+			      void *ptr)
+{
+	struct qcom_usb_hs_phy *uphy;
+	int is_host;
+	u8 addr;
+
+	uphy = container_of(nb, struct qcom_usb_hs_phy, vbus_notify);
+	is_host = extcon_get_cable_state_(uphy->id_edev, EXTCON_USB_HOST);
+	if (is_host < 0)
+		is_host = 0; /* No id event means always a peripheral */
+
+	if (event && !is_host)
+		addr = ULPI_SET(ULPI_MISC_A);
+	else
+		addr = ULPI_CLR(ULPI_MISC_A);
+
+	return ulpi_write(uphy->ulpi, addr,
+			  ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT);
+}
+
+static int qcom_usb_hs_phy_power_on(struct phy *phy)
+{
+	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
+	struct ulpi *ulpi = uphy->ulpi;
+	const struct ulpi_seq *seq;
+	int ret, state;
+
+	ret = clk_prepare_enable(uphy->ref_clk);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(uphy->sleep_clk);
+	if (ret)
+		goto err_sleep;
+
+	ret = regulator_set_voltage(uphy->v1p8, 1800000, 1800000);
+	if (ret)
+		goto err_1p8;
+
+	ret = regulator_set_load(uphy->v1p8, 50000);
+	if (ret < 0)
+		goto err_1p8;
+
+	ret = regulator_enable(uphy->v1p8);
+	if (ret)
+		goto err_1p8;
+
+	ret = regulator_set_voltage_triplet(uphy->v3p3, 3050000, 3300000,
+					    3300000);
+	if (ret)
+		goto err_3p3;
+
+	ret = regulator_set_load(uphy->v3p3, 50000);
+	if (ret < 0)
+		goto err_3p3;
+
+	ret = regulator_enable(uphy->v3p3);
+	if (ret)
+		goto err_3p3;
+
+	for (seq = uphy->init_seq; seq->addr; seq++) {
+		ret = ulpi_write(ulpi, seq->addr, seq->val);
+		if (ret)
+			goto err_ulpi;
+	}
+
+	if (uphy->reset) {
+		ret = reset_control_reset(uphy->reset);
+		if (ret)
+			goto err_ulpi;
+	}
+
+	if (uphy->vbus_edev) {
+		ulpi_write(ulpi, ULPI_SET(ULPI_PWR_CLK_MNG_REG),
+			   ULPI_PWR_OTG_COMP_DISABLE);
+		state = extcon_get_cable_state_(uphy->vbus_edev, EXTCON_USB);
+		/* setup initial state */
+		qcom_usb_hs_phy_vbus_notifier(&uphy->vbus_notify, state,
+					      uphy->vbus_edev);
+		ret = extcon_register_notifier(uphy->vbus_edev, EXTCON_USB,
+						&uphy->vbus_notify);
+		if (ret)
+			return ret;
+	} else {
+		u8 val;
+
+		switch (uphy->dr_mode) {
+		case USB_DR_MODE_OTG:
+			val = ULPI_INT_IDGRD;
+		case USB_DR_MODE_PERIPHERAL:
+			val |= ULPI_INT_SESS_VALID;
+			break;
+		default:
+			val = 0;
+		}
+
+		ret = ulpi_write(ulpi, ULPI_USB_INT_EN_RISE, val);
+		if (ret)
+			goto err_ulpi;
+		ret = ulpi_write(ulpi, ULPI_USB_INT_EN_FALL, val);
+		if (ret)
+			goto err_ulpi;
+	}
+
+	return 0;
+err_ulpi:
+	regulator_disable(uphy->v3p3);
+err_3p3:
+	regulator_disable(uphy->v1p8);
+err_1p8:
+	clk_disable_unprepare(uphy->sleep_clk);
+err_sleep:
+	clk_disable_unprepare(uphy->ref_clk);
+	return ret;
+}
+
+static int qcom_usb_hs_phy_power_off(struct phy *phy)
+{
+	int ret;
+	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
+
+	if (uphy->vbus_edev) {
+		ret = extcon_unregister_notifier(uphy->vbus_edev, EXTCON_USB,
+						 &uphy->vbus_notify);
+		if (ret)
+			return ret;
+	}
+
+	regulator_disable(uphy->v3p3);
+	regulator_disable(uphy->v1p8);
+	clk_disable_unprepare(uphy->sleep_clk);
+	clk_disable_unprepare(uphy->ref_clk);
+
+	return 0;
+}
+
+static const struct phy_ops qcom_usb_hs_phy_ops = {
+	.power_on = qcom_usb_hs_phy_power_on,
+	.power_off = qcom_usb_hs_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static int qcom_usb_hs_phy_probe(struct ulpi *ulpi)
+{
+	struct qcom_usb_hs_phy *uphy;
+	struct phy_provider *p;
+	struct clk *clk;
+	struct regulator *reg;
+	struct reset_control *reset;
+	int size;
+	int ret;
+
+	uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);
+	if (!uphy)
+		return -ENOMEM;
+	ulpi_set_drvdata(ulpi, uphy);
+	uphy->ulpi = ulpi;
+	uphy->dr_mode = of_usb_get_dr_mode_by_phy(ulpi->dev.of_node, -1);
+
+	size = of_property_count_u8_elems(ulpi->dev.of_node, "qcom,init-seq");
+	if (size < 0)
+		size = 0;
+	uphy->init_seq = devm_kmalloc_array(&ulpi->dev, (size / 2) + 1,
+					   sizeof(*uphy->init_seq), GFP_KERNEL);
+	if (!uphy->init_seq)
+		return -ENOMEM;
+	ret = of_property_read_u8_array(ulpi->dev.of_node, "qcom,init-seq",
+					(u8 *)uphy->init_seq, size);
+	if (ret && size)
+		return ret;
+	/* NUL terminate */
+	uphy->init_seq[size / 2].addr = uphy->init_seq[size / 2].val = 0;
+
+	uphy->ref_clk = clk = devm_clk_get(&ulpi->dev, "ref");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	uphy->sleep_clk = clk = devm_clk_get(&ulpi->dev, "sleep");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	uphy->v1p8 = reg = devm_regulator_get(&ulpi->dev, "v1p8");
+	if (IS_ERR(reg))
+		return PTR_ERR(reg);
+
+	uphy->v3p3 = reg = devm_regulator_get(&ulpi->dev, "v3p3");
+	if (IS_ERR(reg))
+		return PTR_ERR(reg);
+
+	uphy->reset = reset = devm_reset_control_get(&ulpi->dev, "por");
+	if (IS_ERR(reset)) {
+		if (PTR_ERR(reset) == -EPROBE_DEFER)
+			return PTR_ERR(reset);
+		uphy->reset = NULL;
+	}
+
+	uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,
+				    &qcom_usb_hs_phy_ops);
+	if (IS_ERR(uphy->phy))
+		return PTR_ERR(uphy->phy);
+
+	uphy->vbus_edev = extcon_get_edev_by_phandle(&ulpi->dev, 0);
+	if (IS_ERR(uphy->vbus_edev)) {
+		if (PTR_ERR(uphy->vbus_edev) != -ENODEV)
+			return PTR_ERR(uphy->vbus_edev);
+		uphy->vbus_edev = NULL;
+	}
+
+	uphy->id_edev = extcon_get_edev_by_phandle(&ulpi->dev, 1);
+	if (IS_ERR(uphy->id_edev)) {
+		if (PTR_ERR(uphy->id_edev) != -ENODEV)
+			return PTR_ERR(uphy->id_edev);
+		uphy->id_edev = NULL;
+	}
+
+	uphy->vbus_notify.notifier_call = qcom_usb_hs_phy_vbus_notifier;
+	phy_set_drvdata(uphy->phy, uphy);
+
+	p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate);
+	return PTR_ERR_OR_ZERO(p);
+}
+
+static const struct of_device_id qcom_usb_hs_phy_match[] = {
+	{ .compatible = "qcom,usb-hs-phy", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, qcom_usb_hs_phy_match);
+
+static struct ulpi_driver qcom_usb_hs_phy_driver = {
+	.probe = qcom_usb_hs_phy_probe,
+	.driver = {
+		.name = "qcom_usb_hs_phy",
+		.of_match_table = qcom_usb_hs_phy_match,
+	},
+};
+module_ulpi_driver(qcom_usb_hs_phy_driver);
+
+MODULE_DESCRIPTION("Qualcomm USB HS phy");
+MODULE_LICENSE("GPL v2");