1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * OnSemi NB7VPQ904M Type-C driver |
4 | * |
5 | * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org> |
6 | */ |
7 | #include <linux/i2c.h> |
8 | #include <linux/mutex.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | #include <linux/regmap.h> |
12 | #include <linux/bitfield.h> |
13 | #include <linux/of_graph.h> |
14 | #include <drm/bridge/aux-bridge.h> |
15 | #include <linux/usb/typec_dp.h> |
16 | #include <linux/usb/typec_mux.h> |
17 | #include <linux/usb/typec_retimer.h> |
18 | #include <linux/gpio/consumer.h> |
19 | #include <linux/regulator/consumer.h> |
20 | |
21 | #define NB7_CHNA 0 |
22 | #define NB7_CHNB 1 |
23 | #define NB7_CHNC 2 |
24 | #define NB7_CHND 3 |
25 | #define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND) |
26 | |
27 | #define GEN_DEV_SET_REG 0x00 |
28 | |
29 | #define GEN_DEV_SET_CHIP_EN BIT(0) |
30 | #define GEN_DEV_SET_CHNA_EN BIT(4) |
31 | #define GEN_DEV_SET_CHNB_EN BIT(5) |
32 | #define GEN_DEV_SET_CHNC_EN BIT(6) |
33 | #define GEN_DEV_SET_CHND_EN BIT(7) |
34 | |
35 | #define GEN_DEV_SET_OP_MODE_MASK GENMASK(3, 1) |
36 | |
37 | #define GEN_DEV_SET_OP_MODE_DP_CC2 0 |
38 | #define GEN_DEV_SET_OP_MODE_DP_CC1 1 |
39 | #define GEN_DEV_SET_OP_MODE_DP_4LANE 2 |
40 | #define GEN_DEV_SET_OP_MODE_USB 5 |
41 | |
42 | #define EQ_SETTING_REG_BASE 0x01 |
43 | #define EQ_SETTING_REG(n) (EQ_SETTING_REG_BASE + (n) * 2) |
44 | #define EQ_SETTING_MASK GENMASK(3, 1) |
45 | |
46 | #define OUTPUT_COMPRESSION_AND_POL_REG_BASE 0x02 |
47 | #define OUTPUT_COMPRESSION_AND_POL_REG(n) (OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2) |
48 | #define OUTPUT_COMPRESSION_MASK GENMASK(2, 1) |
49 | |
50 | #define FLAT_GAIN_REG_BASE 0x18 |
51 | #define FLAT_GAIN_REG(n) (FLAT_GAIN_REG_BASE + (n) * 2) |
52 | #define FLAT_GAIN_MASK GENMASK(1, 0) |
53 | |
54 | #define LOSS_MATCH_REG_BASE 0x19 |
55 | #define LOSS_MATCH_REG(n) (LOSS_MATCH_REG_BASE + (n) * 2) |
56 | #define LOSS_MATCH_MASK GENMASK(1, 0) |
57 | |
58 | #define AUX_CC_REG 0x09 |
59 | |
60 | #define CHIP_VERSION_REG 0x17 |
61 | |
62 | struct nb7vpq904m { |
63 | struct i2c_client *client; |
64 | struct gpio_desc *enable_gpio; |
65 | struct regulator *vcc_supply; |
66 | struct regmap *regmap; |
67 | struct typec_switch_dev *sw; |
68 | struct typec_retimer *retimer; |
69 | |
70 | bool swap_data_lanes; |
71 | struct typec_switch *typec_switch; |
72 | |
73 | struct mutex lock; /* protect non-concurrent retimer & switch */ |
74 | |
75 | enum typec_orientation orientation; |
76 | unsigned long mode; |
77 | unsigned int svid; |
78 | }; |
79 | |
80 | static void nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp) |
81 | { |
82 | u8 eq, out_comp, flat_gain, loss_match; |
83 | |
84 | if (dp) { |
85 | eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4; |
86 | out_comp = 0x3; |
87 | flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1; |
88 | loss_match = 0x3; |
89 | } else { |
90 | eq = 0x4; |
91 | out_comp = 0x3; |
92 | flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1; |
93 | loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3; |
94 | } |
95 | |
96 | regmap_update_bits(map: nb7->regmap, EQ_SETTING_REG(channel), |
97 | EQ_SETTING_MASK, FIELD_PREP(EQ_SETTING_MASK, eq)); |
98 | regmap_update_bits(map: nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel), |
99 | OUTPUT_COMPRESSION_MASK, FIELD_PREP(OUTPUT_COMPRESSION_MASK, out_comp)); |
100 | regmap_update_bits(map: nb7->regmap, FLAT_GAIN_REG(channel), |
101 | FLAT_GAIN_MASK, FIELD_PREP(FLAT_GAIN_MASK, flat_gain)); |
102 | regmap_update_bits(map: nb7->regmap, LOSS_MATCH_REG(channel), |
103 | LOSS_MATCH_MASK, FIELD_PREP(LOSS_MATCH_MASK, loss_match)); |
104 | } |
105 | |
106 | static int nb7vpq904m_set(struct nb7vpq904m *nb7) |
107 | { |
108 | bool reverse = (nb7->orientation == TYPEC_ORIENTATION_REVERSE); |
109 | |
110 | switch (nb7->mode) { |
111 | case TYPEC_STATE_SAFE: |
112 | regmap_write(map: nb7->regmap, GEN_DEV_SET_REG, |
113 | GEN_DEV_SET_CHIP_EN | |
114 | GEN_DEV_SET_CHNA_EN | |
115 | GEN_DEV_SET_CHNB_EN | |
116 | GEN_DEV_SET_CHNC_EN | |
117 | GEN_DEV_SET_CHND_EN | |
118 | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, |
119 | GEN_DEV_SET_OP_MODE_USB)); |
120 | nb7vpq904m_set_channel(nb7, NB7_CHNA, dp: false); |
121 | nb7vpq904m_set_channel(nb7, NB7_CHNB, dp: false); |
122 | nb7vpq904m_set_channel(nb7, NB7_CHNC, dp: false); |
123 | nb7vpq904m_set_channel(nb7, NB7_CHND, dp: false); |
124 | regmap_write(map: nb7->regmap, AUX_CC_REG, val: 0x2); |
125 | |
126 | return 0; |
127 | |
128 | case TYPEC_STATE_USB: |
129 | /* |
130 | * Normal Orientation (CC1) |
131 | * A -> USB RX |
132 | * B -> USB TX |
133 | * C -> X |
134 | * D -> X |
135 | * Flipped Orientation (CC2) |
136 | * A -> X |
137 | * B -> X |
138 | * C -> USB TX |
139 | * D -> USB RX |
140 | * |
141 | * Reversed if data lanes are swapped |
142 | */ |
143 | if (reverse ^ nb7->swap_data_lanes) { |
144 | regmap_write(map: nb7->regmap, GEN_DEV_SET_REG, |
145 | GEN_DEV_SET_CHIP_EN | |
146 | GEN_DEV_SET_CHNA_EN | |
147 | GEN_DEV_SET_CHNB_EN | |
148 | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, |
149 | GEN_DEV_SET_OP_MODE_USB)); |
150 | nb7vpq904m_set_channel(nb7, NB7_CHNA, dp: false); |
151 | nb7vpq904m_set_channel(nb7, NB7_CHNB, dp: false); |
152 | } else { |
153 | regmap_write(map: nb7->regmap, GEN_DEV_SET_REG, |
154 | GEN_DEV_SET_CHIP_EN | |
155 | GEN_DEV_SET_CHNC_EN | |
156 | GEN_DEV_SET_CHND_EN | |
157 | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, |
158 | GEN_DEV_SET_OP_MODE_USB)); |
159 | nb7vpq904m_set_channel(nb7, NB7_CHNC, dp: false); |
160 | nb7vpq904m_set_channel(nb7, NB7_CHND, dp: false); |
161 | } |
162 | regmap_write(map: nb7->regmap, AUX_CC_REG, val: 0x2); |
163 | |
164 | return 0; |
165 | |
166 | default: |
167 | if (nb7->svid != USB_TYPEC_DP_SID) |
168 | return -EINVAL; |
169 | |
170 | break; |
171 | } |
172 | |
173 | /* DP Altmode Setup */ |
174 | |
175 | regmap_write(map: nb7->regmap, AUX_CC_REG, val: reverse ? 0x1 : 0x0); |
176 | |
177 | switch (nb7->mode) { |
178 | case TYPEC_DP_STATE_C: |
179 | case TYPEC_DP_STATE_E: |
180 | /* |
181 | * Normal Orientation (CC1) |
182 | * A -> DP3 |
183 | * B -> DP2 |
184 | * C -> DP1 |
185 | * D -> DP0 |
186 | * Flipped Orientation (CC2) |
187 | * A -> DP0 |
188 | * B -> DP1 |
189 | * C -> DP2 |
190 | * D -> DP3 |
191 | */ |
192 | regmap_write(map: nb7->regmap, GEN_DEV_SET_REG, |
193 | GEN_DEV_SET_CHIP_EN | |
194 | GEN_DEV_SET_CHNA_EN | |
195 | GEN_DEV_SET_CHNB_EN | |
196 | GEN_DEV_SET_CHNC_EN | |
197 | GEN_DEV_SET_CHND_EN | |
198 | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, |
199 | GEN_DEV_SET_OP_MODE_DP_4LANE)); |
200 | nb7vpq904m_set_channel(nb7, NB7_CHNA, dp: true); |
201 | nb7vpq904m_set_channel(nb7, NB7_CHNB, dp: true); |
202 | nb7vpq904m_set_channel(nb7, NB7_CHNC, dp: true); |
203 | nb7vpq904m_set_channel(nb7, NB7_CHND, dp: true); |
204 | break; |
205 | |
206 | case TYPEC_DP_STATE_D: |
207 | case TYPEC_DP_STATE_F: |
208 | regmap_write(map: nb7->regmap, GEN_DEV_SET_REG, |
209 | GEN_DEV_SET_CHIP_EN | |
210 | GEN_DEV_SET_CHNA_EN | |
211 | GEN_DEV_SET_CHNB_EN | |
212 | GEN_DEV_SET_CHNC_EN | |
213 | GEN_DEV_SET_CHND_EN | |
214 | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, |
215 | reverse ^ nb7->swap_data_lanes ? |
216 | GEN_DEV_SET_OP_MODE_DP_CC2 |
217 | : GEN_DEV_SET_OP_MODE_DP_CC1)); |
218 | |
219 | /* |
220 | * Normal Orientation (CC1) |
221 | * A -> USB RX |
222 | * B -> USB TX |
223 | * C -> DP1 |
224 | * D -> DP0 |
225 | * Flipped Orientation (CC2) |
226 | * A -> DP0 |
227 | * B -> DP1 |
228 | * C -> USB TX |
229 | * D -> USB RX |
230 | * |
231 | * Reversed if data lanes are swapped |
232 | */ |
233 | if (nb7->swap_data_lanes) { |
234 | nb7vpq904m_set_channel(nb7, NB7_CHNA, dp: !reverse); |
235 | nb7vpq904m_set_channel(nb7, NB7_CHNB, dp: !reverse); |
236 | nb7vpq904m_set_channel(nb7, NB7_CHNC, dp: reverse); |
237 | nb7vpq904m_set_channel(nb7, NB7_CHND, dp: reverse); |
238 | } else { |
239 | nb7vpq904m_set_channel(nb7, NB7_CHNA, dp: reverse); |
240 | nb7vpq904m_set_channel(nb7, NB7_CHNB, dp: reverse); |
241 | nb7vpq904m_set_channel(nb7, NB7_CHNC, dp: !reverse); |
242 | nb7vpq904m_set_channel(nb7, NB7_CHND, dp: !reverse); |
243 | } |
244 | break; |
245 | |
246 | default: |
247 | return -EOPNOTSUPP; |
248 | } |
249 | |
250 | return 0; |
251 | } |
252 | |
253 | static int nb7vpq904m_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) |
254 | { |
255 | struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw); |
256 | int ret; |
257 | |
258 | ret = typec_switch_set(sw: nb7->typec_switch, orientation); |
259 | if (ret) |
260 | return ret; |
261 | |
262 | mutex_lock(&nb7->lock); |
263 | |
264 | if (nb7->orientation != orientation) { |
265 | nb7->orientation = orientation; |
266 | |
267 | ret = nb7vpq904m_set(nb7); |
268 | } |
269 | |
270 | mutex_unlock(lock: &nb7->lock); |
271 | |
272 | return ret; |
273 | } |
274 | |
275 | static int nb7vpq904m_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) |
276 | { |
277 | struct nb7vpq904m *nb7 = typec_retimer_get_drvdata(retimer); |
278 | int ret = 0; |
279 | |
280 | mutex_lock(&nb7->lock); |
281 | |
282 | if (nb7->mode != state->mode) { |
283 | nb7->mode = state->mode; |
284 | |
285 | if (state->alt) |
286 | nb7->svid = state->alt->svid; |
287 | else |
288 | nb7->svid = 0; // No SVID |
289 | |
290 | ret = nb7vpq904m_set(nb7); |
291 | } |
292 | |
293 | mutex_unlock(lock: &nb7->lock); |
294 | |
295 | return ret; |
296 | } |
297 | |
298 | static const struct regmap_config nb7_regmap = { |
299 | .max_register = 0x1f, |
300 | .reg_bits = 8, |
301 | .val_bits = 8, |
302 | }; |
303 | |
304 | enum { |
305 | NORMAL_LANE_MAPPING, |
306 | INVERT_LANE_MAPPING, |
307 | }; |
308 | |
309 | #define DATA_LANES_COUNT 4 |
310 | |
311 | static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = { |
312 | [NORMAL_LANE_MAPPING] = { 0, 1, 2, 3 }, |
313 | [INVERT_LANE_MAPPING] = { 3, 2, 1, 0 }, |
314 | }; |
315 | |
316 | static int nb7vpq904m_parse_data_lanes_mapping(struct nb7vpq904m *nb7) |
317 | { |
318 | struct device_node *ep; |
319 | u32 data_lanes[4]; |
320 | int ret, i, j; |
321 | |
322 | ep = of_graph_get_endpoint_by_regs(parent: nb7->client->dev.of_node, port_reg: 1, reg: 0); |
323 | |
324 | if (ep) { |
325 | ret = of_property_count_u32_elems(np: ep, propname: "data-lanes" ); |
326 | if (ret == -EINVAL) |
327 | /* Property isn't here, consider default mapping */ |
328 | goto out_done; |
329 | if (ret < 0) |
330 | goto out_error; |
331 | |
332 | if (ret != DATA_LANES_COUNT) { |
333 | dev_err(&nb7->client->dev, "expected 4 data lanes\n" ); |
334 | ret = -EINVAL; |
335 | goto out_error; |
336 | } |
337 | |
338 | ret = of_property_read_u32_array(np: ep, propname: "data-lanes" , out_values: data_lanes, DATA_LANES_COUNT); |
339 | if (ret) |
340 | goto out_error; |
341 | |
342 | for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) { |
343 | for (j = 0; j < DATA_LANES_COUNT; j++) { |
344 | if (data_lanes[j] != supported_data_lane_mapping[i][j]) |
345 | break; |
346 | } |
347 | |
348 | if (j == DATA_LANES_COUNT) |
349 | break; |
350 | } |
351 | |
352 | switch (i) { |
353 | case NORMAL_LANE_MAPPING: |
354 | break; |
355 | case INVERT_LANE_MAPPING: |
356 | nb7->swap_data_lanes = true; |
357 | dev_info(&nb7->client->dev, "using inverted data lanes mapping\n" ); |
358 | break; |
359 | default: |
360 | dev_err(&nb7->client->dev, "invalid data lanes mapping\n" ); |
361 | ret = -EINVAL; |
362 | goto out_error; |
363 | } |
364 | } |
365 | |
366 | out_done: |
367 | ret = 0; |
368 | |
369 | out_error: |
370 | of_node_put(node: ep); |
371 | |
372 | return ret; |
373 | } |
374 | |
375 | static int nb7vpq904m_probe(struct i2c_client *client) |
376 | { |
377 | struct device *dev = &client->dev; |
378 | struct typec_switch_desc sw_desc = { }; |
379 | struct typec_retimer_desc retimer_desc = { }; |
380 | struct nb7vpq904m *nb7; |
381 | int ret; |
382 | |
383 | nb7 = devm_kzalloc(dev, size: sizeof(*nb7), GFP_KERNEL); |
384 | if (!nb7) |
385 | return -ENOMEM; |
386 | |
387 | nb7->client = client; |
388 | |
389 | nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap); |
390 | if (IS_ERR(ptr: nb7->regmap)) { |
391 | dev_err(&client->dev, "Failed to allocate register map\n" ); |
392 | return PTR_ERR(ptr: nb7->regmap); |
393 | } |
394 | |
395 | nb7->mode = TYPEC_STATE_SAFE; |
396 | nb7->orientation = TYPEC_ORIENTATION_NONE; |
397 | |
398 | mutex_init(&nb7->lock); |
399 | |
400 | nb7->enable_gpio = devm_gpiod_get_optional(dev, con_id: "enable" , flags: GPIOD_OUT_LOW); |
401 | if (IS_ERR(ptr: nb7->enable_gpio)) |
402 | return dev_err_probe(dev, err: PTR_ERR(ptr: nb7->enable_gpio), |
403 | fmt: "unable to acquire enable gpio\n" ); |
404 | |
405 | nb7->vcc_supply = devm_regulator_get_optional(dev, id: "vcc" ); |
406 | if (IS_ERR(ptr: nb7->vcc_supply)) |
407 | return PTR_ERR(ptr: nb7->vcc_supply); |
408 | |
409 | nb7->typec_switch = fwnode_typec_switch_get(fwnode: dev->fwnode); |
410 | if (IS_ERR(ptr: nb7->typec_switch)) |
411 | return dev_err_probe(dev, err: PTR_ERR(ptr: nb7->typec_switch), |
412 | fmt: "failed to acquire orientation-switch\n" ); |
413 | |
414 | ret = nb7vpq904m_parse_data_lanes_mapping(nb7); |
415 | if (ret) |
416 | return ret; |
417 | |
418 | ret = regulator_enable(regulator: nb7->vcc_supply); |
419 | if (ret) |
420 | dev_warn(dev, "Failed to enable vcc: %d\n" , ret); |
421 | |
422 | gpiod_set_value(desc: nb7->enable_gpio, value: 1); |
423 | |
424 | ret = drm_aux_bridge_register(parent: dev); |
425 | if (ret) |
426 | goto err_disable_gpio; |
427 | |
428 | sw_desc.drvdata = nb7; |
429 | sw_desc.fwnode = dev->fwnode; |
430 | sw_desc.set = nb7vpq904m_sw_set; |
431 | |
432 | nb7->sw = typec_switch_register(parent: dev, desc: &sw_desc); |
433 | if (IS_ERR(ptr: nb7->sw)) { |
434 | ret = dev_err_probe(dev, err: PTR_ERR(ptr: nb7->sw), |
435 | fmt: "Error registering typec switch\n" ); |
436 | goto err_disable_gpio; |
437 | } |
438 | |
439 | retimer_desc.drvdata = nb7; |
440 | retimer_desc.fwnode = dev->fwnode; |
441 | retimer_desc.set = nb7vpq904m_retimer_set; |
442 | |
443 | nb7->retimer = typec_retimer_register(parent: dev, desc: &retimer_desc); |
444 | if (IS_ERR(ptr: nb7->retimer)) { |
445 | ret = dev_err_probe(dev, err: PTR_ERR(ptr: nb7->retimer), |
446 | fmt: "Error registering typec retimer\n" ); |
447 | goto err_switch_unregister; |
448 | } |
449 | |
450 | return 0; |
451 | |
452 | err_switch_unregister: |
453 | typec_switch_unregister(sw: nb7->sw); |
454 | |
455 | err_disable_gpio: |
456 | gpiod_set_value(desc: nb7->enable_gpio, value: 0); |
457 | regulator_disable(regulator: nb7->vcc_supply); |
458 | |
459 | return ret; |
460 | } |
461 | |
462 | static void nb7vpq904m_remove(struct i2c_client *client) |
463 | { |
464 | struct nb7vpq904m *nb7 = i2c_get_clientdata(client); |
465 | |
466 | typec_retimer_unregister(retimer: nb7->retimer); |
467 | typec_switch_unregister(sw: nb7->sw); |
468 | |
469 | gpiod_set_value(desc: nb7->enable_gpio, value: 0); |
470 | |
471 | regulator_disable(regulator: nb7->vcc_supply); |
472 | } |
473 | |
474 | static const struct i2c_device_id nb7vpq904m_table[] = { |
475 | { "nb7vpq904m" }, |
476 | { } |
477 | }; |
478 | MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table); |
479 | |
480 | static const struct of_device_id nb7vpq904m_of_table[] = { |
481 | { .compatible = "onnn,nb7vpq904m" }, |
482 | { } |
483 | }; |
484 | MODULE_DEVICE_TABLE(of, nb7vpq904m_of_table); |
485 | |
486 | static struct i2c_driver nb7vpq904m_driver = { |
487 | .driver = { |
488 | .name = "nb7vpq904m" , |
489 | .of_match_table = nb7vpq904m_of_table, |
490 | }, |
491 | .probe = nb7vpq904m_probe, |
492 | .remove = nb7vpq904m_remove, |
493 | .id_table = nb7vpq904m_table, |
494 | }; |
495 | |
496 | module_i2c_driver(nb7vpq904m_driver); |
497 | |
498 | MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>" ); |
499 | MODULE_DESCRIPTION("OnSemi NB7VPQ904M Type-C driver" ); |
500 | MODULE_LICENSE("GPL" ); |
501 | |