]>
Commit | Line | Data |
---|---|---|
ce6e153f AH |
1 | /* |
2 | * Silicon Image SiI8620 HDMI/MHL bridge driver | |
3 | * | |
4 | * Copyright (C) 2015, Samsung Electronics Co., Ltd. | |
5 | * Andrzej Hajda <a.hajda@samsung.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | ||
e19e9c69 AH |
12 | #include <asm/unaligned.h> |
13 | ||
ce6e153f AH |
14 | #include <drm/bridge/mhl.h> |
15 | #include <drm/drm_crtc.h> | |
16 | #include <drm/drm_edid.h> | |
17 | ||
18 | #include <linux/clk.h> | |
19 | #include <linux/delay.h> | |
20 | #include <linux/gpio/consumer.h> | |
21 | #include <linux/i2c.h> | |
22 | #include <linux/interrupt.h> | |
23 | #include <linux/irq.h> | |
24 | #include <linux/kernel.h> | |
25 | #include <linux/list.h> | |
26 | #include <linux/module.h> | |
27 | #include <linux/mutex.h> | |
28 | #include <linux/regulator/consumer.h> | |
29 | #include <linux/slab.h> | |
30 | ||
31 | #include "sil-sii8620.h" | |
32 | ||
e19e9c69 AH |
33 | #define SII8620_BURST_BUF_LEN 288 |
34 | #define VAL_RX_HDMI_CTRL2_DEFVAL VAL_RX_HDMI_CTRL2_IDLE_CNT(3) | |
bf1722ca AH |
35 | #define MHL1_MAX_LCLK 225000 |
36 | #define MHL3_MAX_LCLK 600000 | |
ce6e153f AH |
37 | |
38 | enum sii8620_mode { | |
39 | CM_DISCONNECTED, | |
40 | CM_DISCOVERY, | |
41 | CM_MHL1, | |
42 | CM_MHL3, | |
43 | CM_ECBUS_S | |
44 | }; | |
45 | ||
46 | enum sii8620_sink_type { | |
47 | SINK_NONE, | |
48 | SINK_HDMI, | |
49 | SINK_DVI | |
50 | }; | |
51 | ||
52 | enum sii8620_mt_state { | |
53 | MT_STATE_READY, | |
54 | MT_STATE_BUSY, | |
55 | MT_STATE_DONE | |
56 | }; | |
57 | ||
58 | struct sii8620 { | |
59 | struct drm_bridge bridge; | |
60 | struct device *dev; | |
61 | struct clk *clk_xtal; | |
62 | struct gpio_desc *gpio_reset; | |
63 | struct gpio_desc *gpio_int; | |
64 | struct regulator_bulk_data supplies[2]; | |
65 | struct mutex lock; /* context lock, protects fields below */ | |
66 | int error; | |
bf1722ca AH |
67 | int pixel_clock; |
68 | unsigned int use_packed_pixel:1; | |
69 | int video_code; | |
ce6e153f AH |
70 | enum sii8620_mode mode; |
71 | enum sii8620_sink_type sink_type; | |
72 | u8 cbus_status; | |
73 | u8 stat[MHL_DST_SIZE]; | |
74 | u8 xstat[MHL_XDS_SIZE]; | |
75 | u8 devcap[MHL_DCAP_SIZE]; | |
76 | u8 xdevcap[MHL_XDC_SIZE]; | |
bf1722ca | 77 | u8 avif[HDMI_INFOFRAME_SIZE(AVI)]; |
ce6e153f AH |
78 | struct edid *edid; |
79 | unsigned int gen2_write_burst:1; | |
80 | enum sii8620_mt_state mt_state; | |
81 | struct list_head mt_queue; | |
e19e9c69 AH |
82 | struct { |
83 | int r_size; | |
84 | int r_count; | |
85 | int rx_ack; | |
86 | int rx_count; | |
87 | u8 rx_buf[32]; | |
88 | int tx_count; | |
89 | u8 tx_buf[32]; | |
90 | } burst; | |
ce6e153f AH |
91 | }; |
92 | ||
93 | struct sii8620_mt_msg; | |
94 | ||
95 | typedef void (*sii8620_mt_msg_cb)(struct sii8620 *ctx, | |
96 | struct sii8620_mt_msg *msg); | |
97 | ||
0c2d1875 AH |
98 | typedef void (*sii8620_cb)(struct sii8620 *ctx, int ret); |
99 | ||
ce6e153f AH |
100 | struct sii8620_mt_msg { |
101 | struct list_head node; | |
102 | u8 reg[4]; | |
103 | u8 ret; | |
104 | sii8620_mt_msg_cb send; | |
105 | sii8620_mt_msg_cb recv; | |
0c2d1875 | 106 | sii8620_cb continuation; |
ce6e153f AH |
107 | }; |
108 | ||
109 | static const u8 sii8620_i2c_page[] = { | |
110 | 0x39, /* Main System */ | |
111 | 0x3d, /* TDM and HSIC */ | |
112 | 0x49, /* TMDS Receiver, MHL EDID */ | |
113 | 0x4d, /* eMSC, HDCP, HSIC */ | |
114 | 0x5d, /* MHL Spec */ | |
115 | 0x64, /* MHL CBUS */ | |
116 | 0x59, /* Hardware TPI (Transmitter Programming Interface) */ | |
117 | 0x61, /* eCBUS-S, eCBUS-D */ | |
118 | }; | |
119 | ||
120 | static void sii8620_fetch_edid(struct sii8620 *ctx); | |
121 | static void sii8620_set_upstream_edid(struct sii8620 *ctx); | |
122 | static void sii8620_enable_hpd(struct sii8620 *ctx); | |
123 | static void sii8620_mhl_disconnected(struct sii8620 *ctx); | |
2c8fb853 | 124 | static void sii8620_disconnect(struct sii8620 *ctx); |
ce6e153f AH |
125 | |
126 | static int sii8620_clear_error(struct sii8620 *ctx) | |
127 | { | |
128 | int ret = ctx->error; | |
129 | ||
130 | ctx->error = 0; | |
131 | return ret; | |
132 | } | |
133 | ||
134 | static void sii8620_read_buf(struct sii8620 *ctx, u16 addr, u8 *buf, int len) | |
135 | { | |
136 | struct device *dev = ctx->dev; | |
137 | struct i2c_client *client = to_i2c_client(dev); | |
138 | u8 data = addr; | |
139 | struct i2c_msg msg[] = { | |
140 | { | |
141 | .addr = sii8620_i2c_page[addr >> 8], | |
142 | .flags = client->flags, | |
143 | .len = 1, | |
144 | .buf = &data | |
145 | }, | |
146 | { | |
147 | .addr = sii8620_i2c_page[addr >> 8], | |
148 | .flags = client->flags | I2C_M_RD, | |
149 | .len = len, | |
150 | .buf = buf | |
151 | }, | |
152 | }; | |
153 | int ret; | |
154 | ||
155 | if (ctx->error) | |
156 | return; | |
157 | ||
158 | ret = i2c_transfer(client->adapter, msg, 2); | |
159 | dev_dbg(dev, "read at %04x: %*ph, %d\n", addr, len, buf, ret); | |
160 | ||
161 | if (ret != 2) { | |
162 | dev_err(dev, "Read at %#06x of %d bytes failed with code %d.\n", | |
163 | addr, len, ret); | |
164 | ctx->error = ret < 0 ? ret : -EIO; | |
165 | } | |
166 | } | |
167 | ||
168 | static u8 sii8620_readb(struct sii8620 *ctx, u16 addr) | |
169 | { | |
170 | u8 ret; | |
171 | ||
172 | sii8620_read_buf(ctx, addr, &ret, 1); | |
173 | return ret; | |
174 | } | |
175 | ||
176 | static void sii8620_write_buf(struct sii8620 *ctx, u16 addr, const u8 *buf, | |
177 | int len) | |
178 | { | |
179 | struct device *dev = ctx->dev; | |
180 | struct i2c_client *client = to_i2c_client(dev); | |
181 | u8 data[2]; | |
182 | struct i2c_msg msg = { | |
183 | .addr = sii8620_i2c_page[addr >> 8], | |
184 | .flags = client->flags, | |
185 | .len = len + 1, | |
186 | }; | |
187 | int ret; | |
188 | ||
189 | if (ctx->error) | |
190 | return; | |
191 | ||
192 | if (len > 1) { | |
193 | msg.buf = kmalloc(len + 1, GFP_KERNEL); | |
194 | if (!msg.buf) { | |
195 | ctx->error = -ENOMEM; | |
196 | return; | |
197 | } | |
198 | memcpy(msg.buf + 1, buf, len); | |
199 | } else { | |
200 | msg.buf = data; | |
201 | msg.buf[1] = *buf; | |
202 | } | |
203 | ||
204 | msg.buf[0] = addr; | |
205 | ||
206 | ret = i2c_transfer(client->adapter, &msg, 1); | |
207 | dev_dbg(dev, "write at %04x: %*ph, %d\n", addr, len, buf, ret); | |
208 | ||
209 | if (ret != 1) { | |
210 | dev_err(dev, "Write at %#06x of %*ph failed with code %d.\n", | |
211 | addr, len, buf, ret); | |
212 | ctx->error = ret ?: -EIO; | |
213 | } | |
214 | ||
215 | if (len > 1) | |
216 | kfree(msg.buf); | |
217 | } | |
218 | ||
219 | #define sii8620_write(ctx, addr, arr...) \ | |
220 | ({\ | |
221 | u8 d[] = { arr }; \ | |
222 | sii8620_write_buf(ctx, addr, d, ARRAY_SIZE(d)); \ | |
223 | }) | |
224 | ||
225 | static void __sii8620_write_seq(struct sii8620 *ctx, const u16 *seq, int len) | |
226 | { | |
227 | int i; | |
228 | ||
229 | for (i = 0; i < len; i += 2) | |
230 | sii8620_write(ctx, seq[i], seq[i + 1]); | |
231 | } | |
232 | ||
233 | #define sii8620_write_seq(ctx, seq...) \ | |
234 | ({\ | |
235 | const u16 d[] = { seq }; \ | |
236 | __sii8620_write_seq(ctx, d, ARRAY_SIZE(d)); \ | |
237 | }) | |
238 | ||
239 | #define sii8620_write_seq_static(ctx, seq...) \ | |
240 | ({\ | |
241 | static const u16 d[] = { seq }; \ | |
242 | __sii8620_write_seq(ctx, d, ARRAY_SIZE(d)); \ | |
243 | }) | |
244 | ||
245 | static void sii8620_setbits(struct sii8620 *ctx, u16 addr, u8 mask, u8 val) | |
246 | { | |
247 | val = (val & mask) | (sii8620_readb(ctx, addr) & ~mask); | |
248 | sii8620_write(ctx, addr, val); | |
249 | } | |
250 | ||
bb4954c7 AH |
251 | static inline bool sii8620_is_mhl3(struct sii8620 *ctx) |
252 | { | |
253 | return ctx->mode >= CM_MHL3; | |
254 | } | |
255 | ||
ce6e153f AH |
256 | static void sii8620_mt_cleanup(struct sii8620 *ctx) |
257 | { | |
258 | struct sii8620_mt_msg *msg, *n; | |
259 | ||
260 | list_for_each_entry_safe(msg, n, &ctx->mt_queue, node) { | |
261 | list_del(&msg->node); | |
262 | kfree(msg); | |
263 | } | |
264 | ctx->mt_state = MT_STATE_READY; | |
265 | } | |
266 | ||
267 | static void sii8620_mt_work(struct sii8620 *ctx) | |
268 | { | |
269 | struct sii8620_mt_msg *msg; | |
270 | ||
271 | if (ctx->error) | |
272 | return; | |
273 | if (ctx->mt_state == MT_STATE_BUSY || list_empty(&ctx->mt_queue)) | |
274 | return; | |
275 | ||
276 | if (ctx->mt_state == MT_STATE_DONE) { | |
277 | ctx->mt_state = MT_STATE_READY; | |
278 | msg = list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, | |
279 | node); | |
d6d59c5a | 280 | list_del(&msg->node); |
ce6e153f AH |
281 | if (msg->recv) |
282 | msg->recv(ctx, msg); | |
0c2d1875 AH |
283 | if (msg->continuation) |
284 | msg->continuation(ctx, msg->ret); | |
ce6e153f AH |
285 | kfree(msg); |
286 | } | |
287 | ||
288 | if (ctx->mt_state != MT_STATE_READY || list_empty(&ctx->mt_queue)) | |
289 | return; | |
290 | ||
291 | ctx->mt_state = MT_STATE_BUSY; | |
292 | msg = list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, node); | |
293 | if (msg->send) | |
294 | msg->send(ctx, msg); | |
295 | } | |
296 | ||
26a4cef8 AH |
297 | static void sii8620_enable_gen2_write_burst(struct sii8620 *ctx) |
298 | { | |
299 | u8 ctrl = BIT_MDT_RCV_CTRL_MDT_RCV_EN; | |
300 | ||
301 | if (ctx->gen2_write_burst) | |
302 | return; | |
303 | ||
304 | if (ctx->mode >= CM_MHL1) | |
305 | ctrl |= BIT_MDT_RCV_CTRL_MDT_DELAY_RCV_EN; | |
306 | ||
307 | sii8620_write_seq(ctx, | |
308 | REG_MDT_RCV_TIMEOUT, 100, | |
309 | REG_MDT_RCV_CTRL, ctrl | |
310 | ); | |
311 | ctx->gen2_write_burst = 1; | |
312 | } | |
313 | ||
314 | static void sii8620_disable_gen2_write_burst(struct sii8620 *ctx) | |
315 | { | |
316 | if (!ctx->gen2_write_burst) | |
317 | return; | |
318 | ||
319 | sii8620_write_seq_static(ctx, | |
320 | REG_MDT_XMIT_CTRL, 0, | |
321 | REG_MDT_RCV_CTRL, 0 | |
322 | ); | |
323 | ctx->gen2_write_burst = 0; | |
324 | } | |
325 | ||
326 | static void sii8620_start_gen2_write_burst(struct sii8620 *ctx) | |
327 | { | |
328 | sii8620_write_seq_static(ctx, | |
329 | REG_MDT_INT_1_MASK, BIT_MDT_RCV_TIMEOUT | |
330 | | BIT_MDT_RCV_SM_ABORT_PKT_RCVD | BIT_MDT_RCV_SM_ERROR | |
331 | | BIT_MDT_XMIT_TIMEOUT | BIT_MDT_XMIT_SM_ABORT_PKT_RCVD | |
332 | | BIT_MDT_XMIT_SM_ERROR, | |
333 | REG_MDT_INT_0_MASK, BIT_MDT_XFIFO_EMPTY | |
334 | | BIT_MDT_IDLE_AFTER_HAWB_DISABLE | |
335 | | BIT_MDT_RFIFO_DATA_RDY | |
336 | ); | |
337 | sii8620_enable_gen2_write_burst(ctx); | |
338 | } | |
339 | ||
ce6e153f AH |
340 | static void sii8620_mt_msc_cmd_send(struct sii8620 *ctx, |
341 | struct sii8620_mt_msg *msg) | |
342 | { | |
26a4cef8 AH |
343 | if (msg->reg[0] == MHL_SET_INT && |
344 | msg->reg[1] == MHL_INT_REG(RCHANGE) && | |
345 | msg->reg[2] == MHL_INT_RC_FEAT_REQ) | |
346 | sii8620_enable_gen2_write_burst(ctx); | |
347 | else | |
348 | sii8620_disable_gen2_write_burst(ctx); | |
349 | ||
ce6e153f AH |
350 | switch (msg->reg[0]) { |
351 | case MHL_WRITE_STAT: | |
352 | case MHL_SET_INT: | |
353 | sii8620_write_buf(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg + 1, 2); | |
354 | sii8620_write(ctx, REG_MSC_COMMAND_START, | |
355 | BIT_MSC_COMMAND_START_WRITE_STAT); | |
356 | break; | |
357 | case MHL_MSC_MSG: | |
358 | sii8620_write_buf(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg, 3); | |
359 | sii8620_write(ctx, REG_MSC_COMMAND_START, | |
360 | BIT_MSC_COMMAND_START_MSC_MSG); | |
361 | break; | |
e9c6da27 AH |
362 | case MHL_READ_DEVCAP_REG: |
363 | case MHL_READ_XDEVCAP_REG: | |
364 | sii8620_write(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg[1]); | |
365 | sii8620_write(ctx, REG_MSC_COMMAND_START, | |
366 | BIT_MSC_COMMAND_START_READ_DEVCAP); | |
367 | break; | |
ce6e153f AH |
368 | default: |
369 | dev_err(ctx->dev, "%s: command %#x not supported\n", __func__, | |
370 | msg->reg[0]); | |
371 | } | |
372 | } | |
373 | ||
374 | static struct sii8620_mt_msg *sii8620_mt_msg_new(struct sii8620 *ctx) | |
375 | { | |
376 | struct sii8620_mt_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL); | |
377 | ||
378 | if (!msg) | |
379 | ctx->error = -ENOMEM; | |
380 | else | |
381 | list_add_tail(&msg->node, &ctx->mt_queue); | |
382 | ||
383 | return msg; | |
384 | } | |
385 | ||
0c2d1875 AH |
386 | static void sii8620_mt_set_cont(struct sii8620 *ctx, sii8620_cb cont) |
387 | { | |
388 | struct sii8620_mt_msg *msg; | |
389 | ||
390 | if (ctx->error) | |
391 | return; | |
392 | ||
393 | if (list_empty(&ctx->mt_queue)) { | |
394 | ctx->error = -EINVAL; | |
395 | return; | |
396 | } | |
397 | msg = list_last_entry(&ctx->mt_queue, struct sii8620_mt_msg, node); | |
398 | msg->continuation = cont; | |
399 | } | |
400 | ||
ce6e153f AH |
401 | static void sii8620_mt_msc_cmd(struct sii8620 *ctx, u8 cmd, u8 arg1, u8 arg2) |
402 | { | |
403 | struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); | |
404 | ||
405 | if (!msg) | |
406 | return; | |
407 | ||
408 | msg->reg[0] = cmd; | |
409 | msg->reg[1] = arg1; | |
410 | msg->reg[2] = arg2; | |
411 | msg->send = sii8620_mt_msc_cmd_send; | |
412 | } | |
413 | ||
414 | static void sii8620_mt_write_stat(struct sii8620 *ctx, u8 reg, u8 val) | |
415 | { | |
416 | sii8620_mt_msc_cmd(ctx, MHL_WRITE_STAT, reg, val); | |
417 | } | |
418 | ||
419 | static inline void sii8620_mt_set_int(struct sii8620 *ctx, u8 irq, u8 mask) | |
420 | { | |
421 | sii8620_mt_msc_cmd(ctx, MHL_SET_INT, irq, mask); | |
422 | } | |
423 | ||
424 | static void sii8620_mt_msc_msg(struct sii8620 *ctx, u8 cmd, u8 data) | |
425 | { | |
426 | sii8620_mt_msc_cmd(ctx, MHL_MSC_MSG, cmd, data); | |
427 | } | |
428 | ||
429 | static void sii8620_mt_rap(struct sii8620 *ctx, u8 code) | |
430 | { | |
431 | sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RAP, code); | |
432 | } | |
433 | ||
434 | static void sii8620_mt_read_devcap_send(struct sii8620 *ctx, | |
435 | struct sii8620_mt_msg *msg) | |
436 | { | |
437 | u8 ctrl = BIT_EDID_CTRL_DEVCAP_SELECT_DEVCAP | |
438 | | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO | |
439 | | BIT_EDID_CTRL_EDID_MODE_EN; | |
440 | ||
441 | if (msg->reg[0] == MHL_READ_XDEVCAP) | |
442 | ctrl |= BIT_EDID_CTRL_XDEVCAP_EN; | |
443 | ||
444 | sii8620_write_seq(ctx, | |
445 | REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE, | |
446 | REG_EDID_CTRL, ctrl, | |
447 | REG_TPI_CBUS_START, BIT_TPI_CBUS_START_GET_DEVCAP_START | |
448 | ); | |
449 | } | |
450 | ||
451 | /* copy src to dst and set changed bits in src */ | |
452 | static void sii8620_update_array(u8 *dst, u8 *src, int count) | |
453 | { | |
454 | while (--count >= 0) { | |
455 | *src ^= *dst; | |
456 | *dst++ ^= *src++; | |
457 | } | |
458 | } | |
459 | ||
9a466cd1 | 460 | static void sii8620_sink_detected(struct sii8620 *ctx, int ret) |
ce6e153f AH |
461 | { |
462 | static const char * const sink_str[] = { | |
463 | [SINK_NONE] = "NONE", | |
464 | [SINK_HDMI] = "HDMI", | |
465 | [SINK_DVI] = "DVI" | |
466 | }; | |
467 | ||
ce6e153f AH |
468 | char sink_name[20]; |
469 | struct device *dev = ctx->dev; | |
470 | ||
9a466cd1 | 471 | if (ret < 0) |
ce6e153f AH |
472 | return; |
473 | ||
474 | sii8620_fetch_edid(ctx); | |
475 | if (!ctx->edid) { | |
476 | dev_err(ctx->dev, "Cannot fetch EDID\n"); | |
477 | sii8620_mhl_disconnected(ctx); | |
478 | return; | |
479 | } | |
480 | ||
481 | if (drm_detect_hdmi_monitor(ctx->edid)) | |
482 | ctx->sink_type = SINK_HDMI; | |
483 | else | |
484 | ctx->sink_type = SINK_DVI; | |
485 | ||
486 | drm_edid_get_monitor_name(ctx->edid, sink_name, ARRAY_SIZE(sink_name)); | |
487 | ||
488 | dev_info(dev, "detected sink(type: %s): %s\n", | |
489 | sink_str[ctx->sink_type], sink_name); | |
263b5c93 AH |
490 | } |
491 | ||
a21e658b AH |
492 | static void sii8620_hsic_init(struct sii8620 *ctx) |
493 | { | |
494 | if (!sii8620_is_mhl3(ctx)) | |
495 | return; | |
496 | ||
497 | sii8620_write(ctx, REG_FCGC, | |
498 | BIT_FCGC_HSIC_HOSTMODE | BIT_FCGC_HSIC_ENABLE); | |
499 | sii8620_setbits(ctx, REG_HRXCTRL3, | |
500 | BIT_HRXCTRL3_HRX_STAY_RESET | BIT_HRXCTRL3_STATUS_EN, ~0); | |
501 | sii8620_setbits(ctx, REG_TTXNUMB, MSK_TTXNUMB_TTX_NUMBPS, 4); | |
502 | sii8620_setbits(ctx, REG_TRXCTRL, BIT_TRXCTRL_TRX_FROM_SE_COC, ~0); | |
503 | sii8620_setbits(ctx, REG_HTXCTRL, BIT_HTXCTRL_HTX_DRVCONN1, 0); | |
504 | sii8620_setbits(ctx, REG_KEEPER, MSK_KEEPER_MODE, VAL_KEEPER_MODE_HOST); | |
505 | sii8620_write_seq_static(ctx, | |
506 | REG_TDMLLCTL, 0, | |
507 | REG_UTSRST, BIT_UTSRST_HRX_SRST | BIT_UTSRST_HTX_SRST | | |
508 | BIT_UTSRST_KEEPER_SRST | BIT_UTSRST_FC_SRST, | |
509 | REG_UTSRST, BIT_UTSRST_HRX_SRST | BIT_UTSRST_HTX_SRST, | |
510 | REG_HRXINTL, 0xff, | |
511 | REG_HRXINTH, 0xff, | |
512 | REG_TTXINTL, 0xff, | |
513 | REG_TTXINTH, 0xff, | |
514 | REG_TRXINTL, 0xff, | |
515 | REG_TRXINTH, 0xff, | |
516 | REG_HTXINTL, 0xff, | |
517 | REG_HTXINTH, 0xff, | |
518 | REG_FCINTR0, 0xff, | |
519 | REG_FCINTR1, 0xff, | |
520 | REG_FCINTR2, 0xff, | |
521 | REG_FCINTR3, 0xff, | |
522 | REG_FCINTR4, 0xff, | |
523 | REG_FCINTR5, 0xff, | |
524 | REG_FCINTR6, 0xff, | |
525 | REG_FCINTR7, 0xff | |
526 | ); | |
527 | } | |
528 | ||
263b5c93 AH |
529 | static void sii8620_edid_read(struct sii8620 *ctx, int ret) |
530 | { | |
531 | if (ret < 0) | |
532 | return; | |
533 | ||
ce6e153f | 534 | sii8620_set_upstream_edid(ctx); |
a21e658b | 535 | sii8620_hsic_init(ctx); |
ce6e153f AH |
536 | sii8620_enable_hpd(ctx); |
537 | } | |
538 | ||
9a466cd1 AH |
539 | static void sii8620_mr_devcap(struct sii8620 *ctx) |
540 | { | |
541 | u8 dcap[MHL_DCAP_SIZE]; | |
542 | struct device *dev = ctx->dev; | |
543 | ||
544 | sii8620_read_buf(ctx, REG_EDID_FIFO_RD_DATA, dcap, MHL_DCAP_SIZE); | |
545 | if (ctx->error < 0) | |
546 | return; | |
547 | ||
548 | dev_info(dev, "detected dongle MHL %d.%d, ChipID %02x%02x:%02x%02x\n", | |
549 | dcap[MHL_DCAP_MHL_VERSION] / 16, | |
550 | dcap[MHL_DCAP_MHL_VERSION] % 16, | |
551 | dcap[MHL_DCAP_ADOPTER_ID_H], dcap[MHL_DCAP_ADOPTER_ID_L], | |
552 | dcap[MHL_DCAP_DEVICE_ID_H], dcap[MHL_DCAP_DEVICE_ID_L]); | |
553 | sii8620_update_array(ctx->devcap, dcap, MHL_DCAP_SIZE); | |
554 | } | |
555 | ||
ce6e153f AH |
556 | static void sii8620_mr_xdevcap(struct sii8620 *ctx) |
557 | { | |
558 | sii8620_read_buf(ctx, REG_EDID_FIFO_RD_DATA, ctx->xdevcap, | |
559 | MHL_XDC_SIZE); | |
ce6e153f AH |
560 | } |
561 | ||
562 | static void sii8620_mt_read_devcap_recv(struct sii8620 *ctx, | |
563 | struct sii8620_mt_msg *msg) | |
564 | { | |
565 | u8 ctrl = BIT_EDID_CTRL_DEVCAP_SELECT_DEVCAP | |
566 | | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO | |
567 | | BIT_EDID_CTRL_EDID_MODE_EN; | |
568 | ||
569 | if (msg->reg[0] == MHL_READ_XDEVCAP) | |
570 | ctrl |= BIT_EDID_CTRL_XDEVCAP_EN; | |
571 | ||
572 | sii8620_write_seq(ctx, | |
573 | REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE | BIT_INTR9_EDID_DONE | |
574 | | BIT_INTR9_EDID_ERROR, | |
575 | REG_EDID_CTRL, ctrl, | |
576 | REG_EDID_FIFO_ADDR, 0 | |
577 | ); | |
578 | ||
579 | if (msg->reg[0] == MHL_READ_XDEVCAP) | |
580 | sii8620_mr_xdevcap(ctx); | |
581 | else | |
582 | sii8620_mr_devcap(ctx); | |
583 | } | |
584 | ||
585 | static void sii8620_mt_read_devcap(struct sii8620 *ctx, bool xdevcap) | |
586 | { | |
587 | struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); | |
588 | ||
589 | if (!msg) | |
590 | return; | |
591 | ||
592 | msg->reg[0] = xdevcap ? MHL_READ_XDEVCAP : MHL_READ_DEVCAP; | |
593 | msg->send = sii8620_mt_read_devcap_send; | |
594 | msg->recv = sii8620_mt_read_devcap_recv; | |
595 | } | |
596 | ||
e9c6da27 AH |
597 | static void sii8620_mt_read_devcap_reg_recv(struct sii8620 *ctx, |
598 | struct sii8620_mt_msg *msg) | |
599 | { | |
600 | u8 reg = msg->reg[0] & 0x7f; | |
601 | ||
602 | if (msg->reg[0] & 0x80) | |
603 | ctx->xdevcap[reg] = msg->ret; | |
604 | else | |
605 | ctx->devcap[reg] = msg->ret; | |
606 | } | |
607 | ||
608 | static void sii8620_mt_read_devcap_reg(struct sii8620 *ctx, u8 reg) | |
609 | { | |
610 | struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); | |
611 | ||
612 | if (!msg) | |
613 | return; | |
614 | ||
615 | msg->reg[0] = (reg & 0x80) ? MHL_READ_XDEVCAP_REG : MHL_READ_DEVCAP_REG; | |
616 | msg->reg[1] = reg; | |
617 | msg->send = sii8620_mt_msc_cmd_send; | |
618 | msg->recv = sii8620_mt_read_devcap_reg_recv; | |
619 | } | |
620 | ||
621 | static inline void sii8620_mt_read_xdevcap_reg(struct sii8620 *ctx, u8 reg) | |
622 | { | |
623 | sii8620_mt_read_devcap_reg(ctx, reg | 0x80); | |
624 | } | |
625 | ||
e19e9c69 AH |
626 | static void *sii8620_burst_get_tx_buf(struct sii8620 *ctx, int len) |
627 | { | |
628 | u8 *buf = &ctx->burst.tx_buf[ctx->burst.tx_count]; | |
629 | int size = len + 2; | |
630 | ||
631 | if (ctx->burst.tx_count + size > ARRAY_SIZE(ctx->burst.tx_buf)) { | |
632 | dev_err(ctx->dev, "TX-BLK buffer exhausted\n"); | |
633 | ctx->error = -EINVAL; | |
634 | return NULL; | |
635 | } | |
636 | ||
637 | ctx->burst.tx_count += size; | |
638 | buf[1] = len; | |
639 | ||
640 | return buf + 2; | |
641 | } | |
642 | ||
643 | static u8 *sii8620_burst_get_rx_buf(struct sii8620 *ctx, int len) | |
644 | { | |
645 | u8 *buf = &ctx->burst.rx_buf[ctx->burst.rx_count]; | |
646 | int size = len + 1; | |
647 | ||
648 | if (ctx->burst.tx_count + size > ARRAY_SIZE(ctx->burst.tx_buf)) { | |
649 | dev_err(ctx->dev, "RX-BLK buffer exhausted\n"); | |
650 | ctx->error = -EINVAL; | |
651 | return NULL; | |
652 | } | |
653 | ||
654 | ctx->burst.rx_count += size; | |
655 | buf[0] = len; | |
656 | ||
657 | return buf + 1; | |
658 | } | |
659 | ||
660 | static void sii8620_burst_send(struct sii8620 *ctx) | |
661 | { | |
662 | int tx_left = ctx->burst.tx_count; | |
663 | u8 *d = ctx->burst.tx_buf; | |
664 | ||
665 | while (tx_left > 0) { | |
666 | int len = d[1] + 2; | |
667 | ||
668 | if (ctx->burst.r_count + len > ctx->burst.r_size) | |
669 | break; | |
670 | d[0] = min(ctx->burst.rx_ack, 255); | |
671 | ctx->burst.rx_ack -= d[0]; | |
672 | sii8620_write_buf(ctx, REG_EMSC_XMIT_WRITE_PORT, d, len); | |
673 | ctx->burst.r_count += len; | |
674 | tx_left -= len; | |
675 | d += len; | |
676 | } | |
677 | ||
678 | ctx->burst.tx_count = tx_left; | |
679 | ||
680 | while (ctx->burst.rx_ack > 0) { | |
681 | u8 b[2] = { min(ctx->burst.rx_ack, 255), 0 }; | |
682 | ||
683 | if (ctx->burst.r_count + 2 > ctx->burst.r_size) | |
684 | break; | |
685 | ctx->burst.rx_ack -= b[0]; | |
686 | sii8620_write_buf(ctx, REG_EMSC_XMIT_WRITE_PORT, b, 2); | |
687 | ctx->burst.r_count += 2; | |
688 | } | |
689 | } | |
690 | ||
691 | static void sii8620_burst_receive(struct sii8620 *ctx) | |
692 | { | |
693 | u8 buf[3], *d; | |
694 | int count; | |
695 | ||
696 | sii8620_read_buf(ctx, REG_EMSCRFIFOBCNTL, buf, 2); | |
697 | count = get_unaligned_le16(buf); | |
698 | while (count > 0) { | |
699 | int len = min(count, 3); | |
700 | ||
701 | sii8620_read_buf(ctx, REG_EMSC_RCV_READ_PORT, buf, len); | |
702 | count -= len; | |
703 | ctx->burst.rx_ack += len - 1; | |
704 | ctx->burst.r_count -= buf[1]; | |
705 | if (ctx->burst.r_count < 0) | |
706 | ctx->burst.r_count = 0; | |
707 | ||
708 | if (len < 3 || !buf[2]) | |
709 | continue; | |
710 | ||
711 | len = buf[2]; | |
712 | d = sii8620_burst_get_rx_buf(ctx, len); | |
713 | if (!d) | |
714 | continue; | |
715 | sii8620_read_buf(ctx, REG_EMSC_RCV_READ_PORT, d, len); | |
716 | count -= len; | |
717 | ctx->burst.rx_ack += len; | |
718 | } | |
719 | } | |
720 | ||
721 | static void sii8620_burst_tx_rbuf_info(struct sii8620 *ctx, int size) | |
722 | { | |
723 | struct mhl_burst_blk_rcv_buffer_info *d = | |
724 | sii8620_burst_get_tx_buf(ctx, sizeof(*d)); | |
725 | if (!d) | |
726 | return; | |
727 | ||
728 | d->id = cpu_to_be16(MHL_BURST_ID_BLK_RCV_BUFFER_INFO); | |
729 | d->size = cpu_to_le16(size); | |
730 | } | |
731 | ||
bf1722ca AH |
732 | static u8 sii8620_checksum(void *ptr, int size) |
733 | { | |
734 | u8 *d = ptr, sum = 0; | |
735 | ||
736 | while (size--) | |
737 | sum += *d++; | |
738 | ||
739 | return sum; | |
740 | } | |
741 | ||
742 | static void sii8620_mhl_burst_hdr_set(struct mhl3_burst_header *h, | |
743 | enum mhl_burst_id id) | |
744 | { | |
745 | h->id = cpu_to_be16(id); | |
746 | h->total_entries = 1; | |
747 | h->sequence_index = 1; | |
748 | } | |
749 | ||
750 | static void sii8620_burst_tx_bits_per_pixel_fmt(struct sii8620 *ctx, u8 fmt) | |
751 | { | |
752 | struct mhl_burst_bits_per_pixel_fmt *d; | |
753 | const int size = sizeof(*d) + sizeof(d->desc[0]); | |
754 | ||
755 | d = sii8620_burst_get_tx_buf(ctx, size); | |
756 | if (!d) | |
757 | return; | |
758 | ||
759 | sii8620_mhl_burst_hdr_set(&d->hdr, MHL_BURST_ID_BITS_PER_PIXEL_FMT); | |
760 | d->num_entries = 1; | |
761 | d->desc[0].stream_id = 0; | |
762 | d->desc[0].pixel_format = fmt; | |
763 | d->hdr.checksum -= sii8620_checksum(d, size); | |
764 | } | |
765 | ||
e19e9c69 AH |
766 | static void sii8620_burst_rx_all(struct sii8620 *ctx) |
767 | { | |
768 | u8 *d = ctx->burst.rx_buf; | |
769 | int count = ctx->burst.rx_count; | |
770 | ||
771 | while (count-- > 0) { | |
772 | int len = *d++; | |
773 | int id = get_unaligned_be16(&d[0]); | |
774 | ||
775 | switch (id) { | |
776 | case MHL_BURST_ID_BLK_RCV_BUFFER_INFO: | |
777 | ctx->burst.r_size = get_unaligned_le16(&d[2]); | |
778 | break; | |
779 | default: | |
780 | break; | |
781 | } | |
782 | count -= len; | |
783 | d += len; | |
784 | } | |
785 | ctx->burst.rx_count = 0; | |
786 | } | |
787 | ||
ce6e153f AH |
788 | static void sii8620_fetch_edid(struct sii8620 *ctx) |
789 | { | |
790 | u8 lm_ddc, ddc_cmd, int3, cbus; | |
791 | int fetched, i; | |
792 | int edid_len = EDID_LENGTH; | |
793 | u8 *edid; | |
794 | ||
795 | sii8620_readb(ctx, REG_CBUS_STATUS); | |
796 | lm_ddc = sii8620_readb(ctx, REG_LM_DDC); | |
797 | ddc_cmd = sii8620_readb(ctx, REG_DDC_CMD); | |
798 | ||
799 | sii8620_write_seq(ctx, | |
800 | REG_INTR9_MASK, 0, | |
801 | REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO, | |
802 | REG_HDCP2X_POLL_CS, 0x71, | |
803 | REG_HDCP2X_CTRL_0, BIT_HDCP2X_CTRL_0_HDCP2X_HDCPTX, | |
804 | REG_LM_DDC, lm_ddc | BIT_LM_DDC_SW_TPI_EN_DISABLED, | |
805 | ); | |
806 | ||
807 | for (i = 0; i < 256; ++i) { | |
808 | u8 ddc_stat = sii8620_readb(ctx, REG_DDC_STATUS); | |
809 | ||
810 | if (!(ddc_stat & BIT_DDC_STATUS_DDC_I2C_IN_PROG)) | |
811 | break; | |
812 | sii8620_write(ctx, REG_DDC_STATUS, | |
813 | BIT_DDC_STATUS_DDC_FIFO_EMPTY); | |
814 | } | |
815 | ||
816 | sii8620_write(ctx, REG_DDC_ADDR, 0x50 << 1); | |
817 | ||
818 | edid = kmalloc(EDID_LENGTH, GFP_KERNEL); | |
819 | if (!edid) { | |
820 | ctx->error = -ENOMEM; | |
821 | return; | |
822 | } | |
823 | ||
824 | #define FETCH_SIZE 16 | |
825 | for (fetched = 0; fetched < edid_len; fetched += FETCH_SIZE) { | |
826 | sii8620_readb(ctx, REG_DDC_STATUS); | |
827 | sii8620_write_seq(ctx, | |
828 | REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_DDC_CMD_ABORT, | |
829 | REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_DDC_CMD_CLEAR_FIFO, | |
830 | REG_DDC_STATUS, BIT_DDC_STATUS_DDC_FIFO_EMPTY | |
831 | ); | |
832 | sii8620_write_seq(ctx, | |
833 | REG_DDC_SEGM, fetched >> 8, | |
834 | REG_DDC_OFFSET, fetched & 0xff, | |
835 | REG_DDC_DIN_CNT1, FETCH_SIZE, | |
836 | REG_DDC_DIN_CNT2, 0, | |
837 | REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_ENH_DDC_READ_NO_ACK | |
838 | ); | |
839 | ||
840 | do { | |
841 | int3 = sii8620_readb(ctx, REG_INTR3); | |
842 | cbus = sii8620_readb(ctx, REG_CBUS_STATUS); | |
843 | ||
844 | if (int3 & BIT_DDC_CMD_DONE) | |
845 | break; | |
846 | ||
847 | if (!(cbus & BIT_CBUS_STATUS_CBUS_CONNECTED)) { | |
848 | kfree(edid); | |
849 | edid = NULL; | |
850 | goto end; | |
851 | } | |
852 | } while (1); | |
853 | ||
854 | sii8620_readb(ctx, REG_DDC_STATUS); | |
855 | while (sii8620_readb(ctx, REG_DDC_DOUT_CNT) < FETCH_SIZE) | |
856 | usleep_range(10, 20); | |
857 | ||
858 | sii8620_read_buf(ctx, REG_DDC_DATA, edid + fetched, FETCH_SIZE); | |
859 | if (fetched + FETCH_SIZE == EDID_LENGTH) { | |
860 | u8 ext = ((struct edid *)edid)->extensions; | |
861 | ||
862 | if (ext) { | |
863 | u8 *new_edid; | |
864 | ||
865 | edid_len += ext * EDID_LENGTH; | |
866 | new_edid = krealloc(edid, edid_len, GFP_KERNEL); | |
867 | if (!new_edid) { | |
868 | kfree(edid); | |
869 | ctx->error = -ENOMEM; | |
870 | return; | |
871 | } | |
872 | edid = new_edid; | |
873 | } | |
874 | } | |
ce6e153f AH |
875 | } |
876 | ||
263b5c93 AH |
877 | sii8620_write_seq(ctx, |
878 | REG_INTR3_MASK, BIT_DDC_CMD_DONE, | |
879 | REG_LM_DDC, lm_ddc | |
880 | ); | |
ce6e153f AH |
881 | |
882 | end: | |
883 | kfree(ctx->edid); | |
884 | ctx->edid = (struct edid *)edid; | |
885 | } | |
886 | ||
887 | static void sii8620_set_upstream_edid(struct sii8620 *ctx) | |
888 | { | |
889 | sii8620_setbits(ctx, REG_DPD, BIT_DPD_PDNRX12 | BIT_DPD_PDIDCK_N | |
890 | | BIT_DPD_PD_MHL_CLK_N, 0xff); | |
891 | ||
892 | sii8620_write_seq_static(ctx, | |
893 | REG_RX_HDMI_CTRL3, 0x00, | |
894 | REG_PKT_FILTER_0, 0xFF, | |
895 | REG_PKT_FILTER_1, 0xFF, | |
896 | REG_ALICE0_BW_I2C, 0x06 | |
897 | ); | |
898 | ||
899 | sii8620_setbits(ctx, REG_RX_HDMI_CLR_BUFFER, | |
900 | BIT_RX_HDMI_CLR_BUFFER_VSI_CLR_EN, 0xff); | |
901 | ||
902 | sii8620_write_seq_static(ctx, | |
903 | REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO | |
904 | | BIT_EDID_CTRL_EDID_MODE_EN, | |
905 | REG_EDID_FIFO_ADDR, 0, | |
906 | ); | |
907 | ||
908 | sii8620_write_buf(ctx, REG_EDID_FIFO_WR_DATA, (u8 *)ctx->edid, | |
909 | (ctx->edid->extensions + 1) * EDID_LENGTH); | |
910 | ||
911 | sii8620_write_seq_static(ctx, | |
912 | REG_EDID_CTRL, BIT_EDID_CTRL_EDID_PRIME_VALID | |
913 | | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO | |
914 | | BIT_EDID_CTRL_EDID_MODE_EN, | |
915 | REG_INTR5_MASK, BIT_INTR_SCDT_CHANGE, | |
916 | REG_INTR9_MASK, 0 | |
917 | ); | |
918 | } | |
919 | ||
920 | static void sii8620_xtal_set_rate(struct sii8620 *ctx) | |
921 | { | |
922 | static const struct { | |
923 | unsigned int rate; | |
924 | u8 div; | |
925 | u8 tp1; | |
926 | } rates[] = { | |
927 | { 19200, 0x04, 0x53 }, | |
928 | { 20000, 0x04, 0x62 }, | |
929 | { 24000, 0x05, 0x75 }, | |
930 | { 30000, 0x06, 0x92 }, | |
931 | { 38400, 0x0c, 0xbc }, | |
932 | }; | |
933 | unsigned long rate = clk_get_rate(ctx->clk_xtal) / 1000; | |
934 | int i; | |
935 | ||
936 | for (i = 0; i < ARRAY_SIZE(rates) - 1; ++i) | |
937 | if (rate <= rates[i].rate) | |
938 | break; | |
939 | ||
940 | if (rate != rates[i].rate) | |
941 | dev_err(ctx->dev, "xtal clock rate(%lukHz) not supported, setting MHL for %ukHz.\n", | |
942 | rate, rates[i].rate); | |
943 | ||
944 | sii8620_write(ctx, REG_DIV_CTL_MAIN, rates[i].div); | |
945 | sii8620_write(ctx, REG_HDCP2X_TP1, rates[i].tp1); | |
946 | } | |
947 | ||
948 | static int sii8620_hw_on(struct sii8620 *ctx) | |
949 | { | |
950 | int ret; | |
951 | ||
952 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); | |
953 | if (ret) | |
954 | return ret; | |
955 | usleep_range(10000, 20000); | |
956 | return clk_prepare_enable(ctx->clk_xtal); | |
957 | } | |
958 | ||
959 | static int sii8620_hw_off(struct sii8620 *ctx) | |
960 | { | |
961 | clk_disable_unprepare(ctx->clk_xtal); | |
962 | gpiod_set_value(ctx->gpio_reset, 1); | |
963 | return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); | |
964 | } | |
965 | ||
966 | static void sii8620_hw_reset(struct sii8620 *ctx) | |
967 | { | |
968 | usleep_range(10000, 20000); | |
969 | gpiod_set_value(ctx->gpio_reset, 0); | |
970 | usleep_range(5000, 20000); | |
971 | gpiod_set_value(ctx->gpio_reset, 1); | |
972 | usleep_range(10000, 20000); | |
973 | gpiod_set_value(ctx->gpio_reset, 0); | |
974 | msleep(300); | |
975 | } | |
976 | ||
977 | static void sii8620_cbus_reset(struct sii8620 *ctx) | |
978 | { | |
4dc3c071 AH |
979 | sii8620_write(ctx, REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST |
980 | | BIT_PWD_SRST_CBUS_RST_SW_EN); | |
981 | usleep_range(10000, 20000); | |
982 | sii8620_write(ctx, REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST_SW_EN); | |
ce6e153f AH |
983 | } |
984 | ||
985 | static void sii8620_set_auto_zone(struct sii8620 *ctx) | |
986 | { | |
987 | if (ctx->mode != CM_MHL1) { | |
988 | sii8620_write_seq_static(ctx, | |
989 | REG_TX_ZONE_CTL1, 0x0, | |
990 | REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X | |
991 | | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL | |
992 | | BIT_MHL_PLL_CTL0_ZONE_MASK_OE | |
993 | ); | |
994 | } else { | |
995 | sii8620_write_seq_static(ctx, | |
996 | REG_TX_ZONE_CTL1, VAL_TX_ZONE_CTL1_TX_ZONE_CTRL_MODE, | |
997 | REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X | |
998 | | BIT_MHL_PLL_CTL0_ZONE_MASK_OE | |
999 | ); | |
1000 | } | |
1001 | } | |
1002 | ||
1003 | static void sii8620_stop_video(struct sii8620 *ctx) | |
1004 | { | |
1005 | u8 uninitialized_var(val); | |
1006 | ||
1007 | sii8620_write_seq_static(ctx, | |
1008 | REG_TPI_INTR_EN, 0, | |
1009 | REG_HDCP2X_INTR0_MASK, 0, | |
1010 | REG_TPI_COPP_DATA2, 0, | |
1011 | REG_TPI_INTR_ST0, ~0, | |
1012 | ); | |
1013 | ||
1014 | switch (ctx->sink_type) { | |
1015 | case SINK_DVI: | |
1016 | val = BIT_TPI_SC_REG_TMDS_OE_POWER_DOWN | |
1017 | | BIT_TPI_SC_TPI_AV_MUTE; | |
1018 | break; | |
1019 | case SINK_HDMI: | |
ef822a07 | 1020 | default: |
ce6e153f AH |
1021 | val = BIT_TPI_SC_REG_TMDS_OE_POWER_DOWN |
1022 | | BIT_TPI_SC_TPI_AV_MUTE | |
1023 | | BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI; | |
1024 | break; | |
ce6e153f AH |
1025 | } |
1026 | ||
1027 | sii8620_write(ctx, REG_TPI_SC, val); | |
1028 | } | |
1029 | ||
bf1722ca AH |
1030 | static void sii8620_set_format(struct sii8620 *ctx) |
1031 | { | |
1032 | u8 out_fmt; | |
1033 | ||
1034 | if (sii8620_is_mhl3(ctx)) { | |
1035 | sii8620_setbits(ctx, REG_M3_P0CTRL, | |
1036 | BIT_M3_P0CTRL_MHL3_P0_PIXEL_MODE_PACKED, | |
1037 | ctx->use_packed_pixel ? ~0 : 0); | |
1038 | } else { | |
1039 | if (ctx->use_packed_pixel) | |
1040 | sii8620_write_seq_static(ctx, | |
1041 | REG_VID_MODE, BIT_VID_MODE_M1080P, | |
1042 | REG_MHL_TOP_CTL, BIT_MHL_TOP_CTL_MHL_PP_SEL | 1, | |
1043 | REG_MHLTX_CTL6, 0x60 | |
1044 | ); | |
1045 | else | |
1046 | sii8620_write_seq_static(ctx, | |
1047 | REG_VID_MODE, 0, | |
1048 | REG_MHL_TOP_CTL, 1, | |
1049 | REG_MHLTX_CTL6, 0xa0 | |
1050 | ); | |
1051 | } | |
1052 | ||
1053 | if (ctx->use_packed_pixel) | |
1054 | out_fmt = VAL_TPI_FORMAT(YCBCR422, FULL) | | |
1055 | BIT_TPI_OUTPUT_CSCMODE709; | |
1056 | else | |
1057 | out_fmt = VAL_TPI_FORMAT(RGB, FULL); | |
1058 | ||
1059 | sii8620_write_seq(ctx, | |
1060 | REG_TPI_INPUT, VAL_TPI_FORMAT(RGB, FULL), | |
1061 | REG_TPI_OUTPUT, out_fmt, | |
1062 | ); | |
1063 | } | |
1064 | ||
1065 | static int mhl3_infoframe_init(struct mhl3_infoframe *frame) | |
1066 | { | |
1067 | memset(frame, 0, sizeof(*frame)); | |
1068 | ||
1069 | frame->version = 3; | |
1070 | frame->hev_format = -1; | |
1071 | return 0; | |
1072 | } | |
1073 | ||
1074 | static ssize_t mhl3_infoframe_pack(struct mhl3_infoframe *frame, | |
1075 | void *buffer, size_t size) | |
1076 | { | |
1077 | const int frm_len = HDMI_INFOFRAME_HEADER_SIZE + MHL3_INFOFRAME_SIZE; | |
1078 | u8 *ptr = buffer; | |
1079 | ||
1080 | if (size < frm_len) | |
1081 | return -ENOSPC; | |
1082 | ||
1083 | memset(buffer, 0, size); | |
1084 | ptr[0] = HDMI_INFOFRAME_TYPE_VENDOR; | |
1085 | ptr[1] = frame->version; | |
1086 | ptr[2] = MHL3_INFOFRAME_SIZE; | |
1087 | ptr[4] = MHL3_IEEE_OUI & 0xff; | |
1088 | ptr[5] = (MHL3_IEEE_OUI >> 8) & 0xff; | |
1089 | ptr[6] = (MHL3_IEEE_OUI >> 16) & 0xff; | |
1090 | ptr[7] = frame->video_format & 0x3; | |
1091 | ptr[7] |= (frame->format_type & 0x7) << 2; | |
1092 | ptr[7] |= frame->sep_audio ? BIT(5) : 0; | |
1093 | if (frame->hev_format >= 0) { | |
1094 | ptr[9] = 1; | |
1095 | ptr[10] = (frame->hev_format >> 8) & 0xff; | |
1096 | ptr[11] = frame->hev_format & 0xff; | |
1097 | } | |
1098 | if (frame->av_delay) { | |
1099 | bool sign = frame->av_delay < 0; | |
1100 | int delay = sign ? -frame->av_delay : frame->av_delay; | |
1101 | ||
1102 | ptr[12] = (delay >> 16) & 0xf; | |
1103 | if (sign) | |
1104 | ptr[12] |= BIT(4); | |
1105 | ptr[13] = (delay >> 8) & 0xff; | |
1106 | ptr[14] = delay & 0xff; | |
1107 | } | |
1108 | ptr[3] -= sii8620_checksum(buffer, frm_len); | |
1109 | return frm_len; | |
1110 | } | |
1111 | ||
1112 | static void sii8620_set_infoframes(struct sii8620 *ctx) | |
1113 | { | |
1114 | struct mhl3_infoframe mhl_frm; | |
1115 | union hdmi_infoframe frm; | |
1116 | u8 buf[31]; | |
1117 | int ret; | |
1118 | ||
1119 | if (!sii8620_is_mhl3(ctx) || !ctx->use_packed_pixel) { | |
1120 | sii8620_write(ctx, REG_TPI_SC, | |
1121 | BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI); | |
1122 | sii8620_write_buf(ctx, REG_TPI_AVI_CHSUM, ctx->avif + 3, | |
1123 | ARRAY_SIZE(ctx->avif) - 3); | |
1124 | sii8620_write(ctx, REG_PKT_FILTER_0, | |
1125 | BIT_PKT_FILTER_0_DROP_CEA_GAMUT_PKT | | |
1126 | BIT_PKT_FILTER_0_DROP_MPEG_PKT | | |
1127 | BIT_PKT_FILTER_0_DROP_GCP_PKT, | |
1128 | BIT_PKT_FILTER_1_DROP_GEN_PKT); | |
1129 | return; | |
1130 | } | |
1131 | ||
1132 | ret = hdmi_avi_infoframe_init(&frm.avi); | |
1133 | frm.avi.colorspace = HDMI_COLORSPACE_YUV422; | |
1134 | frm.avi.active_aspect = HDMI_ACTIVE_ASPECT_PICTURE; | |
1135 | frm.avi.picture_aspect = HDMI_PICTURE_ASPECT_16_9; | |
1136 | frm.avi.colorimetry = HDMI_COLORIMETRY_ITU_709; | |
1137 | frm.avi.video_code = ctx->video_code; | |
1138 | if (!ret) | |
1139 | ret = hdmi_avi_infoframe_pack(&frm.avi, buf, ARRAY_SIZE(buf)); | |
1140 | if (ret > 0) | |
1141 | sii8620_write_buf(ctx, REG_TPI_AVI_CHSUM, buf + 3, ret - 3); | |
1142 | sii8620_write(ctx, REG_PKT_FILTER_0, | |
1143 | BIT_PKT_FILTER_0_DROP_CEA_GAMUT_PKT | | |
1144 | BIT_PKT_FILTER_0_DROP_MPEG_PKT | | |
1145 | BIT_PKT_FILTER_0_DROP_AVI_PKT | | |
1146 | BIT_PKT_FILTER_0_DROP_GCP_PKT, | |
1147 | BIT_PKT_FILTER_1_VSI_OVERRIDE_DIS | | |
1148 | BIT_PKT_FILTER_1_DROP_GEN_PKT | | |
1149 | BIT_PKT_FILTER_1_DROP_VSIF_PKT); | |
1150 | ||
1151 | sii8620_write(ctx, REG_TPI_INFO_FSEL, BIT_TPI_INFO_FSEL_EN | |
1152 | | BIT_TPI_INFO_FSEL_RPT | VAL_TPI_INFO_FSEL_VSI); | |
1153 | ret = mhl3_infoframe_init(&mhl_frm); | |
1154 | if (!ret) | |
1155 | ret = mhl3_infoframe_pack(&mhl_frm, buf, ARRAY_SIZE(buf)); | |
1156 | sii8620_write_buf(ctx, REG_TPI_INFO_B0, buf, ret); | |
1157 | } | |
1158 | ||
ce6e153f AH |
1159 | static void sii8620_start_hdmi(struct sii8620 *ctx) |
1160 | { | |
1161 | sii8620_write_seq_static(ctx, | |
1162 | REG_RX_HDMI_CTRL2, VAL_RX_HDMI_CTRL2_DEFVAL | |
1163 | | BIT_RX_HDMI_CTRL2_USE_AV_MUTE, | |
1164 | REG_VID_OVRRD, BIT_VID_OVRRD_PP_AUTO_DISABLE | |
bf1722ca AH |
1165 | | BIT_VID_OVRRD_M1080P_OVRRD); |
1166 | sii8620_set_format(ctx); | |
ce6e153f | 1167 | |
bf1722ca AH |
1168 | if (!sii8620_is_mhl3(ctx)) { |
1169 | sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), | |
1170 | MHL_DST_LM_CLK_MODE_NORMAL | MHL_DST_LM_PATH_ENABLED); | |
1171 | sii8620_set_auto_zone(ctx); | |
1172 | } else { | |
1173 | static const struct { | |
1174 | int max_clk; | |
1175 | u8 zone; | |
1176 | u8 link_rate; | |
1177 | u8 rrp_decode; | |
1178 | } clk_spec[] = { | |
1179 | { 150000, VAL_TX_ZONE_CTL3_TX_ZONE_1_5GBPS, | |
1180 | MHL_XDS_LINK_RATE_1_5_GBPS, 0x38 }, | |
1181 | { 300000, VAL_TX_ZONE_CTL3_TX_ZONE_3GBPS, | |
1182 | MHL_XDS_LINK_RATE_3_0_GBPS, 0x40 }, | |
1183 | { 600000, VAL_TX_ZONE_CTL3_TX_ZONE_6GBPS, | |
1184 | MHL_XDS_LINK_RATE_6_0_GBPS, 0x40 }, | |
1185 | }; | |
1186 | u8 p0_ctrl = BIT_M3_P0CTRL_MHL3_P0_PORT_EN; | |
1187 | int clk = ctx->pixel_clock * (ctx->use_packed_pixel ? 2 : 3); | |
1188 | int i; | |
1189 | ||
1190 | for (i = 0; i < ARRAY_SIZE(clk_spec); ++i) | |
1191 | if (clk < clk_spec[i].max_clk) | |
1192 | break; | |
ce6e153f | 1193 | |
bf1722ca AH |
1194 | if (100 * clk >= 98 * clk_spec[i].max_clk) |
1195 | p0_ctrl |= BIT_M3_P0CTRL_MHL3_P0_UNLIMIT_EN; | |
ce6e153f | 1196 | |
bf1722ca AH |
1197 | sii8620_burst_tx_bits_per_pixel_fmt(ctx, ctx->use_packed_pixel); |
1198 | sii8620_burst_send(ctx); | |
1199 | sii8620_write_seq(ctx, | |
1200 | REG_MHL_DP_CTL0, 0xf0, | |
1201 | REG_MHL3_TX_ZONE_CTL, clk_spec[i].zone); | |
1202 | sii8620_setbits(ctx, REG_M3_P0CTRL, | |
1203 | BIT_M3_P0CTRL_MHL3_P0_PORT_EN | |
1204 | | BIT_M3_P0CTRL_MHL3_P0_UNLIMIT_EN, p0_ctrl); | |
1205 | sii8620_setbits(ctx, REG_M3_POSTM, MSK_M3_POSTM_RRP_DECODE, | |
1206 | clk_spec[i].rrp_decode); | |
1207 | sii8620_write_seq_static(ctx, | |
1208 | REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE | |
1209 | | BIT_M3_CTRL_H2M_SWRST, | |
1210 | REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE | |
1211 | ); | |
1212 | sii8620_mt_write_stat(ctx, MHL_XDS_REG(AVLINK_MODE_CONTROL), | |
1213 | clk_spec[i].link_rate); | |
1214 | } | |
ce6e153f | 1215 | |
bf1722ca | 1216 | sii8620_set_infoframes(ctx); |
ce6e153f AH |
1217 | } |
1218 | ||
1219 | static void sii8620_start_video(struct sii8620 *ctx) | |
1220 | { | |
bb4954c7 | 1221 | if (!sii8620_is_mhl3(ctx)) |
ce6e153f AH |
1222 | sii8620_stop_video(ctx); |
1223 | ||
1224 | switch (ctx->sink_type) { | |
1225 | case SINK_HDMI: | |
1226 | sii8620_start_hdmi(ctx); | |
1227 | break; | |
1228 | case SINK_DVI: | |
1229 | default: | |
1230 | break; | |
1231 | } | |
1232 | } | |
1233 | ||
1234 | static void sii8620_disable_hpd(struct sii8620 *ctx) | |
1235 | { | |
1236 | sii8620_setbits(ctx, REG_EDID_CTRL, BIT_EDID_CTRL_EDID_PRIME_VALID, 0); | |
1237 | sii8620_write_seq_static(ctx, | |
1238 | REG_HPD_CTRL, BIT_HPD_CTRL_HPD_OUT_OVR_EN, | |
1239 | REG_INTR8_MASK, 0 | |
1240 | ); | |
1241 | } | |
1242 | ||
1243 | static void sii8620_enable_hpd(struct sii8620 *ctx) | |
1244 | { | |
1245 | sii8620_setbits(ctx, REG_TMDS_CSTAT_P3, | |
1246 | BIT_TMDS_CSTAT_P3_SCDT_CLR_AVI_DIS | |
1247 | | BIT_TMDS_CSTAT_P3_CLR_AVI, ~0); | |
1248 | sii8620_write_seq_static(ctx, | |
1249 | REG_HPD_CTRL, BIT_HPD_CTRL_HPD_OUT_OVR_EN | |
1250 | | BIT_HPD_CTRL_HPD_HIGH, | |
1251 | ); | |
1252 | } | |
1253 | ||
ce6e153f AH |
1254 | static void sii8620_mhl_discover(struct sii8620 *ctx) |
1255 | { | |
1256 | sii8620_write_seq_static(ctx, | |
1257 | REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT | |
1258 | | BIT_DISC_CTRL9_DISC_PULSE_PROCEED, | |
1259 | REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_5K, VAL_PUP_20K), | |
1260 | REG_CBUS_DISC_INTR0_MASK, BIT_MHL3_EST_INT | |
1261 | | BIT_MHL_EST_INT | |
1262 | | BIT_NOT_MHL_EST_INT | |
1263 | | BIT_CBUS_MHL3_DISCON_INT | |
1264 | | BIT_CBUS_MHL12_DISCON_INT | |
1265 | | BIT_RGND_READY_INT, | |
1266 | REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X | |
1267 | | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL | |
1268 | | BIT_MHL_PLL_CTL0_ZONE_MASK_OE, | |
1269 | REG_MHL_DP_CTL0, BIT_MHL_DP_CTL0_DP_OE | |
1270 | | BIT_MHL_DP_CTL0_TX_OE_OVR, | |
1271 | REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE, | |
1272 | REG_MHL_DP_CTL1, 0xA2, | |
1273 | REG_MHL_DP_CTL2, 0x03, | |
1274 | REG_MHL_DP_CTL3, 0x35, | |
1275 | REG_MHL_DP_CTL5, 0x02, | |
1276 | REG_MHL_DP_CTL6, 0x02, | |
1277 | REG_MHL_DP_CTL7, 0x03, | |
1278 | REG_COC_CTLC, 0xFF, | |
1279 | REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | |
1280 | | BIT_DPD_OSC_EN | BIT_DPD_PWRON_HSIC, | |
1281 | REG_COC_INTR_MASK, BIT_COC_PLL_LOCK_STATUS_CHANGE | |
1282 | | BIT_COC_CALIBRATION_DONE, | |
1283 | REG_CBUS_INT_1_MASK, BIT_CBUS_MSC_ABORT_RCVD | |
1284 | | BIT_CBUS_CMD_ABORT, | |
1285 | REG_CBUS_INT_0_MASK, BIT_CBUS_MSC_MT_DONE | |
1286 | | BIT_CBUS_HPD_CHG | |
1287 | | BIT_CBUS_MSC_MR_WRITE_STAT | |
1288 | | BIT_CBUS_MSC_MR_MSC_MSG | |
1289 | | BIT_CBUS_MSC_MR_WRITE_BURST | |
1290 | | BIT_CBUS_MSC_MR_SET_INT | |
1291 | | BIT_CBUS_MSC_MT_DONE_NACK | |
1292 | ); | |
1293 | } | |
1294 | ||
1295 | static void sii8620_peer_specific_init(struct sii8620 *ctx) | |
1296 | { | |
bb4954c7 | 1297 | if (sii8620_is_mhl3(ctx)) |
ce6e153f AH |
1298 | sii8620_write_seq_static(ctx, |
1299 | REG_SYS_CTRL1, BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD, | |
1300 | REG_EMSCINTRMASK1, | |
1301 | BIT_EMSCINTR1_EMSC_TRAINING_COMMA_ERR | |
1302 | ); | |
1303 | else | |
1304 | sii8620_write_seq_static(ctx, | |
1305 | REG_HDCP2X_INTR0_MASK, 0x00, | |
1306 | REG_EMSCINTRMASK1, 0x00, | |
1307 | REG_HDCP2X_INTR0, 0xFF, | |
1308 | REG_INTR1, 0xFF, | |
1309 | REG_SYS_CTRL1, BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD | |
1310 | | BIT_SYS_CTRL1_TX_CTRL_HDMI | |
1311 | ); | |
1312 | } | |
1313 | ||
1314 | #define SII8620_MHL_VERSION 0x32 | |
1315 | #define SII8620_SCRATCHPAD_SIZE 16 | |
1316 | #define SII8620_INT_STAT_SIZE 0x33 | |
1317 | ||
1318 | static void sii8620_set_dev_cap(struct sii8620 *ctx) | |
1319 | { | |
1320 | static const u8 devcap[MHL_DCAP_SIZE] = { | |
1321 | [MHL_DCAP_MHL_VERSION] = SII8620_MHL_VERSION, | |
1322 | [MHL_DCAP_CAT] = MHL_DCAP_CAT_SOURCE | MHL_DCAP_CAT_POWER, | |
1323 | [MHL_DCAP_ADOPTER_ID_H] = 0x01, | |
1324 | [MHL_DCAP_ADOPTER_ID_L] = 0x41, | |
1325 | [MHL_DCAP_VID_LINK_MODE] = MHL_DCAP_VID_LINK_RGB444 | |
1326 | | MHL_DCAP_VID_LINK_PPIXEL | |
1327 | | MHL_DCAP_VID_LINK_16BPP, | |
1328 | [MHL_DCAP_AUD_LINK_MODE] = MHL_DCAP_AUD_LINK_2CH, | |
1329 | [MHL_DCAP_VIDEO_TYPE] = MHL_DCAP_VT_GRAPHICS, | |
1330 | [MHL_DCAP_LOG_DEV_MAP] = MHL_DCAP_LD_GUI, | |
1331 | [MHL_DCAP_BANDWIDTH] = 0x0f, | |
1332 | [MHL_DCAP_FEATURE_FLAG] = MHL_DCAP_FEATURE_RCP_SUPPORT | |
1333 | | MHL_DCAP_FEATURE_RAP_SUPPORT | |
1334 | | MHL_DCAP_FEATURE_SP_SUPPORT, | |
1335 | [MHL_DCAP_SCRATCHPAD_SIZE] = SII8620_SCRATCHPAD_SIZE, | |
1336 | [MHL_DCAP_INT_STAT_SIZE] = SII8620_INT_STAT_SIZE, | |
1337 | }; | |
1338 | static const u8 xdcap[MHL_XDC_SIZE] = { | |
1339 | [MHL_XDC_ECBUS_SPEEDS] = MHL_XDC_ECBUS_S_075 | |
1340 | | MHL_XDC_ECBUS_S_8BIT, | |
1341 | [MHL_XDC_TMDS_SPEEDS] = MHL_XDC_TMDS_150 | |
1342 | | MHL_XDC_TMDS_300 | MHL_XDC_TMDS_600, | |
1343 | [MHL_XDC_ECBUS_ROLES] = MHL_XDC_DEV_HOST, | |
1344 | [MHL_XDC_LOG_DEV_MAPX] = MHL_XDC_LD_PHONE, | |
1345 | }; | |
1346 | ||
1347 | sii8620_write_buf(ctx, REG_MHL_DEVCAP_0, devcap, ARRAY_SIZE(devcap)); | |
1348 | sii8620_write_buf(ctx, REG_MHL_EXTDEVCAP_0, xdcap, ARRAY_SIZE(xdcap)); | |
1349 | } | |
1350 | ||
1351 | static void sii8620_mhl_init(struct sii8620 *ctx) | |
1352 | { | |
1353 | sii8620_write_seq_static(ctx, | |
1354 | REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), | |
1355 | REG_CBUS_MSC_COMPAT_CTRL, | |
1356 | BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN, | |
1357 | ); | |
1358 | ||
1359 | sii8620_peer_specific_init(ctx); | |
1360 | ||
1361 | sii8620_disable_hpd(ctx); | |
1362 | ||
1363 | sii8620_write_seq_static(ctx, | |
1364 | REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO, | |
1365 | REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT | |
1366 | | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, | |
1367 | REG_TMDS0_CCTRL1, 0x90, | |
1368 | REG_TMDS_CLK_EN, 0x01, | |
1369 | REG_TMDS_CH_EN, 0x11, | |
1370 | REG_BGR_BIAS, 0x87, | |
1371 | REG_ALICE0_ZONE_CTRL, 0xE8, | |
1372 | REG_ALICE0_MODE_CTRL, 0x04, | |
1373 | ); | |
1374 | sii8620_setbits(ctx, REG_LM_DDC, BIT_LM_DDC_SW_TPI_EN_DISABLED, 0); | |
1375 | sii8620_write_seq_static(ctx, | |
1376 | REG_TPI_HW_OPT3, 0x76, | |
1377 | REG_TMDS_CCTRL, BIT_TMDS_CCTRL_TMDS_OE, | |
1378 | REG_TPI_DTD_B2, 79, | |
1379 | ); | |
1380 | sii8620_set_dev_cap(ctx); | |
1381 | sii8620_write_seq_static(ctx, | |
1382 | REG_MDT_XMIT_TIMEOUT, 100, | |
1383 | REG_MDT_XMIT_CTRL, 0x03, | |
1384 | REG_MDT_XFIFO_STAT, 0x00, | |
1385 | REG_MDT_RCV_TIMEOUT, 100, | |
1386 | REG_CBUS_LINK_CTRL_8, 0x1D, | |
1387 | ); | |
1388 | ||
1389 | sii8620_start_gen2_write_burst(ctx); | |
1390 | sii8620_write_seq_static(ctx, | |
1391 | REG_BIST_CTRL, 0x00, | |
1392 | REG_COC_CTL1, 0x10, | |
1393 | REG_COC_CTL2, 0x18, | |
1394 | REG_COC_CTLF, 0x07, | |
1395 | REG_COC_CTL11, 0xF8, | |
1396 | REG_COC_CTL17, 0x61, | |
1397 | REG_COC_CTL18, 0x46, | |
1398 | REG_COC_CTL19, 0x15, | |
1399 | REG_COC_CTL1A, 0x01, | |
1400 | REG_MHL_COC_CTL3, BIT_MHL_COC_CTL3_COC_AECHO_EN, | |
1401 | REG_MHL_COC_CTL4, 0x2D, | |
1402 | REG_MHL_COC_CTL5, 0xF9, | |
1403 | REG_MSC_HEARTBEAT_CTRL, 0x27, | |
1404 | ); | |
1405 | sii8620_disable_gen2_write_burst(ctx); | |
1406 | ||
9fc6ade8 | 1407 | sii8620_mt_write_stat(ctx, MHL_DST_REG(VERSION), SII8620_MHL_VERSION); |
ce6e153f AH |
1408 | sii8620_mt_write_stat(ctx, MHL_DST_REG(CONNECTED_RDY), |
1409 | MHL_DST_CONN_DCAP_RDY | MHL_DST_CONN_XDEVCAPP_SUPP | |
1410 | | MHL_DST_CONN_POW_STAT); | |
1411 | sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE), MHL_INT_RC_DCAP_CHG); | |
1412 | } | |
1413 | ||
2c8fb853 AH |
1414 | static void sii8620_emsc_enable(struct sii8620 *ctx) |
1415 | { | |
1416 | u8 reg; | |
1417 | ||
1418 | sii8620_setbits(ctx, REG_GENCTL, BIT_GENCTL_EMSC_EN | |
1419 | | BIT_GENCTL_CLR_EMSC_RFIFO | |
1420 | | BIT_GENCTL_CLR_EMSC_XFIFO, ~0); | |
1421 | sii8620_setbits(ctx, REG_GENCTL, BIT_GENCTL_CLR_EMSC_RFIFO | |
1422 | | BIT_GENCTL_CLR_EMSC_XFIFO, 0); | |
1423 | sii8620_setbits(ctx, REG_COMMECNT, BIT_COMMECNT_I2C_TO_EMSC_EN, ~0); | |
1424 | reg = sii8620_readb(ctx, REG_EMSCINTR); | |
1425 | sii8620_write(ctx, REG_EMSCINTR, reg); | |
1426 | sii8620_write(ctx, REG_EMSCINTRMASK, BIT_EMSCINTR_SPI_DVLD); | |
1427 | } | |
1428 | ||
1429 | static int sii8620_wait_for_fsm_state(struct sii8620 *ctx, u8 state) | |
1430 | { | |
1431 | int i; | |
1432 | ||
1433 | for (i = 0; i < 10; ++i) { | |
1434 | u8 s = sii8620_readb(ctx, REG_COC_STAT_0); | |
1435 | ||
1436 | if ((s & MSK_COC_STAT_0_FSM_STATE) == state) | |
1437 | return 0; | |
1438 | if (!(s & BIT_COC_STAT_0_PLL_LOCKED)) | |
1439 | return -EBUSY; | |
1440 | usleep_range(4000, 6000); | |
1441 | } | |
1442 | return -ETIMEDOUT; | |
1443 | } | |
1444 | ||
ce6e153f AH |
1445 | static void sii8620_set_mode(struct sii8620 *ctx, enum sii8620_mode mode) |
1446 | { | |
2c8fb853 AH |
1447 | int ret; |
1448 | ||
ce6e153f AH |
1449 | if (ctx->mode == mode) |
1450 | return; | |
1451 | ||
ce6e153f AH |
1452 | switch (mode) { |
1453 | case CM_MHL1: | |
1454 | sii8620_write_seq_static(ctx, | |
1455 | REG_CBUS_MSC_COMPAT_CTRL, 0x02, | |
1456 | REG_M3_CTRL, VAL_M3_CTRL_MHL1_2_VALUE, | |
1457 | REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | |
1458 | | BIT_DPD_OSC_EN, | |
1459 | REG_COC_INTR_MASK, 0 | |
1460 | ); | |
2c8fb853 | 1461 | ctx->mode = mode; |
ce6e153f AH |
1462 | break; |
1463 | case CM_MHL3: | |
dd123129 | 1464 | sii8620_write(ctx, REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE); |
2c8fb853 AH |
1465 | ctx->mode = mode; |
1466 | return; | |
1467 | case CM_ECBUS_S: | |
1468 | sii8620_emsc_enable(ctx); | |
1469 | sii8620_write_seq_static(ctx, | |
1470 | REG_TTXSPINUMS, 4, | |
1471 | REG_TRXSPINUMS, 4, | |
1472 | REG_TTXHSICNUMS, 0x14, | |
1473 | REG_TRXHSICNUMS, 0x14, | |
1474 | REG_TTXTOTNUMS, 0x18, | |
1475 | REG_TRXTOTNUMS, 0x18, | |
1476 | REG_PWD_SRST, BIT_PWD_SRST_COC_DOC_RST | |
1477 | | BIT_PWD_SRST_CBUS_RST_SW_EN, | |
1478 | REG_MHL_COC_CTL1, 0xbd, | |
1479 | REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST_SW_EN, | |
1480 | REG_COC_CTLB, 0x01, | |
1481 | REG_COC_CTL0, 0x5c, | |
1482 | REG_COC_CTL14, 0x03, | |
1483 | REG_COC_CTL15, 0x80, | |
1484 | REG_MHL_DP_CTL6, BIT_MHL_DP_CTL6_DP_TAP1_SGN | |
1485 | | BIT_MHL_DP_CTL6_DP_TAP1_EN | |
1486 | | BIT_MHL_DP_CTL6_DT_PREDRV_FEEDCAP_EN, | |
1487 | REG_MHL_DP_CTL8, 0x03 | |
1488 | ); | |
1489 | ret = sii8620_wait_for_fsm_state(ctx, 0x03); | |
1490 | sii8620_write_seq_static(ctx, | |
1491 | REG_COC_CTL14, 0x00, | |
1492 | REG_COC_CTL15, 0x80 | |
1493 | ); | |
1494 | if (!ret) | |
1495 | sii8620_write(ctx, REG_CBUS3_CNVT, 0x85); | |
1496 | else | |
1497 | sii8620_disconnect(ctx); | |
dd123129 | 1498 | return; |
ce6e153f | 1499 | case CM_DISCONNECTED: |
2c8fb853 | 1500 | ctx->mode = mode; |
ce6e153f AH |
1501 | break; |
1502 | default: | |
1503 | dev_err(ctx->dev, "%s mode %d not supported\n", __func__, mode); | |
1504 | break; | |
3a81e960 | 1505 | } |
ce6e153f AH |
1506 | |
1507 | sii8620_set_auto_zone(ctx); | |
1508 | ||
1509 | if (mode != CM_MHL1) | |
1510 | return; | |
1511 | ||
1512 | sii8620_write_seq_static(ctx, | |
1513 | REG_MHL_DP_CTL0, 0xBC, | |
1514 | REG_MHL_DP_CTL1, 0xBB, | |
1515 | REG_MHL_DP_CTL3, 0x48, | |
1516 | REG_MHL_DP_CTL5, 0x39, | |
1517 | REG_MHL_DP_CTL2, 0x2A, | |
1518 | REG_MHL_DP_CTL6, 0x2A, | |
1519 | REG_MHL_DP_CTL7, 0x08 | |
1520 | ); | |
1521 | } | |
1522 | ||
1523 | static void sii8620_disconnect(struct sii8620 *ctx) | |
1524 | { | |
1525 | sii8620_disable_gen2_write_burst(ctx); | |
1526 | sii8620_stop_video(ctx); | |
003f9929 | 1527 | msleep(100); |
ce6e153f AH |
1528 | sii8620_cbus_reset(ctx); |
1529 | sii8620_set_mode(ctx, CM_DISCONNECTED); | |
1530 | sii8620_write_seq_static(ctx, | |
003f9929 AH |
1531 | REG_TX_ZONE_CTL1, 0, |
1532 | REG_MHL_PLL_CTL0, 0x07, | |
ce6e153f AH |
1533 | REG_COC_CTL0, 0x40, |
1534 | REG_CBUS3_CNVT, 0x84, | |
1535 | REG_COC_CTL14, 0x00, | |
1536 | REG_COC_CTL0, 0x40, | |
1537 | REG_HRXCTRL3, 0x07, | |
1538 | REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X | |
1539 | | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL | |
1540 | | BIT_MHL_PLL_CTL0_ZONE_MASK_OE, | |
1541 | REG_MHL_DP_CTL0, BIT_MHL_DP_CTL0_DP_OE | |
1542 | | BIT_MHL_DP_CTL0_TX_OE_OVR, | |
1543 | REG_MHL_DP_CTL1, 0xBB, | |
1544 | REG_MHL_DP_CTL3, 0x48, | |
1545 | REG_MHL_DP_CTL5, 0x3F, | |
1546 | REG_MHL_DP_CTL2, 0x2F, | |
1547 | REG_MHL_DP_CTL6, 0x2A, | |
1548 | REG_MHL_DP_CTL7, 0x03 | |
1549 | ); | |
1550 | sii8620_disable_hpd(ctx); | |
1551 | sii8620_write_seq_static(ctx, | |
1552 | REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE, | |
1553 | REG_MHL_COC_CTL1, 0x07, | |
1554 | REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), | |
1555 | REG_DISC_CTRL8, 0x00, | |
1556 | REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT | |
1557 | | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, | |
1558 | REG_INT_CTRL, 0x00, | |
1559 | REG_MSC_HEARTBEAT_CTRL, 0x27, | |
1560 | REG_DISC_CTRL1, 0x25, | |
1561 | REG_CBUS_DISC_INTR0, (u8)~BIT_RGND_READY_INT, | |
1562 | REG_CBUS_DISC_INTR0_MASK, BIT_RGND_READY_INT, | |
1563 | REG_MDT_INT_1, 0xff, | |
1564 | REG_MDT_INT_1_MASK, 0x00, | |
1565 | REG_MDT_INT_0, 0xff, | |
1566 | REG_MDT_INT_0_MASK, 0x00, | |
1567 | REG_COC_INTR, 0xff, | |
1568 | REG_COC_INTR_MASK, 0x00, | |
1569 | REG_TRXINTH, 0xff, | |
1570 | REG_TRXINTMH, 0x00, | |
1571 | REG_CBUS_INT_0, 0xff, | |
1572 | REG_CBUS_INT_0_MASK, 0x00, | |
1573 | REG_CBUS_INT_1, 0xff, | |
1574 | REG_CBUS_INT_1_MASK, 0x00, | |
1575 | REG_EMSCINTR, 0xff, | |
1576 | REG_EMSCINTRMASK, 0x00, | |
1577 | REG_EMSCINTR1, 0xff, | |
1578 | REG_EMSCINTRMASK1, 0x00, | |
1579 | REG_INTR8, 0xff, | |
1580 | REG_INTR8_MASK, 0x00, | |
1581 | REG_TPI_INTR_ST0, 0xff, | |
1582 | REG_TPI_INTR_EN, 0x00, | |
1583 | REG_HDCP2X_INTR0, 0xff, | |
1584 | REG_HDCP2X_INTR0_MASK, 0x00, | |
1585 | REG_INTR9, 0xff, | |
1586 | REG_INTR9_MASK, 0x00, | |
1587 | REG_INTR3, 0xff, | |
1588 | REG_INTR3_MASK, 0x00, | |
1589 | REG_INTR5, 0xff, | |
1590 | REG_INTR5_MASK, 0x00, | |
1591 | REG_INTR2, 0xff, | |
1592 | REG_INTR2_MASK, 0x00, | |
1593 | ); | |
1594 | memset(ctx->stat, 0, sizeof(ctx->stat)); | |
1595 | memset(ctx->xstat, 0, sizeof(ctx->xstat)); | |
1596 | memset(ctx->devcap, 0, sizeof(ctx->devcap)); | |
1597 | memset(ctx->xdevcap, 0, sizeof(ctx->xdevcap)); | |
1598 | ctx->cbus_status = 0; | |
1599 | ctx->sink_type = SINK_NONE; | |
1600 | kfree(ctx->edid); | |
1601 | ctx->edid = NULL; | |
1602 | sii8620_mt_cleanup(ctx); | |
1603 | } | |
1604 | ||
1605 | static void sii8620_mhl_disconnected(struct sii8620 *ctx) | |
1606 | { | |
1607 | sii8620_write_seq_static(ctx, | |
1608 | REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), | |
1609 | REG_CBUS_MSC_COMPAT_CTRL, | |
1610 | BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN | |
1611 | ); | |
1612 | sii8620_disconnect(ctx); | |
1613 | } | |
1614 | ||
1615 | static void sii8620_irq_disc(struct sii8620 *ctx) | |
1616 | { | |
1617 | u8 stat = sii8620_readb(ctx, REG_CBUS_DISC_INTR0); | |
1618 | ||
1619 | if (stat & VAL_CBUS_MHL_DISCON) | |
1620 | sii8620_mhl_disconnected(ctx); | |
1621 | ||
1622 | if (stat & BIT_RGND_READY_INT) { | |
1623 | u8 stat2 = sii8620_readb(ctx, REG_DISC_STAT2); | |
1624 | ||
1625 | if ((stat2 & MSK_DISC_STAT2_RGND) == VAL_RGND_1K) { | |
1626 | sii8620_mhl_discover(ctx); | |
1627 | } else { | |
1628 | sii8620_write_seq_static(ctx, | |
1629 | REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT | |
1630 | | BIT_DISC_CTRL9_NOMHL_EST | |
1631 | | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, | |
1632 | REG_CBUS_DISC_INTR0_MASK, BIT_RGND_READY_INT | |
1633 | | BIT_CBUS_MHL3_DISCON_INT | |
1634 | | BIT_CBUS_MHL12_DISCON_INT | |
1635 | | BIT_NOT_MHL_EST_INT | |
1636 | ); | |
1637 | } | |
1638 | } | |
1639 | if (stat & BIT_MHL_EST_INT) | |
1640 | sii8620_mhl_init(ctx); | |
1641 | ||
1642 | sii8620_write(ctx, REG_CBUS_DISC_INTR0, stat); | |
1643 | } | |
1644 | ||
581a9237 AH |
1645 | static void sii8620_read_burst(struct sii8620 *ctx) |
1646 | { | |
1647 | u8 buf[17]; | |
1648 | ||
1649 | sii8620_read_buf(ctx, REG_MDT_RCV_READ_PORT, buf, ARRAY_SIZE(buf)); | |
1650 | sii8620_write(ctx, REG_MDT_RCV_CTRL, BIT_MDT_RCV_CTRL_MDT_RCV_EN | | |
1651 | BIT_MDT_RCV_CTRL_MDT_DELAY_RCV_EN | | |
1652 | BIT_MDT_RCV_CTRL_MDT_RFIFO_CLR_CUR); | |
1653 | sii8620_readb(ctx, REG_MDT_RFIFO_STAT); | |
1654 | } | |
1655 | ||
ce6e153f AH |
1656 | static void sii8620_irq_g2wb(struct sii8620 *ctx) |
1657 | { | |
1658 | u8 stat = sii8620_readb(ctx, REG_MDT_INT_0); | |
1659 | ||
1660 | if (stat & BIT_MDT_IDLE_AFTER_HAWB_DISABLE) | |
581a9237 AH |
1661 | if (sii8620_is_mhl3(ctx)) |
1662 | sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE), | |
1663 | MHL_INT_RC_FEAT_COMPLETE); | |
1664 | ||
1665 | if (stat & BIT_MDT_RFIFO_DATA_RDY) | |
1666 | sii8620_read_burst(ctx); | |
1667 | ||
1668 | if (stat & BIT_MDT_XFIFO_EMPTY) | |
1669 | sii8620_write(ctx, REG_MDT_XMIT_CTRL, 0); | |
ce6e153f AH |
1670 | |
1671 | sii8620_write(ctx, REG_MDT_INT_0, stat); | |
1672 | } | |
1673 | ||
9fc6ade8 | 1674 | static void sii8620_status_dcap_ready(struct sii8620 *ctx) |
ce6e153f | 1675 | { |
9fc6ade8 AH |
1676 | enum sii8620_mode mode; |
1677 | ||
1678 | mode = ctx->stat[MHL_DST_VERSION] >= 0x30 ? CM_MHL3 : CM_MHL1; | |
1679 | if (mode > ctx->mode) | |
1680 | sii8620_set_mode(ctx, mode); | |
1681 | sii8620_peer_specific_init(ctx); | |
1682 | sii8620_write(ctx, REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE | |
1683 | | BIT_INTR9_EDID_DONE | BIT_INTR9_EDID_ERROR); | |
ce6e153f AH |
1684 | } |
1685 | ||
1686 | static void sii8620_status_changed_path(struct sii8620 *ctx) | |
1687 | { | |
1688 | if (ctx->stat[MHL_DST_LINK_MODE] & MHL_DST_LM_PATH_ENABLED) { | |
1689 | sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), | |
1690 | MHL_DST_LM_CLK_MODE_NORMAL | |
1691 | | MHL_DST_LM_PATH_ENABLED); | |
e3a65487 AH |
1692 | if (!sii8620_is_mhl3(ctx)) |
1693 | sii8620_mt_read_devcap(ctx, false); | |
9a466cd1 | 1694 | sii8620_mt_set_cont(ctx, sii8620_sink_detected); |
ce6e153f AH |
1695 | } else { |
1696 | sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), | |
1697 | MHL_DST_LM_CLK_MODE_NORMAL); | |
1698 | } | |
1699 | } | |
1700 | ||
1701 | static void sii8620_msc_mr_write_stat(struct sii8620 *ctx) | |
1702 | { | |
1703 | u8 st[MHL_DST_SIZE], xst[MHL_XDS_SIZE]; | |
1704 | ||
1705 | sii8620_read_buf(ctx, REG_MHL_STAT_0, st, MHL_DST_SIZE); | |
1706 | sii8620_read_buf(ctx, REG_MHL_EXTSTAT_0, xst, MHL_XDS_SIZE); | |
1707 | ||
1708 | sii8620_update_array(ctx->stat, st, MHL_DST_SIZE); | |
1709 | sii8620_update_array(ctx->xstat, xst, MHL_XDS_SIZE); | |
1710 | ||
9fc6ade8 AH |
1711 | if (ctx->stat[MHL_DST_CONNECTED_RDY] & MHL_DST_CONN_DCAP_RDY) |
1712 | sii8620_status_dcap_ready(ctx); | |
ce6e153f AH |
1713 | |
1714 | if (st[MHL_DST_LINK_MODE] & MHL_DST_LM_PATH_ENABLED) | |
1715 | sii8620_status_changed_path(ctx); | |
1716 | } | |
1717 | ||
2c8fb853 AH |
1718 | static void sii8620_ecbus_up(struct sii8620 *ctx, int ret) |
1719 | { | |
1720 | if (ret < 0) | |
1721 | return; | |
1722 | ||
1723 | sii8620_set_mode(ctx, CM_ECBUS_S); | |
1724 | } | |
1725 | ||
1726 | static void sii8620_got_ecbus_speed(struct sii8620 *ctx, int ret) | |
1727 | { | |
1728 | if (ret < 0) | |
1729 | return; | |
1730 | ||
1731 | sii8620_mt_write_stat(ctx, MHL_XDS_REG(CURR_ECBUS_MODE), | |
1732 | MHL_XDS_ECBUS_S | MHL_XDS_SLOT_MODE_8BIT); | |
1733 | sii8620_mt_rap(ctx, MHL_RAP_CBUS_MODE_UP); | |
1734 | sii8620_mt_set_cont(ctx, sii8620_ecbus_up); | |
1735 | } | |
1736 | ||
269ed8ee AH |
1737 | static void sii8620_mhl_burst_emsc_support_set(struct mhl_burst_emsc_support *d, |
1738 | enum mhl_burst_id id) | |
1739 | { | |
1740 | sii8620_mhl_burst_hdr_set(&d->hdr, MHL_BURST_ID_EMSC_SUPPORT); | |
1741 | d->num_entries = 1; | |
1742 | d->burst_id[0] = cpu_to_be16(id); | |
1743 | } | |
1744 | ||
1745 | static void sii8620_send_features(struct sii8620 *ctx) | |
1746 | { | |
1747 | u8 buf[16]; | |
1748 | ||
1749 | sii8620_write(ctx, REG_MDT_XMIT_CTRL, BIT_MDT_XMIT_CTRL_EN | |
1750 | | BIT_MDT_XMIT_CTRL_FIXED_BURST_LEN); | |
1751 | sii8620_mhl_burst_emsc_support_set((void *)buf, | |
1752 | MHL_BURST_ID_HID_PAYLOAD); | |
1753 | sii8620_write_buf(ctx, REG_MDT_XMIT_WRITE_PORT, buf, ARRAY_SIZE(buf)); | |
1754 | } | |
1755 | ||
ce6e153f AH |
1756 | static void sii8620_msc_mr_set_int(struct sii8620 *ctx) |
1757 | { | |
1758 | u8 ints[MHL_INT_SIZE]; | |
1759 | ||
1760 | sii8620_read_buf(ctx, REG_MHL_INT_0, ints, MHL_INT_SIZE); | |
1761 | sii8620_write_buf(ctx, REG_MHL_INT_0, ints, MHL_INT_SIZE); | |
2c8fb853 AH |
1762 | |
1763 | if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_DCAP_CHG) { | |
1764 | switch (ctx->mode) { | |
1765 | case CM_MHL3: | |
1766 | sii8620_mt_read_xdevcap_reg(ctx, MHL_XDC_ECBUS_SPEEDS); | |
1767 | sii8620_mt_set_cont(ctx, sii8620_got_ecbus_speed); | |
1768 | break; | |
1769 | case CM_ECBUS_S: | |
1770 | sii8620_mt_read_devcap(ctx, true); | |
1771 | break; | |
1772 | default: | |
1773 | break; | |
1774 | } | |
1775 | } | |
269ed8ee AH |
1776 | if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_FEAT_REQ) |
1777 | sii8620_send_features(ctx); | |
1778 | if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_FEAT_COMPLETE) | |
1779 | sii8620_edid_read(ctx, 0); | |
ce6e153f AH |
1780 | } |
1781 | ||
1782 | static struct sii8620_mt_msg *sii8620_msc_msg_first(struct sii8620 *ctx) | |
1783 | { | |
1784 | struct device *dev = ctx->dev; | |
1785 | ||
1786 | if (list_empty(&ctx->mt_queue)) { | |
1787 | dev_err(dev, "unexpected MSC MT response\n"); | |
1788 | return NULL; | |
1789 | } | |
1790 | ||
1791 | return list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, node); | |
1792 | } | |
1793 | ||
1794 | static void sii8620_msc_mt_done(struct sii8620 *ctx) | |
1795 | { | |
1796 | struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx); | |
1797 | ||
1798 | if (!msg) | |
1799 | return; | |
1800 | ||
1801 | msg->ret = sii8620_readb(ctx, REG_MSC_MT_RCVD_DATA0); | |
1802 | ctx->mt_state = MT_STATE_DONE; | |
1803 | } | |
1804 | ||
1805 | static void sii8620_msc_mr_msc_msg(struct sii8620 *ctx) | |
1806 | { | |
1807 | struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx); | |
1808 | u8 buf[2]; | |
1809 | ||
1810 | if (!msg) | |
1811 | return; | |
1812 | ||
1813 | sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2); | |
1814 | ||
1815 | switch (buf[0]) { | |
1816 | case MHL_MSC_MSG_RAPK: | |
1817 | msg->ret = buf[1]; | |
1818 | ctx->mt_state = MT_STATE_DONE; | |
1819 | break; | |
1820 | default: | |
1821 | dev_err(ctx->dev, "%s message type %d,%d not supported", | |
1822 | __func__, buf[0], buf[1]); | |
1823 | } | |
1824 | } | |
1825 | ||
1826 | static void sii8620_irq_msc(struct sii8620 *ctx) | |
1827 | { | |
1828 | u8 stat = sii8620_readb(ctx, REG_CBUS_INT_0); | |
1829 | ||
1830 | if (stat & ~BIT_CBUS_HPD_CHG) | |
1831 | sii8620_write(ctx, REG_CBUS_INT_0, stat & ~BIT_CBUS_HPD_CHG); | |
1832 | ||
1833 | if (stat & BIT_CBUS_HPD_CHG) { | |
1834 | u8 cbus_stat = sii8620_readb(ctx, REG_CBUS_STATUS); | |
1835 | ||
1836 | if ((cbus_stat ^ ctx->cbus_status) & BIT_CBUS_STATUS_CBUS_HPD) { | |
1837 | sii8620_write(ctx, REG_CBUS_INT_0, BIT_CBUS_HPD_CHG); | |
1838 | } else { | |
1839 | stat ^= BIT_CBUS_STATUS_CBUS_HPD; | |
1840 | cbus_stat ^= BIT_CBUS_STATUS_CBUS_HPD; | |
1841 | } | |
1842 | ctx->cbus_status = cbus_stat; | |
1843 | } | |
1844 | ||
1845 | if (stat & BIT_CBUS_MSC_MR_WRITE_STAT) | |
1846 | sii8620_msc_mr_write_stat(ctx); | |
1847 | ||
1848 | if (stat & BIT_CBUS_MSC_MR_SET_INT) | |
1849 | sii8620_msc_mr_set_int(ctx); | |
1850 | ||
1851 | if (stat & BIT_CBUS_MSC_MT_DONE) | |
1852 | sii8620_msc_mt_done(ctx); | |
1853 | ||
1854 | if (stat & BIT_CBUS_MSC_MR_MSC_MSG) | |
1855 | sii8620_msc_mr_msc_msg(ctx); | |
1856 | } | |
1857 | ||
1858 | static void sii8620_irq_coc(struct sii8620 *ctx) | |
1859 | { | |
1860 | u8 stat = sii8620_readb(ctx, REG_COC_INTR); | |
1861 | ||
e19e9c69 AH |
1862 | if (stat & BIT_COC_CALIBRATION_DONE) { |
1863 | u8 cstat = sii8620_readb(ctx, REG_COC_STAT_0); | |
1864 | ||
1865 | cstat &= BIT_COC_STAT_0_PLL_LOCKED | MSK_COC_STAT_0_FSM_STATE; | |
1866 | if (cstat == (BIT_COC_STAT_0_PLL_LOCKED | 0x02)) { | |
1867 | sii8620_write_seq_static(ctx, | |
1868 | REG_COC_CTLB, 0, | |
1869 | REG_TRXINTMH, BIT_TDM_INTR_SYNC_DATA | |
1870 | | BIT_TDM_INTR_SYNC_WAIT | |
1871 | ); | |
1872 | } | |
1873 | } | |
1874 | ||
ce6e153f AH |
1875 | sii8620_write(ctx, REG_COC_INTR, stat); |
1876 | } | |
1877 | ||
1878 | static void sii8620_irq_merr(struct sii8620 *ctx) | |
1879 | { | |
1880 | u8 stat = sii8620_readb(ctx, REG_CBUS_INT_1); | |
1881 | ||
1882 | sii8620_write(ctx, REG_CBUS_INT_1, stat); | |
1883 | } | |
1884 | ||
1885 | static void sii8620_irq_edid(struct sii8620 *ctx) | |
1886 | { | |
1887 | u8 stat = sii8620_readb(ctx, REG_INTR9); | |
1888 | ||
1889 | sii8620_write(ctx, REG_INTR9, stat); | |
1890 | ||
1891 | if (stat & BIT_INTR9_DEVCAP_DONE) | |
1892 | ctx->mt_state = MT_STATE_DONE; | |
1893 | } | |
1894 | ||
1895 | static void sii8620_scdt_high(struct sii8620 *ctx) | |
1896 | { | |
1897 | sii8620_write_seq_static(ctx, | |
1898 | REG_INTR8_MASK, BIT_CEA_NEW_AVI | BIT_CEA_NEW_VSI, | |
1899 | REG_TPI_SC, BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI, | |
1900 | ); | |
1901 | } | |
1902 | ||
ce6e153f AH |
1903 | static void sii8620_irq_scdt(struct sii8620 *ctx) |
1904 | { | |
1905 | u8 stat = sii8620_readb(ctx, REG_INTR5); | |
1906 | ||
1907 | if (stat & BIT_INTR_SCDT_CHANGE) { | |
1908 | u8 cstat = sii8620_readb(ctx, REG_TMDS_CSTAT_P3); | |
1909 | ||
1910 | if (cstat & BIT_TMDS_CSTAT_P3_SCDT) | |
1911 | sii8620_scdt_high(ctx); | |
ce6e153f AH |
1912 | } |
1913 | ||
1914 | sii8620_write(ctx, REG_INTR5, stat); | |
1915 | } | |
1916 | ||
1917 | static void sii8620_new_vsi(struct sii8620 *ctx) | |
1918 | { | |
1919 | u8 vsif[11]; | |
1920 | ||
1921 | sii8620_write(ctx, REG_RX_HDMI_CTRL2, | |
1922 | VAL_RX_HDMI_CTRL2_DEFVAL | | |
1923 | BIT_RX_HDMI_CTRL2_VSI_MON_SEL_VSI); | |
1924 | sii8620_read_buf(ctx, REG_RX_HDMI_MON_PKT_HEADER1, vsif, | |
1925 | ARRAY_SIZE(vsif)); | |
1926 | } | |
1927 | ||
1928 | static void sii8620_new_avi(struct sii8620 *ctx) | |
1929 | { | |
1930 | sii8620_write(ctx, REG_RX_HDMI_CTRL2, VAL_RX_HDMI_CTRL2_DEFVAL); | |
1931 | sii8620_read_buf(ctx, REG_RX_HDMI_MON_PKT_HEADER1, ctx->avif, | |
1932 | ARRAY_SIZE(ctx->avif)); | |
1933 | } | |
1934 | ||
1935 | static void sii8620_irq_infr(struct sii8620 *ctx) | |
1936 | { | |
1937 | u8 stat = sii8620_readb(ctx, REG_INTR8) | |
1938 | & (BIT_CEA_NEW_VSI | BIT_CEA_NEW_AVI); | |
1939 | ||
1940 | sii8620_write(ctx, REG_INTR8, stat); | |
1941 | ||
1942 | if (stat & BIT_CEA_NEW_VSI) | |
1943 | sii8620_new_vsi(ctx); | |
1944 | ||
1945 | if (stat & BIT_CEA_NEW_AVI) | |
1946 | sii8620_new_avi(ctx); | |
1947 | ||
1948 | if (stat & (BIT_CEA_NEW_VSI | BIT_CEA_NEW_AVI)) | |
1949 | sii8620_start_video(ctx); | |
1950 | } | |
1951 | ||
e3a65487 AH |
1952 | static void sii8620_got_xdevcap(struct sii8620 *ctx, int ret) |
1953 | { | |
1954 | if (ret < 0) | |
1955 | return; | |
1956 | ||
1957 | sii8620_mt_read_devcap(ctx, false); | |
1958 | } | |
1959 | ||
e19e9c69 AH |
1960 | static void sii8620_irq_tdm(struct sii8620 *ctx) |
1961 | { | |
1962 | u8 stat = sii8620_readb(ctx, REG_TRXINTH); | |
1963 | u8 tdm = sii8620_readb(ctx, REG_TRXSTA2); | |
1964 | ||
1965 | if ((tdm & MSK_TDM_SYNCHRONIZED) == VAL_TDM_SYNCHRONIZED) { | |
1966 | ctx->mode = CM_ECBUS_S; | |
1967 | ctx->burst.rx_ack = 0; | |
1968 | ctx->burst.r_size = SII8620_BURST_BUF_LEN; | |
1969 | sii8620_burst_tx_rbuf_info(ctx, SII8620_BURST_BUF_LEN); | |
1970 | sii8620_mt_read_devcap(ctx, true); | |
e3a65487 | 1971 | sii8620_mt_set_cont(ctx, sii8620_got_xdevcap); |
e19e9c69 AH |
1972 | } else { |
1973 | sii8620_write_seq_static(ctx, | |
1974 | REG_MHL_PLL_CTL2, 0, | |
1975 | REG_MHL_PLL_CTL2, BIT_MHL_PLL_CTL2_CLKDETECT_EN | |
1976 | ); | |
1977 | } | |
1978 | ||
1979 | sii8620_write(ctx, REG_TRXINTH, stat); | |
1980 | } | |
1981 | ||
1982 | static void sii8620_irq_block(struct sii8620 *ctx) | |
1983 | { | |
1984 | u8 stat = sii8620_readb(ctx, REG_EMSCINTR); | |
1985 | ||
1986 | if (stat & BIT_EMSCINTR_SPI_DVLD) { | |
1987 | u8 bstat = sii8620_readb(ctx, REG_SPIBURSTSTAT); | |
1988 | ||
1989 | if (bstat & BIT_SPIBURSTSTAT_EMSC_NORMAL_MODE) | |
1990 | sii8620_burst_receive(ctx); | |
1991 | } | |
1992 | ||
1993 | sii8620_write(ctx, REG_EMSCINTR, stat); | |
1994 | } | |
1995 | ||
263b5c93 AH |
1996 | static void sii8620_irq_ddc(struct sii8620 *ctx) |
1997 | { | |
1998 | u8 stat = sii8620_readb(ctx, REG_INTR3); | |
1999 | ||
2000 | if (stat & BIT_DDC_CMD_DONE) { | |
2001 | sii8620_write(ctx, REG_INTR3_MASK, 0); | |
2002 | if (sii8620_is_mhl3(ctx)) | |
2003 | sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE), | |
2004 | MHL_INT_RC_FEAT_REQ); | |
2005 | else | |
2006 | sii8620_edid_read(ctx, 0); | |
2007 | } | |
2008 | sii8620_write(ctx, REG_INTR3, stat); | |
2009 | } | |
2010 | ||
ce6e153f AH |
2011 | /* endian agnostic, non-volatile version of test_bit */ |
2012 | static bool sii8620_test_bit(unsigned int nr, const u8 *addr) | |
2013 | { | |
2014 | return 1 & (addr[nr / BITS_PER_BYTE] >> (nr % BITS_PER_BYTE)); | |
2015 | } | |
2016 | ||
2017 | static irqreturn_t sii8620_irq_thread(int irq, void *data) | |
2018 | { | |
2019 | static const struct { | |
2020 | int bit; | |
2021 | void (*handler)(struct sii8620 *ctx); | |
2022 | } irq_vec[] = { | |
2023 | { BIT_FAST_INTR_STAT_DISC, sii8620_irq_disc }, | |
2024 | { BIT_FAST_INTR_STAT_G2WB, sii8620_irq_g2wb }, | |
2025 | { BIT_FAST_INTR_STAT_COC, sii8620_irq_coc }, | |
e19e9c69 | 2026 | { BIT_FAST_INTR_STAT_TDM, sii8620_irq_tdm }, |
ce6e153f AH |
2027 | { BIT_FAST_INTR_STAT_MSC, sii8620_irq_msc }, |
2028 | { BIT_FAST_INTR_STAT_MERR, sii8620_irq_merr }, | |
e19e9c69 | 2029 | { BIT_FAST_INTR_STAT_BLOCK, sii8620_irq_block }, |
ce6e153f | 2030 | { BIT_FAST_INTR_STAT_EDID, sii8620_irq_edid }, |
263b5c93 | 2031 | { BIT_FAST_INTR_STAT_DDC, sii8620_irq_ddc }, |
ce6e153f AH |
2032 | { BIT_FAST_INTR_STAT_SCDT, sii8620_irq_scdt }, |
2033 | { BIT_FAST_INTR_STAT_INFR, sii8620_irq_infr }, | |
2034 | }; | |
2035 | struct sii8620 *ctx = data; | |
2036 | u8 stats[LEN_FAST_INTR_STAT]; | |
2037 | int i, ret; | |
2038 | ||
2039 | mutex_lock(&ctx->lock); | |
2040 | ||
2041 | sii8620_read_buf(ctx, REG_FAST_INTR_STAT, stats, ARRAY_SIZE(stats)); | |
2042 | for (i = 0; i < ARRAY_SIZE(irq_vec); ++i) | |
2043 | if (sii8620_test_bit(irq_vec[i].bit, stats)) | |
2044 | irq_vec[i].handler(ctx); | |
2045 | ||
e19e9c69 | 2046 | sii8620_burst_rx_all(ctx); |
ce6e153f | 2047 | sii8620_mt_work(ctx); |
e19e9c69 | 2048 | sii8620_burst_send(ctx); |
ce6e153f AH |
2049 | |
2050 | ret = sii8620_clear_error(ctx); | |
2051 | if (ret) { | |
2052 | dev_err(ctx->dev, "Error during IRQ handling, %d.\n", ret); | |
2053 | sii8620_mhl_disconnected(ctx); | |
2054 | } | |
2055 | mutex_unlock(&ctx->lock); | |
2056 | ||
2057 | return IRQ_HANDLED; | |
2058 | } | |
2059 | ||
2060 | static void sii8620_cable_in(struct sii8620 *ctx) | |
2061 | { | |
2062 | struct device *dev = ctx->dev; | |
2063 | u8 ver[5]; | |
2064 | int ret; | |
2065 | ||
2066 | ret = sii8620_hw_on(ctx); | |
2067 | if (ret) { | |
2068 | dev_err(dev, "Error powering on, %d.\n", ret); | |
2069 | return; | |
2070 | } | |
2071 | sii8620_hw_reset(ctx); | |
2072 | ||
2073 | sii8620_read_buf(ctx, REG_VND_IDL, ver, ARRAY_SIZE(ver)); | |
2074 | ret = sii8620_clear_error(ctx); | |
2075 | if (ret) { | |
2076 | dev_err(dev, "Error accessing I2C bus, %d.\n", ret); | |
2077 | return; | |
2078 | } | |
2079 | ||
2080 | dev_info(dev, "ChipID %02x%02x:%02x%02x rev %02x.\n", ver[1], ver[0], | |
2081 | ver[3], ver[2], ver[4]); | |
2082 | ||
2083 | sii8620_write(ctx, REG_DPD, | |
2084 | BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | BIT_DPD_OSC_EN); | |
2085 | ||
2086 | sii8620_xtal_set_rate(ctx); | |
2087 | sii8620_disconnect(ctx); | |
2088 | ||
2089 | sii8620_write_seq_static(ctx, | |
2090 | REG_MHL_CBUS_CTL0, VAL_MHL_CBUS_CTL0_CBUS_DRV_SEL_STRONG | |
2091 | | VAL_MHL_CBUS_CTL0_CBUS_RGND_VBIAS_734, | |
2092 | REG_MHL_CBUS_CTL1, VAL_MHL_CBUS_CTL1_1115_OHM, | |
2093 | REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | BIT_DPD_OSC_EN, | |
2094 | ); | |
2095 | ||
2096 | ret = sii8620_clear_error(ctx); | |
2097 | if (ret) { | |
2098 | dev_err(dev, "Error accessing I2C bus, %d.\n", ret); | |
2099 | return; | |
2100 | } | |
2101 | ||
2102 | enable_irq(to_i2c_client(ctx->dev)->irq); | |
2103 | } | |
2104 | ||
2105 | static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge) | |
2106 | { | |
2107 | return container_of(bridge, struct sii8620, bridge); | |
2108 | } | |
2109 | ||
2110 | static bool sii8620_mode_fixup(struct drm_bridge *bridge, | |
2111 | const struct drm_display_mode *mode, | |
2112 | struct drm_display_mode *adjusted_mode) | |
2113 | { | |
2114 | struct sii8620 *ctx = bridge_to_sii8620(bridge); | |
bf1722ca AH |
2115 | int max_lclk; |
2116 | bool ret = true; | |
ce6e153f | 2117 | |
bf1722ca | 2118 | mutex_lock(&ctx->lock); |
ce6e153f | 2119 | |
bf1722ca AH |
2120 | max_lclk = sii8620_is_mhl3(ctx) ? MHL3_MAX_LCLK : MHL1_MAX_LCLK; |
2121 | if (max_lclk > 3 * adjusted_mode->clock) { | |
2122 | ctx->use_packed_pixel = 0; | |
2123 | goto end; | |
2124 | } | |
2125 | if ((ctx->devcap[MHL_DCAP_VID_LINK_MODE] & MHL_DCAP_VID_LINK_PPIXEL) && | |
2126 | max_lclk > 2 * adjusted_mode->clock) { | |
2127 | ctx->use_packed_pixel = 1; | |
2128 | goto end; | |
2129 | } | |
2130 | ret = false; | |
2131 | end: | |
2132 | if (ret) { | |
2133 | u8 vic = drm_match_cea_mode(adjusted_mode); | |
2134 | ||
2135 | if (!vic) { | |
2136 | union hdmi_infoframe frm; | |
2137 | u8 mhl_vic[] = { 0, 95, 94, 93, 98 }; | |
2138 | ||
2139 | drm_hdmi_vendor_infoframe_from_display_mode( | |
2140 | &frm.vendor.hdmi, adjusted_mode); | |
2141 | vic = frm.vendor.hdmi.vic; | |
2142 | if (vic >= ARRAY_SIZE(mhl_vic)) | |
2143 | vic = 0; | |
2144 | vic = mhl_vic[vic]; | |
2145 | } | |
2146 | ctx->video_code = vic; | |
2147 | ctx->pixel_clock = adjusted_mode->clock; | |
2148 | } | |
ce6e153f | 2149 | mutex_unlock(&ctx->lock); |
ce6e153f AH |
2150 | return ret; |
2151 | } | |
2152 | ||
2153 | static const struct drm_bridge_funcs sii8620_bridge_funcs = { | |
2154 | .mode_fixup = sii8620_mode_fixup, | |
2155 | }; | |
2156 | ||
2157 | static int sii8620_probe(struct i2c_client *client, | |
2158 | const struct i2c_device_id *id) | |
2159 | { | |
2160 | struct device *dev = &client->dev; | |
2161 | struct sii8620 *ctx; | |
2162 | int ret; | |
2163 | ||
2164 | ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); | |
2165 | if (!ctx) | |
2166 | return -ENOMEM; | |
2167 | ||
2168 | ctx->dev = dev; | |
2169 | mutex_init(&ctx->lock); | |
2170 | INIT_LIST_HEAD(&ctx->mt_queue); | |
2171 | ||
2172 | ctx->clk_xtal = devm_clk_get(dev, "xtal"); | |
2173 | if (IS_ERR(ctx->clk_xtal)) { | |
2174 | dev_err(dev, "failed to get xtal clock from DT\n"); | |
2175 | return PTR_ERR(ctx->clk_xtal); | |
2176 | } | |
2177 | ||
2178 | if (!client->irq) { | |
2179 | dev_err(dev, "no irq provided\n"); | |
2180 | return -EINVAL; | |
2181 | } | |
2182 | irq_set_status_flags(client->irq, IRQ_NOAUTOEN); | |
2183 | ret = devm_request_threaded_irq(dev, client->irq, NULL, | |
2184 | sii8620_irq_thread, | |
2185 | IRQF_TRIGGER_HIGH | IRQF_ONESHOT, | |
2186 | "sii8620", ctx); | |
e0ba12ea AH |
2187 | if (ret < 0) { |
2188 | dev_err(dev, "failed to install IRQ handler\n"); | |
2189 | return ret; | |
2190 | } | |
ce6e153f AH |
2191 | |
2192 | ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); | |
2193 | if (IS_ERR(ctx->gpio_reset)) { | |
2194 | dev_err(dev, "failed to get reset gpio from DT\n"); | |
2195 | return PTR_ERR(ctx->gpio_reset); | |
2196 | } | |
2197 | ||
2198 | ctx->supplies[0].supply = "cvcc10"; | |
2199 | ctx->supplies[1].supply = "iovcc18"; | |
2200 | ret = devm_regulator_bulk_get(dev, 2, ctx->supplies); | |
2201 | if (ret) | |
2202 | return ret; | |
2203 | ||
2204 | i2c_set_clientdata(client, ctx); | |
2205 | ||
2206 | ctx->bridge.funcs = &sii8620_bridge_funcs; | |
2207 | ctx->bridge.of_node = dev->of_node; | |
2208 | drm_bridge_add(&ctx->bridge); | |
2209 | ||
2210 | sii8620_cable_in(ctx); | |
2211 | ||
2212 | return 0; | |
2213 | } | |
2214 | ||
2215 | static int sii8620_remove(struct i2c_client *client) | |
2216 | { | |
2217 | struct sii8620 *ctx = i2c_get_clientdata(client); | |
2218 | ||
2219 | disable_irq(to_i2c_client(ctx->dev)->irq); | |
2220 | drm_bridge_remove(&ctx->bridge); | |
2221 | sii8620_hw_off(ctx); | |
2222 | ||
2223 | return 0; | |
2224 | } | |
2225 | ||
2226 | static const struct of_device_id sii8620_dt_match[] = { | |
2227 | { .compatible = "sil,sii8620" }, | |
2228 | { }, | |
2229 | }; | |
2230 | MODULE_DEVICE_TABLE(of, sii8620_dt_match); | |
2231 | ||
2232 | static const struct i2c_device_id sii8620_id[] = { | |
2233 | { "sii8620", 0 }, | |
2234 | { }, | |
2235 | }; | |
2236 | ||
2237 | MODULE_DEVICE_TABLE(i2c, sii8620_id); | |
2238 | static struct i2c_driver sii8620_driver = { | |
2239 | .driver = { | |
2240 | .name = "sii8620", | |
ce6e153f AH |
2241 | .of_match_table = of_match_ptr(sii8620_dt_match), |
2242 | }, | |
2243 | .probe = sii8620_probe, | |
2244 | .remove = sii8620_remove, | |
2245 | .id_table = sii8620_id, | |
2246 | }; | |
2247 | ||
2248 | module_i2c_driver(sii8620_driver); | |
2249 | MODULE_LICENSE("GPL v2"); |