summaryrefslogtreecommitdiff
path: root/drivers/phy/qualcomm/phy-qcom-usb-hsic.c
blob: c110563a73cb984d46e8868dc1e7c882aa481899 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
 * 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/phy/phy.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/pinctrl-state.h>
#include <linux/delay.h>
#include <linux/clk.h>

#define ULPI_HSIC_CFG		0x30
#define ULPI_HSIC_IO_CAL	0x33

struct qcom_usb_hsic_phy {
	struct ulpi *ulpi;
	struct phy *phy;
	struct pinctrl *pctl;
	struct clk *phy_clk;
	struct clk *cal_clk;
	struct clk *cal_sleep_clk;
};

static int qcom_usb_hsic_phy_power_on(struct phy *phy)
{
	struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy);
	struct ulpi *ulpi = uphy->ulpi;
	struct pinctrl_state *pins_default;
	int ret;

	ret = clk_prepare_enable(uphy->phy_clk);
	if (ret)
		return ret;

	ret = clk_prepare_enable(uphy->cal_clk);
	if (ret)
		goto err_cal;

	ret = clk_prepare_enable(uphy->cal_sleep_clk);
	if (ret)
		goto err_sleep;

	/* Set periodic calibration interval to ~2.048sec in HSIC_IO_CAL_REG */
	ret = ulpi_write(ulpi, ULPI_HSIC_IO_CAL, 0xff);
	if (ret)
		goto err_ulpi;

	/* Enable periodic IO calibration in HSIC_CFG register */
	ret = ulpi_write(ulpi, ULPI_HSIC_CFG, 0xa8);
	if (ret)
		goto err_ulpi;

	/* Configure pins for HSIC functionality */
	pins_default = pinctrl_lookup_state(uphy->pctl, PINCTRL_STATE_DEFAULT);
	if (IS_ERR(pins_default))
		return PTR_ERR(pins_default);

	ret = pinctrl_select_state(uphy->pctl, pins_default);
	if (ret)
		goto err_ulpi;

	 /* Enable HSIC mode in HSIC_CFG register */
	ret = ulpi_write(ulpi, ULPI_SET(ULPI_HSIC_CFG), 0x01);
	if (ret)
		goto err_ulpi;

	/* Disable auto-resume */
	ret = ulpi_write(ulpi, ULPI_CLR(ULPI_IFC_CTRL),
			 ULPI_IFC_CTRL_AUTORESUME);
	if (ret)
		goto err_ulpi;

	return ret;
err_ulpi:
	clk_disable_unprepare(uphy->cal_sleep_clk);
err_sleep:
	clk_disable_unprepare(uphy->cal_clk);
err_cal:
	clk_disable_unprepare(uphy->phy_clk);
	return ret;
}

static int qcom_usb_hsic_phy_power_off(struct phy *phy)
{
	struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy);

	clk_disable_unprepare(uphy->cal_sleep_clk);
	clk_disable_unprepare(uphy->cal_clk);
	clk_disable_unprepare(uphy->phy_clk);

	return 0;
}

static const struct phy_ops qcom_usb_hsic_phy_ops = {
	.power_on = qcom_usb_hsic_phy_power_on,
	.power_off = qcom_usb_hsic_phy_power_off,
	.owner = THIS_MODULE,
};

static int qcom_usb_hsic_phy_probe(struct ulpi *ulpi)
{
	struct qcom_usb_hsic_phy *uphy;
	struct phy_provider *p;
	struct clk *clk;

	uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);
	if (!uphy)
		return -ENOMEM;
	ulpi_set_drvdata(ulpi, uphy);

	uphy->ulpi = ulpi;
	uphy->pctl = devm_pinctrl_get(&ulpi->dev);
	if (IS_ERR(uphy->pctl))
		return PTR_ERR(uphy->pctl);

	uphy->phy_clk = clk = devm_clk_get(&ulpi->dev, "phy");
	if (IS_ERR(clk))
		return PTR_ERR(clk);

	uphy->cal_clk = clk = devm_clk_get(&ulpi->dev, "cal");
	if (IS_ERR(clk))
		return PTR_ERR(clk);

	uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep");
	if (IS_ERR(clk))
		return PTR_ERR(clk);

	uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,
				    &qcom_usb_hsic_phy_ops);
	if (IS_ERR(uphy->phy))
		return PTR_ERR(uphy->phy);
	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_hsic_phy_match[] = {
	{ .compatible = "qcom,usb-hsic-phy", },
	{ }
};
MODULE_DEVICE_TABLE(of, qcom_usb_hsic_phy_match);

static struct ulpi_driver qcom_usb_hsic_phy_driver = {
	.probe = qcom_usb_hsic_phy_probe,
	.driver = {
		.name = "qcom_usb_hsic_phy",
		.of_match_table = qcom_usb_hsic_phy_match,
	},
};
module_ulpi_driver(qcom_usb_hsic_phy_driver);

MODULE_DESCRIPTION("Qualcomm USB HSIC phy");
MODULE_LICENSE("GPL v2");