From mboxrd@z Thu Jan 1 00:00:00 1970 From: Peter Chen Subject: Re: [PATCH v4 22/22] phy: Add support for Qualcomm's USB HS phy Date: Tue, 13 Sep 2016 15:03:58 +0800 Message-ID: <20160913070358.GB30425@b29397-desktop> References: <20160907213519.27340-1-stephen.boyd@linaro.org> <20160907213519.27340-23-stephen.boyd@linaro.org> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Return-path: Received: from mail-pf0-f196.google.com ([209.85.192.196]:35532 "EHLO mail-pf0-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750940AbcIMHEH (ORCPT ); Tue, 13 Sep 2016 03:04:07 -0400 Content-Disposition: inline In-Reply-To: <20160907213519.27340-23-stephen.boyd@linaro.org> Sender: linux-arm-msm-owner@vger.kernel.org List-Id: linux-arm-msm@vger.kernel.org To: Stephen Boyd Cc: linux-usb@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, Andy Gross , Bjorn Andersson , Neil Armstrong , Arnd Bergmann , Felipe Balbi , Peter Chen , Kishon Vijay Abraham I , devicetree@vger.kernel.org, Fabien Lahoudere 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. > Hi Stephen, I am a little puzzled how this driver co-work with chipidea driver. According to nxp IC guys, the ULPI PHY's clock needs to be enabled before access portsc.pts (calling hw_phymode_configure), otherwise, the system will hang. But I find you call hw_phymode_configure before phy->power_on, doesn't your design have this requirement? Besides, you read ulpi id before phy->power_on, how can read work before phy power on? Peter > Cc: Kishon Vijay Abraham I > Cc: > Signed-off-by: Stephen Boyd > --- > .../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: > + 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: > + Definition: Should contain 0 > + > +- clocks: > + Usage: required > + Value type: > + Definition: Should contain clock specifier for the reference and sleep > + clocks > + > +- clock-names: > + Usage: required > + Value type: > + Definition: Should contain "ref" and "sleep" for the reference and sleep > + clocks respectively > + > +- resets: > + Usage: required > + Value type: > + Definition: Should contain the phy and POR resets > + > +- reset-names: > + Usage: required > + Value type: > + Definition: Should contain "phy" and "por" for the phy and POR resets > + respectively > + > +- v3p3-supply: > + Usage: required > + Value type: > + Definition: Should contain a reference to the 3.3V supply > + > +- v1p8-supply: > + Usage: required > + Value type: > + Definition: Should contain a reference to the 1.8V supply > + > +- extcon: > + Usage: optional > + Value type: > + Definition: Should contain the vbus and ID extcons in the first and second > + cells respectively > + > +- qcom,init-seq: > + Usage: optional > + Value type: > + 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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 linux-usb" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Best Regards, Peter Chen From mboxrd@z Thu Jan 1 00:00:00 1970 From: hzpeterchen@gmail.com (Peter Chen) Date: Tue, 13 Sep 2016 15:03:58 +0800 Subject: [PATCH v4 22/22] phy: Add support for Qualcomm's USB HS phy In-Reply-To: <20160907213519.27340-23-stephen.boyd@linaro.org> References: <20160907213519.27340-1-stephen.boyd@linaro.org> <20160907213519.27340-23-stephen.boyd@linaro.org> Message-ID: <20160913070358.GB30425@b29397-desktop> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org 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. > Hi Stephen, I am a little puzzled how this driver co-work with chipidea driver. According to nxp IC guys, the ULPI PHY's clock needs to be enabled before access portsc.pts (calling hw_phymode_configure), otherwise, the system will hang. But I find you call hw_phymode_configure before phy->power_on, doesn't your design have this requirement? Besides, you read ulpi id before phy->power_on, how can read work before phy power on? Peter > Cc: Kishon Vijay Abraham I > Cc: > Signed-off-by: Stephen Boyd > --- > .../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: > + 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: > + Definition: Should contain 0 > + > +- clocks: > + Usage: required > + Value type: > + Definition: Should contain clock specifier for the reference and sleep > + clocks > + > +- clock-names: > + Usage: required > + Value type: > + Definition: Should contain "ref" and "sleep" for the reference and sleep > + clocks respectively > + > +- resets: > + Usage: required > + Value type: > + Definition: Should contain the phy and POR resets > + > +- reset-names: > + Usage: required > + Value type: > + Definition: Should contain "phy" and "por" for the phy and POR resets > + respectively > + > +- v3p3-supply: > + Usage: required > + Value type: > + Definition: Should contain a reference to the 3.3V supply > + > +- v1p8-supply: > + Usage: required > + Value type: > + Definition: Should contain a reference to the 1.8V supply > + > +- extcon: > + Usage: optional > + Value type: > + Definition: Should contain the vbus and ID extcons in the first and second > + cells respectively > + > +- qcom,init-seq: > + Usage: optional > + Value type: > + 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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 linux-usb" in > the body of a message to majordomo at vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Best Regards, Peter Chen