1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * MediaTek MIPI CSI v0.5 driver |
4 | * |
5 | * Copyright (c) 2023, MediaTek Inc. |
6 | * Copyright (c) 2023, BayLibre Inc. |
7 | */ |
8 | |
9 | #include <dt-bindings/phy/phy.h> |
10 | #include <linux/bitfield.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/io.h> |
13 | #include <linux/module.h> |
14 | #include <linux/mutex.h> |
15 | #include <linux/phy/phy.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/slab.h> |
18 | |
19 | #include "phy-mtk-io.h" |
20 | #include "phy-mtk-mipi-csi-0-5-rx-reg.h" |
21 | |
22 | #define CSIXB_OFFSET 0x1000 |
23 | |
24 | struct mtk_mipi_cdphy_port { |
25 | struct device *dev; |
26 | void __iomem *base; |
27 | struct phy *phy; |
28 | u32 type; |
29 | u32 mode; |
30 | u32 num_lanes; |
31 | }; |
32 | |
33 | enum PHY_TYPE { |
34 | DPHY = 0, |
35 | CPHY, |
36 | CDPHY, |
37 | }; |
38 | |
39 | static void mtk_phy_csi_cdphy_ana_eq_tune(void __iomem *base) |
40 | { |
41 | mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI0A_L0_T0AB_EQ_IS, 1); |
42 | mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI0A_L0_T0AB_EQ_BW, 1); |
43 | mtk_phy_update_field(base + MIPI_RX_ANA1C_CSIXA, RG_CSI0A_L1_T1AB_EQ_IS, 1); |
44 | mtk_phy_update_field(base + MIPI_RX_ANA1C_CSIXA, RG_CSI0A_L1_T1AB_EQ_BW, 1); |
45 | mtk_phy_update_field(base + MIPI_RX_ANA20_CSI0A, RG_CSI0A_L2_T1BC_EQ_IS, 1); |
46 | mtk_phy_update_field(base + MIPI_RX_ANA20_CSI0A, RG_CSI0A_L2_T1BC_EQ_BW, 1); |
47 | |
48 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI0A_L0_T0AB_EQ_IS, 1); |
49 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI0A_L0_T0AB_EQ_BW, 1); |
50 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA1C_CSIXA, RG_CSI0A_L1_T1AB_EQ_IS, 1); |
51 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA1C_CSIXA, RG_CSI0A_L1_T1AB_EQ_BW, 1); |
52 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA20_CSI0A, RG_CSI0A_L2_T1BC_EQ_IS, 1); |
53 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA20_CSI0A, RG_CSI0A_L2_T1BC_EQ_BW, 1); |
54 | } |
55 | |
56 | static void mtk_phy_csi_dphy_ana_eq_tune(void __iomem *base) |
57 | { |
58 | mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L0_EQ_IS, 1); |
59 | mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L0_EQ_BW, 1); |
60 | mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L1_EQ_IS, 1); |
61 | mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L1_EQ_BW, 1); |
62 | mtk_phy_update_field(base + MIPI_RX_ANA1C_CSIXA, RG_CSI1A_L2_EQ_IS, 1); |
63 | mtk_phy_update_field(base + MIPI_RX_ANA1C_CSIXA, RG_CSI1A_L2_EQ_BW, 1); |
64 | |
65 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L0_EQ_IS, 1); |
66 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L0_EQ_BW, 1); |
67 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L1_EQ_IS, 1); |
68 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L1_EQ_BW, 1); |
69 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA1C_CSIXA, RG_CSI1A_L2_EQ_IS, 1); |
70 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA1C_CSIXA, RG_CSI1A_L2_EQ_BW, 1); |
71 | } |
72 | |
73 | static int mtk_mipi_phy_power_on(struct phy *phy) |
74 | { |
75 | struct mtk_mipi_cdphy_port *port = phy_get_drvdata(phy); |
76 | void __iomem *base = port->base; |
77 | |
78 | /* |
79 | * The driver currently supports DPHY and CD-PHY phys, |
80 | * but the only mode supported is DPHY, |
81 | * so CD-PHY capable phys must be configured in DPHY mode |
82 | */ |
83 | if (port->type == CDPHY) { |
84 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSI0A_CPHY_EN, 0); |
85 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, |
86 | RG_CSI0A_CPHY_EN, 0); |
87 | } |
88 | |
89 | /* |
90 | * Lane configuration: |
91 | * |
92 | * Only 4 data + 1 clock is supported for now with the following mapping: |
93 | * |
94 | * CSIXA_LNR0 --> D2 |
95 | * CSIXA_LNR1 --> D0 |
96 | * CSIXA_LNR2 --> C |
97 | * CSIXB_LNR0 --> D1 |
98 | * CSIXB_LNR1 --> D3 |
99 | */ |
100 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L0_CKMODE_EN, 0); |
101 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L0_CKSEL, 1); |
102 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L1_CKMODE_EN, 0); |
103 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L1_CKSEL, 1); |
104 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L2_CKMODE_EN, 1); |
105 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L2_CKSEL, 1); |
106 | |
107 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, |
108 | RG_CSIXA_DPHY_L0_CKMODE_EN, 0); |
109 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L0_CKSEL, 1); |
110 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, |
111 | RG_CSIXA_DPHY_L1_CKMODE_EN, 0); |
112 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L1_CKSEL, 1); |
113 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, |
114 | RG_CSIXA_DPHY_L2_CKMODE_EN, 0); |
115 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L2_CKSEL, 1); |
116 | |
117 | /* Byte clock invert */ |
118 | mtk_phy_update_field(base + MIPI_RX_ANAA8_CSIXA, RG_CSIXA_CDPHY_L0_T0_BYTECK_INVERT, 1); |
119 | mtk_phy_update_field(base + MIPI_RX_ANAA8_CSIXA, RG_CSIXA_DPHY_L1_BYTECK_INVERT, 1); |
120 | mtk_phy_update_field(base + MIPI_RX_ANAA8_CSIXA, RG_CSIXA_CDPHY_L2_T1_BYTECK_INVERT, 1); |
121 | |
122 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANAA8_CSIXA, |
123 | RG_CSIXA_CDPHY_L0_T0_BYTECK_INVERT, 1); |
124 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANAA8_CSIXA, |
125 | RG_CSIXA_DPHY_L1_BYTECK_INVERT, 1); |
126 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANAA8_CSIXA, |
127 | RG_CSIXA_CDPHY_L2_T1_BYTECK_INVERT, 1); |
128 | |
129 | /* Start ANA EQ tuning */ |
130 | if (port->type == CDPHY) |
131 | mtk_phy_csi_cdphy_ana_eq_tune(base); |
132 | else |
133 | mtk_phy_csi_dphy_ana_eq_tune(base); |
134 | |
135 | /* End ANA EQ tuning */ |
136 | mtk_phy_set_bits(reg: base + MIPI_RX_ANA40_CSIXA, bits: 0x90); |
137 | |
138 | mtk_phy_update_field(base + MIPI_RX_ANA24_CSIXA, RG_CSIXA_RESERVE, 0x40); |
139 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA24_CSIXA, RG_CSIXA_RESERVE, 0x40); |
140 | mtk_phy_update_field(base + MIPI_RX_WRAPPER80_CSIXA, CSR_CSI_RST_MODE, 0); |
141 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_WRAPPER80_CSIXA, CSR_CSI_RST_MODE, 0); |
142 | /* ANA power on */ |
143 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_CORE_EN, 1); |
144 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_CORE_EN, 1); |
145 | usleep_range(min: 20, max: 40); |
146 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_LPF_EN, 1); |
147 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_LPF_EN, 1); |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static int mtk_mipi_phy_power_off(struct phy *phy) |
153 | { |
154 | struct mtk_mipi_cdphy_port *port = phy_get_drvdata(phy); |
155 | void __iomem *base = port->base; |
156 | |
157 | /* Disable MIPI BG. */ |
158 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_CORE_EN, 0); |
159 | mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_LPF_EN, 0); |
160 | |
161 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_CORE_EN, 0); |
162 | mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_LPF_EN, 0); |
163 | |
164 | return 0; |
165 | } |
166 | |
167 | static struct phy *mtk_mipi_cdphy_xlate(struct device *dev, |
168 | const struct of_phandle_args *args) |
169 | { |
170 | struct mtk_mipi_cdphy_port *priv = dev_get_drvdata(dev); |
171 | |
172 | /* |
173 | * If PHY is CD-PHY then we need to get the operating mode |
174 | * For now only D-PHY mode is supported |
175 | */ |
176 | if (priv->type == CDPHY) { |
177 | if (args->args_count != 1) { |
178 | dev_err(dev, "invalid number of arguments\n" ); |
179 | return ERR_PTR(error: -EINVAL); |
180 | } |
181 | switch (args->args[0]) { |
182 | case PHY_TYPE_DPHY: |
183 | priv->mode = DPHY; |
184 | if (priv->num_lanes != 4) { |
185 | dev_err(dev, "Only 4D1C mode is supported for now!\n" ); |
186 | return ERR_PTR(error: -EINVAL); |
187 | } |
188 | break; |
189 | default: |
190 | dev_err(dev, "Unsupported PHY type: %i\n" , args->args[0]); |
191 | return ERR_PTR(error: -EINVAL); |
192 | } |
193 | } else { |
194 | if (args->args_count) { |
195 | dev_err(dev, "invalid number of arguments\n" ); |
196 | return ERR_PTR(error: -EINVAL); |
197 | } |
198 | priv->mode = DPHY; |
199 | } |
200 | |
201 | return priv->phy; |
202 | } |
203 | |
204 | static const struct phy_ops mtk_cdphy_ops = { |
205 | .power_on = mtk_mipi_phy_power_on, |
206 | .power_off = mtk_mipi_phy_power_off, |
207 | .owner = THIS_MODULE, |
208 | }; |
209 | |
210 | static int mtk_mipi_cdphy_probe(struct platform_device *pdev) |
211 | { |
212 | struct device *dev = &pdev->dev; |
213 | struct phy_provider *phy_provider; |
214 | struct mtk_mipi_cdphy_port *port; |
215 | struct phy *phy; |
216 | int ret; |
217 | u32 phy_type; |
218 | |
219 | port = devm_kzalloc(dev, size: sizeof(*port), GFP_KERNEL); |
220 | if (!port) |
221 | return -ENOMEM; |
222 | |
223 | dev_set_drvdata(dev, data: port); |
224 | |
225 | port->dev = dev; |
226 | |
227 | port->base = devm_platform_ioremap_resource(pdev, index: 0); |
228 | if (IS_ERR(ptr: port->base)) |
229 | return PTR_ERR(ptr: port->base); |
230 | |
231 | ret = of_property_read_u32(np: dev->of_node, propname: "num-lanes" , out_value: &port->num_lanes); |
232 | if (ret) { |
233 | dev_err(dev, "Failed to read num-lanes property: %i\n" , ret); |
234 | return ret; |
235 | } |
236 | |
237 | /* |
238 | * phy-type is optional, if not present, PHY is considered to be CD-PHY |
239 | */ |
240 | if (device_property_present(dev, propname: "phy-type" )) { |
241 | ret = of_property_read_u32(np: dev->of_node, propname: "phy-type" , out_value: &phy_type); |
242 | if (ret) { |
243 | dev_err(dev, "Failed to read phy-type property: %i\n" , ret); |
244 | return ret; |
245 | } |
246 | switch (phy_type) { |
247 | case PHY_TYPE_DPHY: |
248 | port->type = DPHY; |
249 | break; |
250 | default: |
251 | dev_err(dev, "Unsupported PHY type: %i\n" , phy_type); |
252 | return -EINVAL; |
253 | } |
254 | } else { |
255 | port->type = CDPHY; |
256 | } |
257 | |
258 | phy = devm_phy_create(dev, NULL, ops: &mtk_cdphy_ops); |
259 | if (IS_ERR(ptr: phy)) { |
260 | dev_err(dev, "Failed to create PHY: %ld\n" , PTR_ERR(phy)); |
261 | return PTR_ERR(ptr: phy); |
262 | } |
263 | |
264 | port->phy = phy; |
265 | phy_set_drvdata(phy, data: port); |
266 | |
267 | phy_provider = devm_of_phy_provider_register(dev, mtk_mipi_cdphy_xlate); |
268 | if (IS_ERR(ptr: phy_provider)) { |
269 | dev_err(dev, "Failed to register PHY provider: %ld\n" , |
270 | PTR_ERR(phy_provider)); |
271 | return PTR_ERR(ptr: phy_provider); |
272 | } |
273 | |
274 | return 0; |
275 | } |
276 | |
277 | static const struct of_device_id mtk_mipi_cdphy_of_match[] = { |
278 | { .compatible = "mediatek,mt8365-csi-rx" }, |
279 | { /* sentinel */}, |
280 | }; |
281 | MODULE_DEVICE_TABLE(of, mtk_mipi_cdphy_of_match); |
282 | |
283 | static struct platform_driver mipi_cdphy_pdrv = { |
284 | .probe = mtk_mipi_cdphy_probe, |
285 | .driver = { |
286 | .name = "mtk-mipi-csi-0-5" , |
287 | .of_match_table = mtk_mipi_cdphy_of_match, |
288 | }, |
289 | }; |
290 | module_platform_driver(mipi_cdphy_pdrv); |
291 | |
292 | MODULE_DESCRIPTION("MediaTek MIPI CSI CD-PHY v0.5 Driver" ); |
293 | MODULE_AUTHOR("Louis Kuo <louis.kuo@mediatek.com>" ); |
294 | MODULE_LICENSE("GPL" ); |
295 | |