|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2009-2018, Linux Foundation. All rights reserved. | 
|  | * Copyright (c) 2018-2020, Linaro Limited | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.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]; | 
|  | const struct hsphy_data *data; | 
|  | enum phy_mode mode; | 
|  | }; | 
|  |  | 
|  | static int qcom_snps_hsphy_set_mode(struct phy *phy, enum phy_mode mode, | 
|  | int submode) | 
|  | { | 
|  | struct hsphy_priv *priv = phy_get_drvdata(phy); | 
|  |  | 
|  | priv->mode = PHY_MODE_INVALID; | 
|  |  | 
|  | if (mode > 0) | 
|  | 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_power_on(struct phy *phy) | 
|  | { | 
|  | struct hsphy_priv *priv = phy_get_drvdata(phy); | 
|  | int ret; | 
|  |  | 
|  | ret = regulator_bulk_enable(VREG_NUM, priv->vregs); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | qcom_snps_hsphy_disable_hv_interrupts(priv); | 
|  | 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); | 
|  |  | 
|  | qcom_snps_hsphy_enter_retention(priv); | 
|  | qcom_snps_hsphy_enable_hv_interrupts(priv); | 
|  | regulator_bulk_disable(VREG_NUM, priv->vregs); | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int qcom_snps_hsphy_init(struct phy *phy) | 
|  | { | 
|  | struct hsphy_priv *priv = phy_get_drvdata(phy); | 
|  | int ret; | 
|  |  | 
|  | ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = qcom_snps_hsphy_reset(priv); | 
|  | if (ret) | 
|  | goto disable_clocks; | 
|  |  | 
|  | qcom_snps_hsphy_init_sequence(priv); | 
|  |  | 
|  | ret = qcom_snps_hsphy_por_reset(priv); | 
|  | if (ret) | 
|  | goto disable_clocks; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | disable_clocks: | 
|  | clk_bulk_disable_unprepare(priv->num_clks, priv->clks); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int qcom_snps_hsphy_exit(struct phy *phy) | 
|  | { | 
|  | struct hsphy_priv *priv = phy_get_drvdata(phy); | 
|  |  | 
|  | clk_bulk_disable_unprepare(priv->num_clks, priv->clks); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct phy_ops qcom_snps_hsphy_ops = { | 
|  | .init = qcom_snps_hsphy_init, | 
|  | .exit = qcom_snps_hsphy_exit, | 
|  | .power_on = qcom_snps_hsphy_power_on, | 
|  | .power_off = qcom_snps_hsphy_power_off, | 
|  | .set_mode = qcom_snps_hsphy_set_mode, | 
|  | .owner = THIS_MODULE, | 
|  | }; | 
|  |  | 
|  | static const char * const qcom_snps_hsphy_clks[] = { | 
|  | "ref", | 
|  | "ahb", | 
|  | "sleep", | 
|  | }; | 
|  |  | 
|  | static int qcom_snps_hsphy_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct phy_provider *provider; | 
|  | struct hsphy_priv *priv; | 
|  | struct phy *phy; | 
|  | int ret; | 
|  | int i; | 
|  |  | 
|  | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | 
|  | if (!priv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | priv->base = devm_platform_ioremap_resource(pdev, 0); | 
|  | if (IS_ERR(priv->base)) | 
|  | return PTR_ERR(priv->base); | 
|  |  | 
|  | priv->num_clks = ARRAY_SIZE(qcom_snps_hsphy_clks); | 
|  | priv->clks = devm_kcalloc(dev, priv->num_clks, sizeof(*priv->clks), | 
|  | GFP_KERNEL); | 
|  | if (!priv->clks) | 
|  | return -ENOMEM; | 
|  |  | 
|  | for (i = 0; i < priv->num_clks; i++) | 
|  | priv->clks[i].id = qcom_snps_hsphy_clks[i]; | 
|  |  | 
|  | ret = devm_clk_bulk_get(dev, priv->num_clks, priv->clks); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | priv->phy_reset = devm_reset_control_get_exclusive(dev, "phy"); | 
|  | if (IS_ERR(priv->phy_reset)) | 
|  | return PTR_ERR(priv->phy_reset); | 
|  |  | 
|  | priv->por_reset = devm_reset_control_get_exclusive(dev, "por"); | 
|  | if (IS_ERR(priv->por_reset)) | 
|  | return PTR_ERR(priv->por_reset); | 
|  |  | 
|  | priv->vregs[VDD].supply = "vdd"; | 
|  | priv->vregs[VDDA_1P8].supply = "vdda1p8"; | 
|  | priv->vregs[VDDA_3P3].supply = "vdda3p3"; | 
|  |  | 
|  | ret = devm_regulator_bulk_get(dev, VREG_NUM, priv->vregs); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | /* Get device match data */ | 
|  | priv->data = device_get_match_data(dev); | 
|  |  | 
|  | phy = devm_phy_create(dev, dev->of_node, &qcom_snps_hsphy_ops); | 
|  | if (IS_ERR(phy)) | 
|  | return PTR_ERR(phy); | 
|  |  | 
|  | phy_set_drvdata(phy, priv); | 
|  |  | 
|  | provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | 
|  | if (IS_ERR(provider)) | 
|  | return PTR_ERR(provider); | 
|  |  | 
|  | ret = regulator_set_load(priv->vregs[VDDA_1P8].consumer, 19000); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regulator_set_load(priv->vregs[VDDA_3P3].consumer, 16000); | 
|  | if (ret < 0) | 
|  | goto unset_1p8_load; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | unset_1p8_load: | 
|  | regulator_set_load(priv->vregs[VDDA_1P8].consumer, 0); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The macro is used to define an initialization sequence.  Each tuple | 
|  | * is meant to program 'value' into phy register at 'offset' with 'delay' | 
|  | * in us followed. | 
|  | */ | 
|  | #define HSPHY_INIT_CFG(o, v, d)	{ .offset = o, .val = v, .delay = d, } | 
|  |  | 
|  | static const struct hsphy_init_seq init_seq_femtophy[] = { | 
|  | HSPHY_INIT_CFG(0xc0, 0x01, 0), | 
|  | HSPHY_INIT_CFG(0xe8, 0x0d, 0), | 
|  | HSPHY_INIT_CFG(0x74, 0x12, 0), | 
|  | HSPHY_INIT_CFG(0x98, 0x63, 0), | 
|  | HSPHY_INIT_CFG(0x9c, 0x03, 0), | 
|  | HSPHY_INIT_CFG(0xa0, 0x1d, 0), | 
|  | HSPHY_INIT_CFG(0xa4, 0x03, 0), | 
|  | HSPHY_INIT_CFG(0x8c, 0x23, 0), | 
|  | HSPHY_INIT_CFG(0x78, 0x08, 0), | 
|  | HSPHY_INIT_CFG(0x7c, 0xdc, 0), | 
|  | HSPHY_INIT_CFG(0x90, 0xe0, 20), | 
|  | HSPHY_INIT_CFG(0x74, 0x10, 0), | 
|  | HSPHY_INIT_CFG(0x90, 0x60, 0), | 
|  | }; | 
|  |  | 
|  | static const struct hsphy_init_seq init_seq_mdm9607[] = { | 
|  | HSPHY_INIT_CFG(0x80, 0x44, 0), | 
|  | HSPHY_INIT_CFG(0x81, 0x38, 0), | 
|  | HSPHY_INIT_CFG(0x82, 0x24, 0), | 
|  | HSPHY_INIT_CFG(0x83, 0x13, 0), | 
|  | }; | 
|  |  | 
|  | static const struct hsphy_data hsphy_data_femtophy = { | 
|  | .init_seq = init_seq_femtophy, | 
|  | .init_seq_num = ARRAY_SIZE(init_seq_femtophy), | 
|  | }; | 
|  |  | 
|  | static const struct hsphy_data hsphy_data_mdm9607 = { | 
|  | .init_seq = init_seq_mdm9607, | 
|  | .init_seq_num = ARRAY_SIZE(init_seq_mdm9607), | 
|  | }; | 
|  |  | 
|  | static const struct of_device_id qcom_snps_hsphy_match[] = { | 
|  | { .compatible = "qcom,usb-hs-28nm-femtophy", .data = &hsphy_data_femtophy, }, | 
|  | { .compatible = "qcom,usb-hs-28nm-mdm9607", .data = &hsphy_data_mdm9607, }, | 
|  | { }, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, qcom_snps_hsphy_match); | 
|  |  | 
|  | static struct platform_driver qcom_snps_hsphy_driver = { | 
|  | .probe = qcom_snps_hsphy_probe, | 
|  | .driver	= { | 
|  | .name = "qcom,usb-hs-28nm-phy", | 
|  | .of_match_table = qcom_snps_hsphy_match, | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(qcom_snps_hsphy_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("Qualcomm 28nm Hi-Speed USB PHY driver"); | 
|  | MODULE_LICENSE("GPL v2"); |