1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2016 Linaro Ltd |
4 | */ |
5 | #include <linux/module.h> |
6 | #include <linux/ulpi/driver.h> |
7 | #include <linux/ulpi/regs.h> |
8 | #include <linux/phy/phy.h> |
9 | #include <linux/pinctrl/consumer.h> |
10 | #include <linux/pinctrl/pinctrl-state.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/clk.h> |
13 | |
14 | #define ULPI_HSIC_CFG 0x30 |
15 | #define ULPI_HSIC_IO_CAL 0x33 |
16 | |
17 | struct qcom_usb_hsic_phy { |
18 | struct ulpi *ulpi; |
19 | struct phy *phy; |
20 | struct pinctrl *pctl; |
21 | struct clk *phy_clk; |
22 | struct clk *cal_clk; |
23 | struct clk *cal_sleep_clk; |
24 | }; |
25 | |
26 | static int qcom_usb_hsic_phy_power_on(struct phy *phy) |
27 | { |
28 | struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy); |
29 | struct ulpi *ulpi = uphy->ulpi; |
30 | struct pinctrl_state *pins_default; |
31 | int ret; |
32 | |
33 | ret = clk_prepare_enable(clk: uphy->phy_clk); |
34 | if (ret) |
35 | return ret; |
36 | |
37 | ret = clk_prepare_enable(clk: uphy->cal_clk); |
38 | if (ret) |
39 | goto err_cal; |
40 | |
41 | ret = clk_prepare_enable(clk: uphy->cal_sleep_clk); |
42 | if (ret) |
43 | goto err_sleep; |
44 | |
45 | /* Set periodic calibration interval to ~2.048sec in HSIC_IO_CAL_REG */ |
46 | ret = ulpi_write(ulpi, ULPI_HSIC_IO_CAL, val: 0xff); |
47 | if (ret) |
48 | goto err_ulpi; |
49 | |
50 | /* Enable periodic IO calibration in HSIC_CFG register */ |
51 | ret = ulpi_write(ulpi, ULPI_HSIC_CFG, val: 0xa8); |
52 | if (ret) |
53 | goto err_ulpi; |
54 | |
55 | /* Configure pins for HSIC functionality */ |
56 | pins_default = pinctrl_lookup_state(p: uphy->pctl, PINCTRL_STATE_DEFAULT); |
57 | if (IS_ERR(ptr: pins_default)) { |
58 | ret = PTR_ERR(ptr: pins_default); |
59 | goto err_ulpi; |
60 | } |
61 | |
62 | ret = pinctrl_select_state(p: uphy->pctl, s: pins_default); |
63 | if (ret) |
64 | goto err_ulpi; |
65 | |
66 | /* Enable HSIC mode in HSIC_CFG register */ |
67 | ret = ulpi_write(ulpi, ULPI_SET(ULPI_HSIC_CFG), val: 0x01); |
68 | if (ret) |
69 | goto err_ulpi; |
70 | |
71 | /* Disable auto-resume */ |
72 | ret = ulpi_write(ulpi, ULPI_CLR(ULPI_IFC_CTRL), |
73 | ULPI_IFC_CTRL_AUTORESUME); |
74 | if (ret) |
75 | goto err_ulpi; |
76 | |
77 | return ret; |
78 | err_ulpi: |
79 | clk_disable_unprepare(clk: uphy->cal_sleep_clk); |
80 | err_sleep: |
81 | clk_disable_unprepare(clk: uphy->cal_clk); |
82 | err_cal: |
83 | clk_disable_unprepare(clk: uphy->phy_clk); |
84 | return ret; |
85 | } |
86 | |
87 | static int qcom_usb_hsic_phy_power_off(struct phy *phy) |
88 | { |
89 | struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy); |
90 | |
91 | clk_disable_unprepare(clk: uphy->cal_sleep_clk); |
92 | clk_disable_unprepare(clk: uphy->cal_clk); |
93 | clk_disable_unprepare(clk: uphy->phy_clk); |
94 | |
95 | return 0; |
96 | } |
97 | |
98 | static const struct phy_ops qcom_usb_hsic_phy_ops = { |
99 | .power_on = qcom_usb_hsic_phy_power_on, |
100 | .power_off = qcom_usb_hsic_phy_power_off, |
101 | .owner = THIS_MODULE, |
102 | }; |
103 | |
104 | static int qcom_usb_hsic_phy_probe(struct ulpi *ulpi) |
105 | { |
106 | struct qcom_usb_hsic_phy *uphy; |
107 | struct phy_provider *p; |
108 | struct clk *clk; |
109 | |
110 | uphy = devm_kzalloc(dev: &ulpi->dev, size: sizeof(*uphy), GFP_KERNEL); |
111 | if (!uphy) |
112 | return -ENOMEM; |
113 | ulpi_set_drvdata(ulpi, data: uphy); |
114 | |
115 | uphy->ulpi = ulpi; |
116 | uphy->pctl = devm_pinctrl_get(dev: &ulpi->dev); |
117 | if (IS_ERR(ptr: uphy->pctl)) |
118 | return PTR_ERR(ptr: uphy->pctl); |
119 | |
120 | uphy->phy_clk = clk = devm_clk_get(dev: &ulpi->dev, id: "phy" ); |
121 | if (IS_ERR(ptr: clk)) |
122 | return PTR_ERR(ptr: clk); |
123 | |
124 | uphy->cal_clk = clk = devm_clk_get(dev: &ulpi->dev, id: "cal" ); |
125 | if (IS_ERR(ptr: clk)) |
126 | return PTR_ERR(ptr: clk); |
127 | |
128 | uphy->cal_sleep_clk = clk = devm_clk_get(dev: &ulpi->dev, id: "cal_sleep" ); |
129 | if (IS_ERR(ptr: clk)) |
130 | return PTR_ERR(ptr: clk); |
131 | |
132 | uphy->phy = devm_phy_create(dev: &ulpi->dev, node: ulpi->dev.of_node, |
133 | ops: &qcom_usb_hsic_phy_ops); |
134 | if (IS_ERR(ptr: uphy->phy)) |
135 | return PTR_ERR(ptr: uphy->phy); |
136 | phy_set_drvdata(phy: uphy->phy, data: uphy); |
137 | |
138 | p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate); |
139 | return PTR_ERR_OR_ZERO(ptr: p); |
140 | } |
141 | |
142 | static const struct of_device_id qcom_usb_hsic_phy_match[] = { |
143 | { .compatible = "qcom,usb-hsic-phy" , }, |
144 | { } |
145 | }; |
146 | MODULE_DEVICE_TABLE(of, qcom_usb_hsic_phy_match); |
147 | |
148 | static struct ulpi_driver qcom_usb_hsic_phy_driver = { |
149 | .probe = qcom_usb_hsic_phy_probe, |
150 | .driver = { |
151 | .name = "qcom_usb_hsic_phy" , |
152 | .of_match_table = qcom_usb_hsic_phy_match, |
153 | }, |
154 | }; |
155 | module_ulpi_driver(qcom_usb_hsic_phy_driver); |
156 | |
157 | MODULE_DESCRIPTION("Qualcomm USB HSIC phy" ); |
158 | MODULE_LICENSE("GPL v2" ); |
159 | |