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

Kishon Vijay Abraham I kishon at ti.com
Tue Sep 13 22:29:51 PDT 2016



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 at ti.com>
>> Cc: <devicetree at vger.kernel.org>
>> Signed-off-by: Stephen Boyd <stephen.boyd at 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
>>



More information about the linux-arm-kernel mailing list