Re: [PATCH v4 2/2] phy: qualcomm: Add Synopsys High-Speed USB PHY driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

On 19/11/18 4:38 PM, Shawn Guo wrote:
> It adds Synopsys 28nm Femto High-Speed USB PHY driver support, which
> is usually paired with Synopsys DWC3 USB controllers on Qualcomm SoCs.
> 
> Signed-off-by: Shawn Guo <shawn.guo@xxxxxxxxxx>
> ---
>  drivers/phy/qualcomm/Kconfig                  |  10 +
>  drivers/phy/qualcomm/Makefile                 |   1 +
>  .../phy/qualcomm/phy-qcom-usb-hs-snsp-28nm.c  | 535 ++++++++++++++++++
>  3 files changed, 546 insertions(+)
>  create mode 100644 drivers/phy/qualcomm/phy-qcom-usb-hs-snsp-28nm.c
> 
> diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
> index 32f7d34eb784..c7b5ee82895d 100644
> --- a/drivers/phy/qualcomm/Kconfig
> +++ b/drivers/phy/qualcomm/Kconfig
> @@ -82,3 +82,13 @@ config PHY_QCOM_USB_HSIC
>  	select GENERIC_PHY
>  	help
>  	  Support for the USB HSIC ULPI compliant PHY on QCOM chipsets.
> +
> +config PHY_QCOM_USB_HS_SNPS_28NM
> +	tristate "Qualcomm Synopsys 28nm USB HS PHY driver"
> +	depends on ARCH_QCOM || COMPILE_TEST
> +	depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in
> +	select GENERIC_PHY
> +	help
> +	  Enable this to support the Synopsys 28nm Femto USB PHY on Qualcomm
> +	  chips. This driver supports the high-speed PHY which is usually
> +	  paired with either the ChipIdea or Synopsys DWC3 USB IPs on MSM SOCs.
> diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile
> index c56efd3af205..dc238d95b18c 100644
> --- a/drivers/phy/qualcomm/Makefile
> +++ b/drivers/phy/qualcomm/Makefile
> @@ -9,3 +9,4 @@ obj-$(CONFIG_PHY_QCOM_UFS_14NM)		+= phy-qcom-ufs-qmp-14nm.o
>  obj-$(CONFIG_PHY_QCOM_UFS_20NM)		+= phy-qcom-ufs-qmp-20nm.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_QCOM_USB_HS_SNPS_28NM)	+= phy-qcom-usb-hs-snsp-28nm.o
> diff --git a/drivers/phy/qualcomm/phy-qcom-usb-hs-snsp-28nm.c b/drivers/phy/qualcomm/phy-qcom-usb-hs-snsp-28nm.c
> new file mode 100644
> index 000000000000..ee52bb6df6da
> --- /dev/null
> +++ b/drivers/phy/qualcomm/phy-qcom-usb-hs-snsp-28nm.c
> @@ -0,0 +1,535 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2009-2018, Linux Foundation. All rights reserved.
> + * Copyright (c) 2018, Linaro Limited
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/extcon.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/reset.h>
> +#include <linux/slab.h>
> +
> +/* PHY register and bit definitions */
> +#define PHY_CTRL_COMMON0		0x078
> +#define SIDDQ				BIT(2)
> +#define PHY_IRQ_CMD			0x0d0
> +#define PHY_INTR_MASK0			0x0d4
> +#define PHY_INTR_CLEAR0			0x0dc
> +#define DPDM_MASK			0x1e
> +#define DP_1_0				BIT(4)
> +#define DP_0_1				BIT(3)
> +#define DM_1_0				BIT(2)
> +#define DM_0_1				BIT(1)
> +
> +enum hsphy_voltage {
> +	VOL_NONE,
> +	VOL_MIN,
> +	VOL_MAX,
> +	VOL_NUM,
> +};
> +
> +enum hsphy_vreg {
> +	VDD,
> +	VDDA_1P8,
> +	VDDA_3P3,
> +	VREG_NUM,
> +};
> +
> +struct hsphy_init_seq {
> +	int offset;
> +	int val;
> +	int delay;
> +};
> +
> +struct hsphy_data {
> +	const struct hsphy_init_seq *init_seq;
> +	unsigned int init_seq_num;
> +};
> +
> +struct hsphy_priv {
> +	void __iomem *base;
> +	struct clk_bulk_data *clks;
> +	int num_clks;
> +	struct reset_control *phy_reset;
> +	struct reset_control *por_reset;
> +	struct regulator_bulk_data vregs[VREG_NUM];
> +	unsigned int voltages[VREG_NUM][VOL_NUM];
> +	const struct hsphy_data *data;
> +	bool cable_connected;
> +	struct extcon_dev *vbus_edev;
> +	struct notifier_block vbus_notify;
> +	enum phy_mode mode;
> +};
> +
> +static int qcom_snps_hsphy_config_regulators(struct hsphy_priv *priv, int high)
> +{
> +	int old_uV[VREG_NUM];
> +	int min, ret, i;
> +
> +	min = high ? 1 : 0; /* low or none? */
> +
> +	for (i = 0; i < VREG_NUM; i++) {
> +		old_uV[i] = regulator_get_voltage(priv->vregs[i].consumer);
> +		ret = regulator_set_voltage(priv->vregs[i].consumer,
> +					    priv->voltages[i][min],
> +					    priv->voltages[i][VOL_MAX]);
> +		if (ret)
> +			goto roll_back;
> +	}
> +
> +	return 0;
> +
> +roll_back:
> +	for (; i >= 0; i--)
> +		regulator_set_voltage(priv->vregs[i].consumer,
> +				      old_uV[i], old_uV[i]);
> +	return ret;
> +}
> +
> +static int qcom_snps_hsphy_enable_regulators(struct hsphy_priv *priv)
> +{
> +	int ret;
> +
> +	ret = qcom_snps_hsphy_config_regulators(priv, 1);
> +	if (ret)
> +		return ret;
> +
> +	ret = regulator_set_load(priv->vregs[VDDA_1P8].consumer, 19000);
> +	if (ret < 0)
> +		goto unconfig_regulators;
> +
> +	ret = regulator_set_load(priv->vregs[VDDA_3P3].consumer, 16000);
> +	if (ret < 0)
> +		goto unset_1p8_load;
> +
> +	ret = regulator_bulk_enable(VREG_NUM, priv->vregs);
> +	if (ret)
> +		goto unset_3p3_load;
> +
> +	return 0;
> +
> +unset_3p3_load:
> +	regulator_set_load(priv->vregs[VDDA_3P3].consumer, 0);
> +unset_1p8_load:
> +	regulator_set_load(priv->vregs[VDDA_1P8].consumer, 0);
> +unconfig_regulators:
> +	qcom_snps_hsphy_config_regulators(priv, 0);
> +	return ret;
> +}
> +
> +static void qcom_snps_hsphy_disable_regulators(struct hsphy_priv *priv)
> +{
> +	regulator_bulk_disable(VREG_NUM, priv->vregs);
> +	regulator_set_load(priv->vregs[VDDA_1P8].consumer, 0);
> +	regulator_set_load(priv->vregs[VDDA_3P3].consumer, 0);
> +	qcom_snps_hsphy_config_regulators(priv, 0);
> +}
> +
> +static int qcom_snps_hsphy_set_mode(struct phy *phy, enum phy_mode mode)
> +{
> +	struct hsphy_priv *priv = phy_get_drvdata(phy);
> +
> +	priv->mode = mode;
> +
> +	return 0;
> +}
> +
> +static void qcom_snps_hsphy_enable_hv_interrupts(struct hsphy_priv *priv)
> +{
> +	u32 val;
> +
> +	/* Clear any existing interrupts before enabling the interrupts */
> +	val = readb(priv->base + PHY_INTR_CLEAR0);
> +	val |= DPDM_MASK;
> +	writeb(val, priv->base + PHY_INTR_CLEAR0);
> +
> +	writeb(0x0, priv->base + PHY_IRQ_CMD);
> +	usleep_range(200, 220);
> +	writeb(0x1, priv->base + PHY_IRQ_CMD);
> +
> +	/* Make sure the interrupts are cleared */
> +	usleep_range(200, 220);
> +
> +	val = readb(priv->base + PHY_INTR_MASK0);
> +	switch (priv->mode) {
> +	case PHY_MODE_USB_HOST_HS:
> +	case PHY_MODE_USB_HOST_FS:
> +	case PHY_MODE_USB_DEVICE_HS:
> +	case PHY_MODE_USB_DEVICE_FS:
> +		val |= DP_1_0 | DM_0_1;
> +		break;
> +	case PHY_MODE_USB_HOST_LS:
> +	case PHY_MODE_USB_DEVICE_LS:
> +		val |= DP_0_1 | DM_1_0;
> +		break;
> +	default:
> +		/* No device connected */
> +		val |= DP_0_1 | DM_0_1;
> +		break;
> +	}
> +	writeb(val, priv->base + PHY_INTR_MASK0);
> +}
> +
> +static void qcom_snps_hsphy_disable_hv_interrupts(struct hsphy_priv *priv)
> +{
> +	u32 val;
> +
> +	val = readb(priv->base + PHY_INTR_MASK0);
> +	val &= ~DPDM_MASK;
> +	writeb(val, priv->base + PHY_INTR_MASK0);
> +
> +	/* Clear any pending interrupts */
> +	val = readb(priv->base + PHY_INTR_CLEAR0);
> +	val |= DPDM_MASK;
> +	writeb(val, priv->base + PHY_INTR_CLEAR0);
> +
> +	writeb(0x0, priv->base + PHY_IRQ_CMD);
> +	usleep_range(200, 220);
> +
> +	writeb(0x1, priv->base + PHY_IRQ_CMD);
> +	usleep_range(200, 220);
> +}
> +
> +static void qcom_snps_hsphy_enter_retention(struct hsphy_priv *priv)
> +{
> +	u32 val;
> +
> +	val = readb(priv->base + PHY_CTRL_COMMON0);
> +	val |= SIDDQ;
> +	writeb(val, priv->base + PHY_CTRL_COMMON0);
> +}
> +
> +static void qcom_snps_hsphy_exit_retention(struct hsphy_priv *priv)
> +{
> +	u32 val;
> +
> +	val = readb(priv->base + PHY_CTRL_COMMON0);
> +	val &= ~SIDDQ;
> +	writeb(val, priv->base + PHY_CTRL_COMMON0);
> +}
> +
> +static int qcom_snps_hsphy_vbus_notifier(struct notifier_block *nb,
> +					 unsigned long event, void *ptr)
> +{
> +	struct hsphy_priv *priv = container_of(nb, struct hsphy_priv,
> +						    vbus_notify);
> +	priv->cable_connected = !!event;
> +	return 0;
> +}
> +
> +static int qcom_snps_hsphy_power_on(struct phy *phy)
> +{
> +	struct hsphy_priv *priv = phy_get_drvdata(phy);
> +	int ret;
> +
> +	if (priv->cable_connected) {
> +		ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> +		if (ret)
> +			return ret;
> +		qcom_snps_hsphy_disable_hv_interrupts(priv);
> +	} else {
> +		ret = qcom_snps_hsphy_enable_regulators(priv);
> +		if (ret)
> +			return ret;
> +		ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> +		if (ret)
> +			return ret;
> +		qcom_snps_hsphy_exit_retention(priv);
> +	}
> +
> +	return 0;
> +}
> +
> +static int qcom_snps_hsphy_power_off(struct phy *phy)
> +{
> +	struct hsphy_priv *priv = phy_get_drvdata(phy);
> +
> +	if (priv->cable_connected) {
> +		qcom_snps_hsphy_enable_hv_interrupts(priv);
> +		clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> +	} else {
> +		qcom_snps_hsphy_enter_retention(priv);
> +		clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> +		qcom_snps_hsphy_disable_regulators(priv);
> +	}
> +
> +	return 0;
> +}
> +
> +static int qcom_snps_hsphy_reset(struct hsphy_priv *priv)
> +{
> +	int ret;
> +
> +	ret = reset_control_assert(priv->phy_reset);
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(10, 15);
> +
> +	ret = reset_control_deassert(priv->phy_reset);
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(80, 100);
> +
> +	return 0;
> +}
> +
> +static void qcom_snps_hsphy_init_sequence(struct hsphy_priv *priv)
> +{
> +	const struct hsphy_data *data = priv->data;
> +	const struct hsphy_init_seq *seq;
> +	int i;
> +
> +	/* Device match data is optional. */
> +	if (!data)
> +		return;
> +
> +	seq = data->init_seq;
> +
> +	for (i = 0; i < data->init_seq_num; i++, seq++) {
> +		writeb(seq->val, priv->base + seq->offset);
> +		if (seq->delay)
> +			usleep_range(seq->delay, seq->delay + 10);
> +	}
> +
> +	/* Ensure that the above parameter overrides is successful. */
> +	mb();
> +}
> +
> +static int qcom_snps_hsphy_por_reset(struct hsphy_priv *priv)
> +{
> +	int ret;
> +
> +	ret = reset_control_assert(priv->por_reset);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * The Femto PHY is POR reset in the following scenarios.
> +	 *
> +	 * 1. After overriding the parameter registers.
> +	 * 2. Low power mode exit from PHY retention.
> +	 *
> +	 * Ensure that SIDDQ is cleared before bringing the PHY
> +	 * out of reset.
> +	 */
> +	qcom_snps_hsphy_exit_retention(priv);
> +
> +	/*
> +	 * As per databook, 10 usec delay is required between
> +	 * PHY POR assert and de-assert.
> +	 */
> +	usleep_range(10, 20);
> +	ret = reset_control_deassert(priv->por_reset);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * As per databook, it takes 75 usec for PHY to stabilize
> +	 * after the reset.
> +	 */
> +	usleep_range(80, 100);
> +
> +	/* Ensure that RESET operation is completed. */
> +	mb();

How will you ensure the reset operation is complete with a memory barrier? mb
usage here looks incorrect to me.

Thanks
Kishon



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux