162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2016 Linaro Ltd
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci#include <linux/module.h>
662306a36Sopenharmony_ci#include <linux/ulpi/driver.h>
762306a36Sopenharmony_ci#include <linux/ulpi/regs.h>
862306a36Sopenharmony_ci#include <linux/clk.h>
962306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
1062306a36Sopenharmony_ci#include <linux/of.h>
1162306a36Sopenharmony_ci#include <linux/phy/phy.h>
1262306a36Sopenharmony_ci#include <linux/reset.h>
1362306a36Sopenharmony_ci#include <linux/extcon.h>
1462306a36Sopenharmony_ci#include <linux/notifier.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define ULPI_PWR_CLK_MNG_REG		0x88
1762306a36Sopenharmony_ci# define ULPI_PWR_OTG_COMP_DISABLE	BIT(0)
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define ULPI_MISC_A			0x96
2062306a36Sopenharmony_ci# define ULPI_MISC_A_VBUSVLDEXTSEL	BIT(1)
2162306a36Sopenharmony_ci# define ULPI_MISC_A_VBUSVLDEXT		BIT(0)
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistruct ulpi_seq {
2562306a36Sopenharmony_ci	u8 addr;
2662306a36Sopenharmony_ci	u8 val;
2762306a36Sopenharmony_ci};
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct qcom_usb_hs_phy {
3062306a36Sopenharmony_ci	struct ulpi *ulpi;
3162306a36Sopenharmony_ci	struct phy *phy;
3262306a36Sopenharmony_ci	struct clk *ref_clk;
3362306a36Sopenharmony_ci	struct clk *sleep_clk;
3462306a36Sopenharmony_ci	struct regulator *v1p8;
3562306a36Sopenharmony_ci	struct regulator *v3p3;
3662306a36Sopenharmony_ci	struct reset_control *reset;
3762306a36Sopenharmony_ci	struct ulpi_seq *init_seq;
3862306a36Sopenharmony_ci	struct extcon_dev *vbus_edev;
3962306a36Sopenharmony_ci	struct notifier_block vbus_notify;
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int qcom_usb_hs_phy_set_mode(struct phy *phy,
4362306a36Sopenharmony_ci				    enum phy_mode mode, int submode)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
4662306a36Sopenharmony_ci	u8 addr;
4762306a36Sopenharmony_ci	int ret;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (!uphy->vbus_edev) {
5062306a36Sopenharmony_ci		u8 val = 0;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci		switch (mode) {
5362306a36Sopenharmony_ci		case PHY_MODE_USB_OTG:
5462306a36Sopenharmony_ci		case PHY_MODE_USB_HOST:
5562306a36Sopenharmony_ci			val |= ULPI_INT_IDGRD;
5662306a36Sopenharmony_ci			fallthrough;
5762306a36Sopenharmony_ci		case PHY_MODE_USB_DEVICE:
5862306a36Sopenharmony_ci			val |= ULPI_INT_SESS_VALID;
5962306a36Sopenharmony_ci			break;
6062306a36Sopenharmony_ci		default:
6162306a36Sopenharmony_ci			break;
6262306a36Sopenharmony_ci		}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci		ret = ulpi_write(uphy->ulpi, ULPI_USB_INT_EN_RISE, val);
6562306a36Sopenharmony_ci		if (ret)
6662306a36Sopenharmony_ci			return ret;
6762306a36Sopenharmony_ci		ret = ulpi_write(uphy->ulpi, ULPI_USB_INT_EN_FALL, val);
6862306a36Sopenharmony_ci	} else {
6962306a36Sopenharmony_ci		switch (mode) {
7062306a36Sopenharmony_ci		case PHY_MODE_USB_OTG:
7162306a36Sopenharmony_ci		case PHY_MODE_USB_DEVICE:
7262306a36Sopenharmony_ci			addr = ULPI_SET(ULPI_MISC_A);
7362306a36Sopenharmony_ci			break;
7462306a36Sopenharmony_ci		case PHY_MODE_USB_HOST:
7562306a36Sopenharmony_ci			addr = ULPI_CLR(ULPI_MISC_A);
7662306a36Sopenharmony_ci			break;
7762306a36Sopenharmony_ci		default:
7862306a36Sopenharmony_ci			return -EINVAL;
7962306a36Sopenharmony_ci		}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci		ret = ulpi_write(uphy->ulpi, ULPI_SET(ULPI_PWR_CLK_MNG_REG),
8262306a36Sopenharmony_ci				 ULPI_PWR_OTG_COMP_DISABLE);
8362306a36Sopenharmony_ci		if (ret)
8462306a36Sopenharmony_ci			return ret;
8562306a36Sopenharmony_ci		ret = ulpi_write(uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXTSEL);
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return ret;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic int
9262306a36Sopenharmony_ciqcom_usb_hs_phy_vbus_notifier(struct notifier_block *nb, unsigned long event,
9362306a36Sopenharmony_ci			      void *ptr)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	struct qcom_usb_hs_phy *uphy;
9662306a36Sopenharmony_ci	u8 addr;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	uphy = container_of(nb, struct qcom_usb_hs_phy, vbus_notify);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	if (event)
10162306a36Sopenharmony_ci		addr = ULPI_SET(ULPI_MISC_A);
10262306a36Sopenharmony_ci	else
10362306a36Sopenharmony_ci		addr = ULPI_CLR(ULPI_MISC_A);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return ulpi_write(uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXT);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic int qcom_usb_hs_phy_power_on(struct phy *phy)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
11162306a36Sopenharmony_ci	struct ulpi *ulpi = uphy->ulpi;
11262306a36Sopenharmony_ci	const struct ulpi_seq *seq;
11362306a36Sopenharmony_ci	int ret, state;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	ret = clk_prepare_enable(uphy->ref_clk);
11662306a36Sopenharmony_ci	if (ret)
11762306a36Sopenharmony_ci		return ret;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	ret = clk_prepare_enable(uphy->sleep_clk);
12062306a36Sopenharmony_ci	if (ret)
12162306a36Sopenharmony_ci		goto err_sleep;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	ret = regulator_set_load(uphy->v1p8, 50000);
12462306a36Sopenharmony_ci	if (ret < 0)
12562306a36Sopenharmony_ci		goto err_1p8;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	ret = regulator_enable(uphy->v1p8);
12862306a36Sopenharmony_ci	if (ret)
12962306a36Sopenharmony_ci		goto err_1p8;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	ret = regulator_set_voltage_triplet(uphy->v3p3, 3050000, 3300000,
13262306a36Sopenharmony_ci					    3300000);
13362306a36Sopenharmony_ci	if (ret)
13462306a36Sopenharmony_ci		goto err_3p3;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	ret = regulator_set_load(uphy->v3p3, 50000);
13762306a36Sopenharmony_ci	if (ret < 0)
13862306a36Sopenharmony_ci		goto err_3p3;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	ret = regulator_enable(uphy->v3p3);
14162306a36Sopenharmony_ci	if (ret)
14262306a36Sopenharmony_ci		goto err_3p3;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	for (seq = uphy->init_seq; seq->addr; seq++) {
14562306a36Sopenharmony_ci		ret = ulpi_write(ulpi, ULPI_EXT_VENDOR_SPECIFIC + seq->addr,
14662306a36Sopenharmony_ci				 seq->val);
14762306a36Sopenharmony_ci		if (ret)
14862306a36Sopenharmony_ci			goto err_ulpi;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (uphy->reset) {
15262306a36Sopenharmony_ci		ret = reset_control_reset(uphy->reset);
15362306a36Sopenharmony_ci		if (ret)
15462306a36Sopenharmony_ci			goto err_ulpi;
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (uphy->vbus_edev) {
15862306a36Sopenharmony_ci		state = extcon_get_state(uphy->vbus_edev, EXTCON_USB);
15962306a36Sopenharmony_ci		/* setup initial state */
16062306a36Sopenharmony_ci		qcom_usb_hs_phy_vbus_notifier(&uphy->vbus_notify, state,
16162306a36Sopenharmony_ci					      uphy->vbus_edev);
16262306a36Sopenharmony_ci		ret = extcon_register_notifier(uphy->vbus_edev, EXTCON_USB,
16362306a36Sopenharmony_ci					       &uphy->vbus_notify);
16462306a36Sopenharmony_ci		if (ret)
16562306a36Sopenharmony_ci			goto err_ulpi;
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	return 0;
16962306a36Sopenharmony_cierr_ulpi:
17062306a36Sopenharmony_ci	regulator_disable(uphy->v3p3);
17162306a36Sopenharmony_cierr_3p3:
17262306a36Sopenharmony_ci	regulator_disable(uphy->v1p8);
17362306a36Sopenharmony_cierr_1p8:
17462306a36Sopenharmony_ci	clk_disable_unprepare(uphy->sleep_clk);
17562306a36Sopenharmony_cierr_sleep:
17662306a36Sopenharmony_ci	clk_disable_unprepare(uphy->ref_clk);
17762306a36Sopenharmony_ci	return ret;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic int qcom_usb_hs_phy_power_off(struct phy *phy)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	if (uphy->vbus_edev)
18562306a36Sopenharmony_ci		extcon_unregister_notifier(uphy->vbus_edev, EXTCON_USB,
18662306a36Sopenharmony_ci					   &uphy->vbus_notify);
18762306a36Sopenharmony_ci	regulator_disable(uphy->v3p3);
18862306a36Sopenharmony_ci	regulator_disable(uphy->v1p8);
18962306a36Sopenharmony_ci	clk_disable_unprepare(uphy->sleep_clk);
19062306a36Sopenharmony_ci	clk_disable_unprepare(uphy->ref_clk);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	return 0;
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic const struct phy_ops qcom_usb_hs_phy_ops = {
19662306a36Sopenharmony_ci	.power_on = qcom_usb_hs_phy_power_on,
19762306a36Sopenharmony_ci	.power_off = qcom_usb_hs_phy_power_off,
19862306a36Sopenharmony_ci	.set_mode = qcom_usb_hs_phy_set_mode,
19962306a36Sopenharmony_ci	.owner = THIS_MODULE,
20062306a36Sopenharmony_ci};
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic int qcom_usb_hs_phy_probe(struct ulpi *ulpi)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	struct qcom_usb_hs_phy *uphy;
20562306a36Sopenharmony_ci	struct phy_provider *p;
20662306a36Sopenharmony_ci	struct clk *clk;
20762306a36Sopenharmony_ci	struct regulator *reg;
20862306a36Sopenharmony_ci	struct reset_control *reset;
20962306a36Sopenharmony_ci	int size;
21062306a36Sopenharmony_ci	int ret;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);
21362306a36Sopenharmony_ci	if (!uphy)
21462306a36Sopenharmony_ci		return -ENOMEM;
21562306a36Sopenharmony_ci	ulpi_set_drvdata(ulpi, uphy);
21662306a36Sopenharmony_ci	uphy->ulpi = ulpi;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	size = of_property_count_u8_elems(ulpi->dev.of_node, "qcom,init-seq");
21962306a36Sopenharmony_ci	if (size < 0)
22062306a36Sopenharmony_ci		size = 0;
22162306a36Sopenharmony_ci	uphy->init_seq = devm_kmalloc_array(&ulpi->dev, (size / 2) + 1,
22262306a36Sopenharmony_ci					   sizeof(*uphy->init_seq), GFP_KERNEL);
22362306a36Sopenharmony_ci	if (!uphy->init_seq)
22462306a36Sopenharmony_ci		return -ENOMEM;
22562306a36Sopenharmony_ci	ret = of_property_read_u8_array(ulpi->dev.of_node, "qcom,init-seq",
22662306a36Sopenharmony_ci					(u8 *)uphy->init_seq, size);
22762306a36Sopenharmony_ci	if (ret && size)
22862306a36Sopenharmony_ci		return ret;
22962306a36Sopenharmony_ci	/* NUL terminate */
23062306a36Sopenharmony_ci	uphy->init_seq[size / 2].addr = uphy->init_seq[size / 2].val = 0;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	uphy->ref_clk = clk = devm_clk_get(&ulpi->dev, "ref");
23362306a36Sopenharmony_ci	if (IS_ERR(clk))
23462306a36Sopenharmony_ci		return PTR_ERR(clk);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	uphy->sleep_clk = clk = devm_clk_get(&ulpi->dev, "sleep");
23762306a36Sopenharmony_ci	if (IS_ERR(clk))
23862306a36Sopenharmony_ci		return PTR_ERR(clk);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	uphy->v1p8 = reg = devm_regulator_get(&ulpi->dev, "v1p8");
24162306a36Sopenharmony_ci	if (IS_ERR(reg))
24262306a36Sopenharmony_ci		return PTR_ERR(reg);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	uphy->v3p3 = reg = devm_regulator_get(&ulpi->dev, "v3p3");
24562306a36Sopenharmony_ci	if (IS_ERR(reg))
24662306a36Sopenharmony_ci		return PTR_ERR(reg);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	uphy->reset = reset = devm_reset_control_get(&ulpi->dev, "por");
24962306a36Sopenharmony_ci	if (IS_ERR(reset)) {
25062306a36Sopenharmony_ci		if (PTR_ERR(reset) == -EPROBE_DEFER)
25162306a36Sopenharmony_ci			return PTR_ERR(reset);
25262306a36Sopenharmony_ci		uphy->reset = NULL;
25362306a36Sopenharmony_ci	}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,
25662306a36Sopenharmony_ci				    &qcom_usb_hs_phy_ops);
25762306a36Sopenharmony_ci	if (IS_ERR(uphy->phy))
25862306a36Sopenharmony_ci		return PTR_ERR(uphy->phy);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	uphy->vbus_edev = extcon_get_edev_by_phandle(&ulpi->dev, 0);
26162306a36Sopenharmony_ci	if (IS_ERR(uphy->vbus_edev)) {
26262306a36Sopenharmony_ci		if (PTR_ERR(uphy->vbus_edev) != -ENODEV)
26362306a36Sopenharmony_ci			return PTR_ERR(uphy->vbus_edev);
26462306a36Sopenharmony_ci		uphy->vbus_edev = NULL;
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	uphy->vbus_notify.notifier_call = qcom_usb_hs_phy_vbus_notifier;
26862306a36Sopenharmony_ci	phy_set_drvdata(uphy->phy, uphy);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate);
27162306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(p);
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic const struct of_device_id qcom_usb_hs_phy_match[] = {
27562306a36Sopenharmony_ci	{ .compatible = "qcom,usb-hs-phy", },
27662306a36Sopenharmony_ci	{ }
27762306a36Sopenharmony_ci};
27862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcom_usb_hs_phy_match);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic struct ulpi_driver qcom_usb_hs_phy_driver = {
28162306a36Sopenharmony_ci	.probe = qcom_usb_hs_phy_probe,
28262306a36Sopenharmony_ci	.driver = {
28362306a36Sopenharmony_ci		.name = "qcom_usb_hs_phy",
28462306a36Sopenharmony_ci		.of_match_table = qcom_usb_hs_phy_match,
28562306a36Sopenharmony_ci	},
28662306a36Sopenharmony_ci};
28762306a36Sopenharmony_cimodule_ulpi_driver(qcom_usb_hs_phy_driver);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm USB HS phy");
29062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
291