1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Amlogic AXG MIPI + PCIE analog PHY driver |
4 | * |
5 | * Copyright (C) 2019 Remi Pommarel <repk@triplefau.lt> |
6 | */ |
7 | #include <linux/bitfield.h> |
8 | #include <linux/bitops.h> |
9 | #include <linux/module.h> |
10 | #include <linux/phy/phy.h> |
11 | #include <linux/regmap.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/mfd/syscon.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <dt-bindings/phy/phy.h> |
17 | |
18 | #define HHI_MIPI_CNTL0 0x00 |
19 | #define HHI_MIPI_CNTL0_COMMON_BLOCK GENMASK(31, 28) |
20 | #define HHI_MIPI_CNTL0_ENABLE BIT(29) |
21 | #define HHI_MIPI_CNTL0_BANDGAP BIT(26) |
22 | #define HHI_MIPI_CNTL0_DIF_REF_CTL1 GENMASK(25, 16) |
23 | #define HHI_MIPI_CNTL0_DIF_REF_CTL0 GENMASK(15, 0) |
24 | |
25 | #define HHI_MIPI_CNTL1 0x04 |
26 | #define HHI_MIPI_CNTL1_CH0_CML_PDR_EN BIT(12) |
27 | #define HHI_MIPI_CNTL1_LP_ABILITY GENMASK(5, 4) |
28 | #define HHI_MIPI_CNTL1_LP_RESISTER BIT(3) |
29 | #define HHI_MIPI_CNTL1_INPUT_SETTING BIT(2) |
30 | #define HHI_MIPI_CNTL1_INPUT_SEL BIT(1) |
31 | #define HHI_MIPI_CNTL1_PRBS7_EN BIT(0) |
32 | |
33 | #define HHI_MIPI_CNTL2 0x08 |
34 | #define HHI_MIPI_CNTL2_CH_PU GENMASK(31, 25) |
35 | #define HHI_MIPI_CNTL2_CH_CTL GENMASK(24, 19) |
36 | #define HHI_MIPI_CNTL2_CH0_DIGDR_EN BIT(18) |
37 | #define HHI_MIPI_CNTL2_CH_DIGDR_EN BIT(17) |
38 | #define HHI_MIPI_CNTL2_LPULPS_EN BIT(16) |
39 | #define HHI_MIPI_CNTL2_CH_EN GENMASK(15, 11) |
40 | #define HHI_MIPI_CNTL2_CH0_LP_CTL GENMASK(10, 1) |
41 | |
42 | #define DSI_LANE_0 BIT(4) |
43 | #define DSI_LANE_1 BIT(3) |
44 | #define DSI_LANE_CLK BIT(2) |
45 | #define DSI_LANE_2 BIT(1) |
46 | #define DSI_LANE_3 BIT(0) |
47 | |
48 | struct phy_axg_mipi_pcie_analog_priv { |
49 | struct phy *phy; |
50 | struct regmap *regmap; |
51 | bool dsi_configured; |
52 | bool dsi_enabled; |
53 | bool powered; |
54 | struct phy_configure_opts_mipi_dphy config; |
55 | }; |
56 | |
57 | static void phy_bandgap_enable(struct phy_axg_mipi_pcie_analog_priv *priv) |
58 | { |
59 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
60 | HHI_MIPI_CNTL0_BANDGAP, HHI_MIPI_CNTL0_BANDGAP); |
61 | |
62 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
63 | HHI_MIPI_CNTL0_ENABLE, HHI_MIPI_CNTL0_ENABLE); |
64 | } |
65 | |
66 | static void phy_bandgap_disable(struct phy_axg_mipi_pcie_analog_priv *priv) |
67 | { |
68 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
69 | HHI_MIPI_CNTL0_BANDGAP, val: 0); |
70 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
71 | HHI_MIPI_CNTL0_ENABLE, val: 0); |
72 | } |
73 | |
74 | static void phy_dsi_analog_enable(struct phy_axg_mipi_pcie_analog_priv *priv) |
75 | { |
76 | u32 reg; |
77 | |
78 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
79 | HHI_MIPI_CNTL0_DIF_REF_CTL1, |
80 | FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0x1b8)); |
81 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
82 | BIT(31), BIT(31)); |
83 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
84 | HHI_MIPI_CNTL0_DIF_REF_CTL0, |
85 | FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL0, 0x8)); |
86 | |
87 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL1, val: 0x001e); |
88 | |
89 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL2, |
90 | val: (0x26e0 << 16) | (0x459 << 0)); |
91 | |
92 | reg = DSI_LANE_CLK; |
93 | switch (priv->config.lanes) { |
94 | case 4: |
95 | reg |= DSI_LANE_3; |
96 | fallthrough; |
97 | case 3: |
98 | reg |= DSI_LANE_2; |
99 | fallthrough; |
100 | case 2: |
101 | reg |= DSI_LANE_1; |
102 | fallthrough; |
103 | case 1: |
104 | reg |= DSI_LANE_0; |
105 | break; |
106 | default: |
107 | reg = 0; |
108 | } |
109 | |
110 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL2, |
111 | HHI_MIPI_CNTL2_CH_EN, |
112 | FIELD_PREP(HHI_MIPI_CNTL2_CH_EN, reg)); |
113 | |
114 | priv->dsi_enabled = true; |
115 | } |
116 | |
117 | static void phy_dsi_analog_disable(struct phy_axg_mipi_pcie_analog_priv *priv) |
118 | { |
119 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
120 | HHI_MIPI_CNTL0_DIF_REF_CTL1, |
121 | FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0)); |
122 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, BIT(31), val: 0); |
123 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL0, |
124 | HHI_MIPI_CNTL0_DIF_REF_CTL1, val: 0); |
125 | |
126 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL1, val: 0x6); |
127 | |
128 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL2, val: 0x00200000); |
129 | |
130 | priv->dsi_enabled = false; |
131 | } |
132 | |
133 | static int phy_axg_mipi_pcie_analog_configure(struct phy *phy, |
134 | union phy_configure_opts *opts) |
135 | { |
136 | struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy); |
137 | int ret; |
138 | |
139 | ret = phy_mipi_dphy_config_validate(cfg: &opts->mipi_dphy); |
140 | if (ret) |
141 | return ret; |
142 | |
143 | memcpy(&priv->config, opts, sizeof(priv->config)); |
144 | |
145 | priv->dsi_configured = true; |
146 | |
147 | /* If PHY was already powered on, setup the DSI analog part */ |
148 | if (priv->powered) { |
149 | /* If reconfiguring, disable & reconfigure */ |
150 | if (priv->dsi_enabled) |
151 | phy_dsi_analog_disable(priv); |
152 | |
153 | usleep_range(min: 100, max: 200); |
154 | |
155 | phy_dsi_analog_enable(priv); |
156 | } |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | static int phy_axg_mipi_pcie_analog_power_on(struct phy *phy) |
162 | { |
163 | struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy); |
164 | |
165 | phy_bandgap_enable(priv); |
166 | |
167 | if (priv->dsi_configured) |
168 | phy_dsi_analog_enable(priv); |
169 | |
170 | priv->powered = true; |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static int phy_axg_mipi_pcie_analog_power_off(struct phy *phy) |
176 | { |
177 | struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy); |
178 | |
179 | phy_bandgap_disable(priv); |
180 | |
181 | if (priv->dsi_enabled) |
182 | phy_dsi_analog_disable(priv); |
183 | |
184 | priv->powered = false; |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static const struct phy_ops phy_axg_mipi_pcie_analog_ops = { |
190 | .configure = phy_axg_mipi_pcie_analog_configure, |
191 | .power_on = phy_axg_mipi_pcie_analog_power_on, |
192 | .power_off = phy_axg_mipi_pcie_analog_power_off, |
193 | .owner = THIS_MODULE, |
194 | }; |
195 | |
196 | static int phy_axg_mipi_pcie_analog_probe(struct platform_device *pdev) |
197 | { |
198 | struct phy_provider *phy; |
199 | struct device *dev = &pdev->dev; |
200 | struct phy_axg_mipi_pcie_analog_priv *priv; |
201 | struct device_node *np = dev->of_node, *parent_np; |
202 | struct regmap *map; |
203 | int ret; |
204 | |
205 | priv = devm_kmalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
206 | if (!priv) |
207 | return -ENOMEM; |
208 | |
209 | /* Get the hhi system controller node */ |
210 | parent_np = of_get_parent(node: dev->of_node); |
211 | map = syscon_node_to_regmap(np: parent_np); |
212 | of_node_put(node: parent_np); |
213 | if (IS_ERR(ptr: map)) { |
214 | dev_err(dev, |
215 | "failed to get HHI regmap\n" ); |
216 | return PTR_ERR(ptr: map); |
217 | } |
218 | |
219 | priv->regmap = map; |
220 | |
221 | priv->phy = devm_phy_create(dev, node: np, ops: &phy_axg_mipi_pcie_analog_ops); |
222 | if (IS_ERR(ptr: priv->phy)) { |
223 | ret = PTR_ERR(ptr: priv->phy); |
224 | if (ret != -EPROBE_DEFER) |
225 | dev_err(dev, "failed to create PHY\n" ); |
226 | return ret; |
227 | } |
228 | |
229 | phy_set_drvdata(phy: priv->phy, data: priv); |
230 | dev_set_drvdata(dev, data: priv); |
231 | |
232 | phy = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
233 | |
234 | return PTR_ERR_OR_ZERO(ptr: phy); |
235 | } |
236 | |
237 | static const struct of_device_id phy_axg_mipi_pcie_analog_of_match[] = { |
238 | { |
239 | .compatible = "amlogic,axg-mipi-pcie-analog-phy" , |
240 | }, |
241 | { }, |
242 | }; |
243 | MODULE_DEVICE_TABLE(of, phy_axg_mipi_pcie_analog_of_match); |
244 | |
245 | static struct platform_driver phy_axg_mipi_pcie_analog_driver = { |
246 | .probe = phy_axg_mipi_pcie_analog_probe, |
247 | .driver = { |
248 | .name = "phy-axg-mipi-pcie-analog" , |
249 | .of_match_table = phy_axg_mipi_pcie_analog_of_match, |
250 | }, |
251 | }; |
252 | module_platform_driver(phy_axg_mipi_pcie_analog_driver); |
253 | |
254 | MODULE_AUTHOR("Remi Pommarel <repk@triplefau.lt>" ); |
255 | MODULE_DESCRIPTION("Amlogic AXG MIPI + PCIE analog PHY driver" ); |
256 | MODULE_LICENSE("GPL v2" ); |
257 | |