]>
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 | ||
12 | #include <drm/bridge/mhl.h> | |
13 | #include <drm/drm_crtc.h> | |
14 | #include <drm/drm_edid.h> | |
15 | ||
16 | #include <linux/clk.h> | |
17 | #include <linux/delay.h> | |
18 | #include <linux/gpio/consumer.h> | |
19 | #include <linux/i2c.h> | |
20 | #include <linux/interrupt.h> | |
21 | #include <linux/irq.h> | |
22 | #include <linux/kernel.h> | |
23 | #include <linux/list.h> | |
24 | #include <linux/module.h> | |
25 | #include <linux/mutex.h> | |
26 | #include <linux/regulator/consumer.h> | |
27 | #include <linux/slab.h> | |
28 | ||
29 | #include "sil-sii8620.h" | |
30 | ||
31 | #define VAL_RX_HDMI_CTRL2_DEFVAL VAL_RX_HDMI_CTRL2_IDLE_CNT(3) | |
32 | ||
33 | enum sii8620_mode { | |
34 | CM_DISCONNECTED, | |
35 | CM_DISCOVERY, | |
36 | CM_MHL1, | |
37 | CM_MHL3, | |
38 | CM_ECBUS_S | |
39 | }; | |
40 | ||
41 | enum sii8620_sink_type { | |
42 | SINK_NONE, | |
43 | SINK_HDMI, | |
44 | SINK_DVI | |
45 | }; | |
46 | ||
47 | enum sii8620_mt_state { | |
48 | MT_STATE_READY, | |
49 | MT_STATE_BUSY, | |
50 | MT_STATE_DONE | |
51 | }; | |
52 | ||
53 | struct sii8620 { | |
54 | struct drm_bridge bridge; | |
55 | struct device *dev; | |
56 | struct clk *clk_xtal; | |
57 | struct gpio_desc *gpio_reset; | |
58 | struct gpio_desc *gpio_int; | |
59 | struct regulator_bulk_data supplies[2]; | |
60 | struct mutex lock; /* context lock, protects fields below */ | |
61 | int error; | |
62 | enum sii8620_mode mode; | |
63 | enum sii8620_sink_type sink_type; | |
64 | u8 cbus_status; | |
65 | u8 stat[MHL_DST_SIZE]; | |
66 | u8 xstat[MHL_XDS_SIZE]; | |
67 | u8 devcap[MHL_DCAP_SIZE]; | |
68 | u8 xdevcap[MHL_XDC_SIZE]; | |
69 | u8 avif[19]; | |
70 | struct edid *edid; | |
71 | unsigned int gen2_write_burst:1; | |
72 | enum sii8620_mt_state mt_state; | |
73 | struct list_head mt_queue; | |
74 | }; | |
75 | ||
76 | struct sii8620_mt_msg; | |
77 | ||
78 | typedef void (*sii8620_mt_msg_cb)(struct sii8620 *ctx, | |
79 | struct sii8620_mt_msg *msg); | |
80 | ||
81 | struct sii8620_mt_msg { | |
82 | struct list_head node; | |
83 | u8 reg[4]; | |
84 | u8 ret; | |
85 | sii8620_mt_msg_cb send; | |
86 | sii8620_mt_msg_cb recv; | |
87 | }; | |
88 | ||
89 | static const u8 sii8620_i2c_page[] = { | |
90 | 0x39, /* Main System */ | |
91 | 0x3d, /* TDM and HSIC */ | |
92 | 0x49, /* TMDS Receiver, MHL EDID */ | |
93 | 0x4d, /* eMSC, HDCP, HSIC */ | |
94 | 0x5d, /* MHL Spec */ | |
95 | 0x64, /* MHL CBUS */ | |
96 | 0x59, /* Hardware TPI (Transmitter Programming Interface) */ | |
97 | 0x61, /* eCBUS-S, eCBUS-D */ | |
98 | }; | |
99 | ||
100 | static void sii8620_fetch_edid(struct sii8620 *ctx); | |
101 | static void sii8620_set_upstream_edid(struct sii8620 *ctx); | |
102 | static void sii8620_enable_hpd(struct sii8620 *ctx); | |
103 | static void sii8620_mhl_disconnected(struct sii8620 *ctx); | |
104 | ||
105 | static int sii8620_clear_error(struct sii8620 *ctx) | |
106 | { | |
107 | int ret = ctx->error; | |
108 | ||
109 | ctx->error = 0; | |
110 | return ret; | |
111 | } | |
112 | ||
113 | static void sii8620_read_buf(struct sii8620 *ctx, u16 addr, u8 *buf, int len) | |
114 | { | |
115 | struct device *dev = ctx->dev; | |
116 | struct i2c_client *client = to_i2c_client(dev); | |
117 | u8 data = addr; | |
118 | struct i2c_msg msg[] = { | |
119 | { | |
120 | .addr = sii8620_i2c_page[addr >> 8], | |
121 | .flags = client->flags, | |
122 | .len = 1, | |
123 | .buf = &data | |
124 | }, | |
125 | { | |
126 | .addr = sii8620_i2c_page[addr >> 8], | |
127 | .flags = client->flags | I2C_M_RD, | |
128 | .len = len, | |
129 | .buf = buf | |
130 | }, | |
131 | }; | |
132 | int ret; | |
133 | ||
134 | if (ctx->error) | |
135 | return; | |
136 | ||
137 | ret = i2c_transfer(client->adapter, msg, 2); | |
138 | dev_dbg(dev, "read at %04x: %*ph, %d\n", addr, len, buf, ret); | |
139 | ||
140 | if (ret != 2) { | |
141 | dev_err(dev, "Read at %#06x of %d bytes failed with code %d.\n", | |
142 | addr, len, ret); | |
143 | ctx->error = ret < 0 ? ret : -EIO; | |
144 | } | |
145 | } | |
146 | ||
147 | static u8 sii8620_readb(struct sii8620 *ctx, u16 addr) | |
148 | { | |
149 | u8 ret; | |
150 | ||
151 | sii8620_read_buf(ctx, addr, &ret, 1); | |
152 | return ret; | |
153 | } | |
154 | ||
155 | static void sii8620_write_buf(struct sii8620 *ctx, u16 addr, const u8 *buf, | |
156 | int len) | |
157 | { | |
158 | struct device *dev = ctx->dev; | |
159 | struct i2c_client *client = to_i2c_client(dev); | |
160 | u8 data[2]; | |
161 | struct i2c_msg msg = { | |
162 | .addr = sii8620_i2c_page[addr >> 8], | |
163 | .flags = client->flags, | |
164 | .len = len + 1, | |
165 | }; | |
166 | int ret; | |
167 | ||
168 | if (ctx->error) | |
169 | return; | |
170 | ||
171 | if (len > 1) { | |
172 | msg.buf = kmalloc(len + 1, GFP_KERNEL); | |
173 | if (!msg.buf) { | |
174 | ctx->error = -ENOMEM; | |
175 | return; | |
176 | } | |
177 | memcpy(msg.buf + 1, buf, len); | |
178 | } else { | |
179 | msg.buf = data; | |
180 | msg.buf[1] = *buf; | |
181 | } | |
182 | ||
183 | msg.buf[0] = addr; | |
184 | ||
185 | ret = i2c_transfer(client->adapter, &msg, 1); | |
186 | dev_dbg(dev, "write at %04x: %*ph, %d\n", addr, len, buf, ret); | |
187 | ||
188 | if (ret != 1) { | |
189 | dev_err(dev, "Write at %#06x of %*ph failed with code %d.\n", | |
190 | addr, len, buf, ret); | |
191 | ctx->error = ret ?: -EIO; | |
192 | } | |
193 | ||
194 | if (len > 1) | |
195 | kfree(msg.buf); | |
196 | } | |
197 | ||
198 | #define sii8620_write(ctx, addr, arr...) \ | |
199 | ({\ | |
200 | u8 d[] = { arr }; \ | |
201 | sii8620_write_buf(ctx, addr, d, ARRAY_SIZE(d)); \ | |
202 | }) | |
203 | ||
204 | static void __sii8620_write_seq(struct sii8620 *ctx, const u16 *seq, int len) | |
205 | { | |
206 | int i; | |
207 | ||
208 | for (i = 0; i < len; i += 2) | |
209 | sii8620_write(ctx, seq[i], seq[i + 1]); | |
210 | } | |
211 | ||
212 | #define sii8620_write_seq(ctx, seq...) \ | |
213 | ({\ | |
214 | const u16 d[] = { seq }; \ | |
215 | __sii8620_write_seq(ctx, d, ARRAY_SIZE(d)); \ | |
216 | }) | |
217 | ||
218 | #define sii8620_write_seq_static(ctx, seq...) \ | |
219 | ({\ | |
220 | static const u16 d[] = { seq }; \ | |
221 | __sii8620_write_seq(ctx, d, ARRAY_SIZE(d)); \ | |
222 | }) | |
223 | ||
224 | static void sii8620_setbits(struct sii8620 *ctx, u16 addr, u8 mask, u8 val) | |
225 | { | |
226 | val = (val & mask) | (sii8620_readb(ctx, addr) & ~mask); | |
227 | sii8620_write(ctx, addr, val); | |
228 | } | |
229 | ||
bb4954c7 AH |
230 | static inline bool sii8620_is_mhl3(struct sii8620 *ctx) |
231 | { | |
232 | return ctx->mode >= CM_MHL3; | |
233 | } | |
234 | ||
ce6e153f AH |
235 | static void sii8620_mt_cleanup(struct sii8620 *ctx) |
236 | { | |
237 | struct sii8620_mt_msg *msg, *n; | |
238 | ||
239 | list_for_each_entry_safe(msg, n, &ctx->mt_queue, node) { | |
240 | list_del(&msg->node); | |
241 | kfree(msg); | |
242 | } | |
243 | ctx->mt_state = MT_STATE_READY; | |
244 | } | |
245 | ||
246 | static void sii8620_mt_work(struct sii8620 *ctx) | |
247 | { | |
248 | struct sii8620_mt_msg *msg; | |
249 | ||
250 | if (ctx->error) | |
251 | return; | |
252 | if (ctx->mt_state == MT_STATE_BUSY || list_empty(&ctx->mt_queue)) | |
253 | return; | |
254 | ||
255 | if (ctx->mt_state == MT_STATE_DONE) { | |
256 | ctx->mt_state = MT_STATE_READY; | |
257 | msg = list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, | |
258 | node); | |
259 | if (msg->recv) | |
260 | msg->recv(ctx, msg); | |
261 | list_del(&msg->node); | |
262 | kfree(msg); | |
263 | } | |
264 | ||
265 | if (ctx->mt_state != MT_STATE_READY || list_empty(&ctx->mt_queue)) | |
266 | return; | |
267 | ||
268 | ctx->mt_state = MT_STATE_BUSY; | |
269 | msg = list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, node); | |
270 | if (msg->send) | |
271 | msg->send(ctx, msg); | |
272 | } | |
273 | ||
274 | static void sii8620_mt_msc_cmd_send(struct sii8620 *ctx, | |
275 | struct sii8620_mt_msg *msg) | |
276 | { | |
277 | switch (msg->reg[0]) { | |
278 | case MHL_WRITE_STAT: | |
279 | case MHL_SET_INT: | |
280 | sii8620_write_buf(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg + 1, 2); | |
281 | sii8620_write(ctx, REG_MSC_COMMAND_START, | |
282 | BIT_MSC_COMMAND_START_WRITE_STAT); | |
283 | break; | |
284 | case MHL_MSC_MSG: | |
285 | sii8620_write_buf(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg, 3); | |
286 | sii8620_write(ctx, REG_MSC_COMMAND_START, | |
287 | BIT_MSC_COMMAND_START_MSC_MSG); | |
288 | break; | |
e9c6da27 AH |
289 | case MHL_READ_DEVCAP_REG: |
290 | case MHL_READ_XDEVCAP_REG: | |
291 | sii8620_write(ctx, REG_MSC_CMD_OR_OFFSET, msg->reg[1]); | |
292 | sii8620_write(ctx, REG_MSC_COMMAND_START, | |
293 | BIT_MSC_COMMAND_START_READ_DEVCAP); | |
294 | break; | |
ce6e153f AH |
295 | default: |
296 | dev_err(ctx->dev, "%s: command %#x not supported\n", __func__, | |
297 | msg->reg[0]); | |
298 | } | |
299 | } | |
300 | ||
301 | static struct sii8620_mt_msg *sii8620_mt_msg_new(struct sii8620 *ctx) | |
302 | { | |
303 | struct sii8620_mt_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL); | |
304 | ||
305 | if (!msg) | |
306 | ctx->error = -ENOMEM; | |
307 | else | |
308 | list_add_tail(&msg->node, &ctx->mt_queue); | |
309 | ||
310 | return msg; | |
311 | } | |
312 | ||
313 | static void sii8620_mt_msc_cmd(struct sii8620 *ctx, u8 cmd, u8 arg1, u8 arg2) | |
314 | { | |
315 | struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); | |
316 | ||
317 | if (!msg) | |
318 | return; | |
319 | ||
320 | msg->reg[0] = cmd; | |
321 | msg->reg[1] = arg1; | |
322 | msg->reg[2] = arg2; | |
323 | msg->send = sii8620_mt_msc_cmd_send; | |
324 | } | |
325 | ||
326 | static void sii8620_mt_write_stat(struct sii8620 *ctx, u8 reg, u8 val) | |
327 | { | |
328 | sii8620_mt_msc_cmd(ctx, MHL_WRITE_STAT, reg, val); | |
329 | } | |
330 | ||
331 | static inline void sii8620_mt_set_int(struct sii8620 *ctx, u8 irq, u8 mask) | |
332 | { | |
333 | sii8620_mt_msc_cmd(ctx, MHL_SET_INT, irq, mask); | |
334 | } | |
335 | ||
336 | static void sii8620_mt_msc_msg(struct sii8620 *ctx, u8 cmd, u8 data) | |
337 | { | |
338 | sii8620_mt_msc_cmd(ctx, MHL_MSC_MSG, cmd, data); | |
339 | } | |
340 | ||
341 | static void sii8620_mt_rap(struct sii8620 *ctx, u8 code) | |
342 | { | |
343 | sii8620_mt_msc_msg(ctx, MHL_MSC_MSG_RAP, code); | |
344 | } | |
345 | ||
346 | static void sii8620_mt_read_devcap_send(struct sii8620 *ctx, | |
347 | struct sii8620_mt_msg *msg) | |
348 | { | |
349 | u8 ctrl = BIT_EDID_CTRL_DEVCAP_SELECT_DEVCAP | |
350 | | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO | |
351 | | BIT_EDID_CTRL_EDID_MODE_EN; | |
352 | ||
353 | if (msg->reg[0] == MHL_READ_XDEVCAP) | |
354 | ctrl |= BIT_EDID_CTRL_XDEVCAP_EN; | |
355 | ||
356 | sii8620_write_seq(ctx, | |
357 | REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE, | |
358 | REG_EDID_CTRL, ctrl, | |
359 | REG_TPI_CBUS_START, BIT_TPI_CBUS_START_GET_DEVCAP_START | |
360 | ); | |
361 | } | |
362 | ||
363 | /* copy src to dst and set changed bits in src */ | |
364 | static void sii8620_update_array(u8 *dst, u8 *src, int count) | |
365 | { | |
366 | while (--count >= 0) { | |
367 | *src ^= *dst; | |
368 | *dst++ ^= *src++; | |
369 | } | |
370 | } | |
371 | ||
372 | static void sii8620_mr_devcap(struct sii8620 *ctx) | |
373 | { | |
374 | static const char * const sink_str[] = { | |
375 | [SINK_NONE] = "NONE", | |
376 | [SINK_HDMI] = "HDMI", | |
377 | [SINK_DVI] = "DVI" | |
378 | }; | |
379 | ||
380 | u8 dcap[MHL_DCAP_SIZE]; | |
381 | char sink_name[20]; | |
382 | struct device *dev = ctx->dev; | |
383 | ||
384 | sii8620_read_buf(ctx, REG_EDID_FIFO_RD_DATA, dcap, MHL_DCAP_SIZE); | |
385 | if (ctx->error < 0) | |
386 | return; | |
387 | ||
388 | dev_info(dev, "dcap: %*ph\n", MHL_DCAP_SIZE, dcap); | |
389 | dev_info(dev, "detected dongle MHL %d.%d, ChipID %02x%02x:%02x%02x\n", | |
390 | dcap[MHL_DCAP_MHL_VERSION] / 16, | |
391 | dcap[MHL_DCAP_MHL_VERSION] % 16, dcap[MHL_DCAP_ADOPTER_ID_H], | |
392 | dcap[MHL_DCAP_ADOPTER_ID_L], dcap[MHL_DCAP_DEVICE_ID_H], | |
393 | dcap[MHL_DCAP_DEVICE_ID_L]); | |
394 | sii8620_update_array(ctx->devcap, dcap, MHL_DCAP_SIZE); | |
395 | ||
396 | if (!(dcap[MHL_DCAP_CAT] & MHL_DCAP_CAT_SINK)) | |
397 | return; | |
398 | ||
399 | sii8620_fetch_edid(ctx); | |
400 | if (!ctx->edid) { | |
401 | dev_err(ctx->dev, "Cannot fetch EDID\n"); | |
402 | sii8620_mhl_disconnected(ctx); | |
403 | return; | |
404 | } | |
405 | ||
406 | if (drm_detect_hdmi_monitor(ctx->edid)) | |
407 | ctx->sink_type = SINK_HDMI; | |
408 | else | |
409 | ctx->sink_type = SINK_DVI; | |
410 | ||
411 | drm_edid_get_monitor_name(ctx->edid, sink_name, ARRAY_SIZE(sink_name)); | |
412 | ||
413 | dev_info(dev, "detected sink(type: %s): %s\n", | |
414 | sink_str[ctx->sink_type], sink_name); | |
415 | sii8620_set_upstream_edid(ctx); | |
416 | sii8620_enable_hpd(ctx); | |
417 | } | |
418 | ||
419 | static void sii8620_mr_xdevcap(struct sii8620 *ctx) | |
420 | { | |
421 | sii8620_read_buf(ctx, REG_EDID_FIFO_RD_DATA, ctx->xdevcap, | |
422 | MHL_XDC_SIZE); | |
423 | ||
424 | sii8620_mt_write_stat(ctx, MHL_XDS_REG(CURR_ECBUS_MODE), | |
425 | MHL_XDS_ECBUS_S | MHL_XDS_SLOT_MODE_8BIT); | |
426 | sii8620_mt_rap(ctx, MHL_RAP_CBUS_MODE_UP); | |
427 | } | |
428 | ||
429 | static void sii8620_mt_read_devcap_recv(struct sii8620 *ctx, | |
430 | struct sii8620_mt_msg *msg) | |
431 | { | |
432 | u8 ctrl = BIT_EDID_CTRL_DEVCAP_SELECT_DEVCAP | |
433 | | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO | |
434 | | BIT_EDID_CTRL_EDID_MODE_EN; | |
435 | ||
436 | if (msg->reg[0] == MHL_READ_XDEVCAP) | |
437 | ctrl |= BIT_EDID_CTRL_XDEVCAP_EN; | |
438 | ||
439 | sii8620_write_seq(ctx, | |
440 | REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE | BIT_INTR9_EDID_DONE | |
441 | | BIT_INTR9_EDID_ERROR, | |
442 | REG_EDID_CTRL, ctrl, | |
443 | REG_EDID_FIFO_ADDR, 0 | |
444 | ); | |
445 | ||
446 | if (msg->reg[0] == MHL_READ_XDEVCAP) | |
447 | sii8620_mr_xdevcap(ctx); | |
448 | else | |
449 | sii8620_mr_devcap(ctx); | |
450 | } | |
451 | ||
452 | static void sii8620_mt_read_devcap(struct sii8620 *ctx, bool xdevcap) | |
453 | { | |
454 | struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); | |
455 | ||
456 | if (!msg) | |
457 | return; | |
458 | ||
459 | msg->reg[0] = xdevcap ? MHL_READ_XDEVCAP : MHL_READ_DEVCAP; | |
460 | msg->send = sii8620_mt_read_devcap_send; | |
461 | msg->recv = sii8620_mt_read_devcap_recv; | |
462 | } | |
463 | ||
e9c6da27 AH |
464 | static void sii8620_mt_read_devcap_reg_recv(struct sii8620 *ctx, |
465 | struct sii8620_mt_msg *msg) | |
466 | { | |
467 | u8 reg = msg->reg[0] & 0x7f; | |
468 | ||
469 | if (msg->reg[0] & 0x80) | |
470 | ctx->xdevcap[reg] = msg->ret; | |
471 | else | |
472 | ctx->devcap[reg] = msg->ret; | |
473 | } | |
474 | ||
475 | static void sii8620_mt_read_devcap_reg(struct sii8620 *ctx, u8 reg) | |
476 | { | |
477 | struct sii8620_mt_msg *msg = sii8620_mt_msg_new(ctx); | |
478 | ||
479 | if (!msg) | |
480 | return; | |
481 | ||
482 | msg->reg[0] = (reg & 0x80) ? MHL_READ_XDEVCAP_REG : MHL_READ_DEVCAP_REG; | |
483 | msg->reg[1] = reg; | |
484 | msg->send = sii8620_mt_msc_cmd_send; | |
485 | msg->recv = sii8620_mt_read_devcap_reg_recv; | |
486 | } | |
487 | ||
488 | static inline void sii8620_mt_read_xdevcap_reg(struct sii8620 *ctx, u8 reg) | |
489 | { | |
490 | sii8620_mt_read_devcap_reg(ctx, reg | 0x80); | |
491 | } | |
492 | ||
ce6e153f AH |
493 | static void sii8620_fetch_edid(struct sii8620 *ctx) |
494 | { | |
495 | u8 lm_ddc, ddc_cmd, int3, cbus; | |
496 | int fetched, i; | |
497 | int edid_len = EDID_LENGTH; | |
498 | u8 *edid; | |
499 | ||
500 | sii8620_readb(ctx, REG_CBUS_STATUS); | |
501 | lm_ddc = sii8620_readb(ctx, REG_LM_DDC); | |
502 | ddc_cmd = sii8620_readb(ctx, REG_DDC_CMD); | |
503 | ||
504 | sii8620_write_seq(ctx, | |
505 | REG_INTR9_MASK, 0, | |
506 | REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO, | |
507 | REG_HDCP2X_POLL_CS, 0x71, | |
508 | REG_HDCP2X_CTRL_0, BIT_HDCP2X_CTRL_0_HDCP2X_HDCPTX, | |
509 | REG_LM_DDC, lm_ddc | BIT_LM_DDC_SW_TPI_EN_DISABLED, | |
510 | ); | |
511 | ||
512 | for (i = 0; i < 256; ++i) { | |
513 | u8 ddc_stat = sii8620_readb(ctx, REG_DDC_STATUS); | |
514 | ||
515 | if (!(ddc_stat & BIT_DDC_STATUS_DDC_I2C_IN_PROG)) | |
516 | break; | |
517 | sii8620_write(ctx, REG_DDC_STATUS, | |
518 | BIT_DDC_STATUS_DDC_FIFO_EMPTY); | |
519 | } | |
520 | ||
521 | sii8620_write(ctx, REG_DDC_ADDR, 0x50 << 1); | |
522 | ||
523 | edid = kmalloc(EDID_LENGTH, GFP_KERNEL); | |
524 | if (!edid) { | |
525 | ctx->error = -ENOMEM; | |
526 | return; | |
527 | } | |
528 | ||
529 | #define FETCH_SIZE 16 | |
530 | for (fetched = 0; fetched < edid_len; fetched += FETCH_SIZE) { | |
531 | sii8620_readb(ctx, REG_DDC_STATUS); | |
532 | sii8620_write_seq(ctx, | |
533 | REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_DDC_CMD_ABORT, | |
534 | REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_DDC_CMD_CLEAR_FIFO, | |
535 | REG_DDC_STATUS, BIT_DDC_STATUS_DDC_FIFO_EMPTY | |
536 | ); | |
537 | sii8620_write_seq(ctx, | |
538 | REG_DDC_SEGM, fetched >> 8, | |
539 | REG_DDC_OFFSET, fetched & 0xff, | |
540 | REG_DDC_DIN_CNT1, FETCH_SIZE, | |
541 | REG_DDC_DIN_CNT2, 0, | |
542 | REG_DDC_CMD, ddc_cmd | VAL_DDC_CMD_ENH_DDC_READ_NO_ACK | |
543 | ); | |
544 | ||
545 | do { | |
546 | int3 = sii8620_readb(ctx, REG_INTR3); | |
547 | cbus = sii8620_readb(ctx, REG_CBUS_STATUS); | |
548 | ||
549 | if (int3 & BIT_DDC_CMD_DONE) | |
550 | break; | |
551 | ||
552 | if (!(cbus & BIT_CBUS_STATUS_CBUS_CONNECTED)) { | |
553 | kfree(edid); | |
554 | edid = NULL; | |
555 | goto end; | |
556 | } | |
557 | } while (1); | |
558 | ||
559 | sii8620_readb(ctx, REG_DDC_STATUS); | |
560 | while (sii8620_readb(ctx, REG_DDC_DOUT_CNT) < FETCH_SIZE) | |
561 | usleep_range(10, 20); | |
562 | ||
563 | sii8620_read_buf(ctx, REG_DDC_DATA, edid + fetched, FETCH_SIZE); | |
564 | if (fetched + FETCH_SIZE == EDID_LENGTH) { | |
565 | u8 ext = ((struct edid *)edid)->extensions; | |
566 | ||
567 | if (ext) { | |
568 | u8 *new_edid; | |
569 | ||
570 | edid_len += ext * EDID_LENGTH; | |
571 | new_edid = krealloc(edid, edid_len, GFP_KERNEL); | |
572 | if (!new_edid) { | |
573 | kfree(edid); | |
574 | ctx->error = -ENOMEM; | |
575 | return; | |
576 | } | |
577 | edid = new_edid; | |
578 | } | |
579 | } | |
580 | ||
581 | if (fetched + FETCH_SIZE == edid_len) | |
582 | sii8620_write(ctx, REG_INTR3, int3); | |
583 | } | |
584 | ||
585 | sii8620_write(ctx, REG_LM_DDC, lm_ddc); | |
586 | ||
587 | end: | |
588 | kfree(ctx->edid); | |
589 | ctx->edid = (struct edid *)edid; | |
590 | } | |
591 | ||
592 | static void sii8620_set_upstream_edid(struct sii8620 *ctx) | |
593 | { | |
594 | sii8620_setbits(ctx, REG_DPD, BIT_DPD_PDNRX12 | BIT_DPD_PDIDCK_N | |
595 | | BIT_DPD_PD_MHL_CLK_N, 0xff); | |
596 | ||
597 | sii8620_write_seq_static(ctx, | |
598 | REG_RX_HDMI_CTRL3, 0x00, | |
599 | REG_PKT_FILTER_0, 0xFF, | |
600 | REG_PKT_FILTER_1, 0xFF, | |
601 | REG_ALICE0_BW_I2C, 0x06 | |
602 | ); | |
603 | ||
604 | sii8620_setbits(ctx, REG_RX_HDMI_CLR_BUFFER, | |
605 | BIT_RX_HDMI_CLR_BUFFER_VSI_CLR_EN, 0xff); | |
606 | ||
607 | sii8620_write_seq_static(ctx, | |
608 | REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO | |
609 | | BIT_EDID_CTRL_EDID_MODE_EN, | |
610 | REG_EDID_FIFO_ADDR, 0, | |
611 | ); | |
612 | ||
613 | sii8620_write_buf(ctx, REG_EDID_FIFO_WR_DATA, (u8 *)ctx->edid, | |
614 | (ctx->edid->extensions + 1) * EDID_LENGTH); | |
615 | ||
616 | sii8620_write_seq_static(ctx, | |
617 | REG_EDID_CTRL, BIT_EDID_CTRL_EDID_PRIME_VALID | |
618 | | BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO | |
619 | | BIT_EDID_CTRL_EDID_MODE_EN, | |
620 | REG_INTR5_MASK, BIT_INTR_SCDT_CHANGE, | |
621 | REG_INTR9_MASK, 0 | |
622 | ); | |
623 | } | |
624 | ||
625 | static void sii8620_xtal_set_rate(struct sii8620 *ctx) | |
626 | { | |
627 | static const struct { | |
628 | unsigned int rate; | |
629 | u8 div; | |
630 | u8 tp1; | |
631 | } rates[] = { | |
632 | { 19200, 0x04, 0x53 }, | |
633 | { 20000, 0x04, 0x62 }, | |
634 | { 24000, 0x05, 0x75 }, | |
635 | { 30000, 0x06, 0x92 }, | |
636 | { 38400, 0x0c, 0xbc }, | |
637 | }; | |
638 | unsigned long rate = clk_get_rate(ctx->clk_xtal) / 1000; | |
639 | int i; | |
640 | ||
641 | for (i = 0; i < ARRAY_SIZE(rates) - 1; ++i) | |
642 | if (rate <= rates[i].rate) | |
643 | break; | |
644 | ||
645 | if (rate != rates[i].rate) | |
646 | dev_err(ctx->dev, "xtal clock rate(%lukHz) not supported, setting MHL for %ukHz.\n", | |
647 | rate, rates[i].rate); | |
648 | ||
649 | sii8620_write(ctx, REG_DIV_CTL_MAIN, rates[i].div); | |
650 | sii8620_write(ctx, REG_HDCP2X_TP1, rates[i].tp1); | |
651 | } | |
652 | ||
653 | static int sii8620_hw_on(struct sii8620 *ctx) | |
654 | { | |
655 | int ret; | |
656 | ||
657 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); | |
658 | if (ret) | |
659 | return ret; | |
660 | usleep_range(10000, 20000); | |
661 | return clk_prepare_enable(ctx->clk_xtal); | |
662 | } | |
663 | ||
664 | static int sii8620_hw_off(struct sii8620 *ctx) | |
665 | { | |
666 | clk_disable_unprepare(ctx->clk_xtal); | |
667 | gpiod_set_value(ctx->gpio_reset, 1); | |
668 | return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); | |
669 | } | |
670 | ||
671 | static void sii8620_hw_reset(struct sii8620 *ctx) | |
672 | { | |
673 | usleep_range(10000, 20000); | |
674 | gpiod_set_value(ctx->gpio_reset, 0); | |
675 | usleep_range(5000, 20000); | |
676 | gpiod_set_value(ctx->gpio_reset, 1); | |
677 | usleep_range(10000, 20000); | |
678 | gpiod_set_value(ctx->gpio_reset, 0); | |
679 | msleep(300); | |
680 | } | |
681 | ||
682 | static void sii8620_cbus_reset(struct sii8620 *ctx) | |
683 | { | |
684 | sii8620_write_seq_static(ctx, | |
685 | REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST | |
686 | | BIT_PWD_SRST_CBUS_RST_SW_EN, | |
687 | REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST_SW_EN | |
688 | ); | |
689 | } | |
690 | ||
691 | static void sii8620_set_auto_zone(struct sii8620 *ctx) | |
692 | { | |
693 | if (ctx->mode != CM_MHL1) { | |
694 | sii8620_write_seq_static(ctx, | |
695 | REG_TX_ZONE_CTL1, 0x0, | |
696 | REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X | |
697 | | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL | |
698 | | BIT_MHL_PLL_CTL0_ZONE_MASK_OE | |
699 | ); | |
700 | } else { | |
701 | sii8620_write_seq_static(ctx, | |
702 | REG_TX_ZONE_CTL1, VAL_TX_ZONE_CTL1_TX_ZONE_CTRL_MODE, | |
703 | REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X | |
704 | | BIT_MHL_PLL_CTL0_ZONE_MASK_OE | |
705 | ); | |
706 | } | |
707 | } | |
708 | ||
709 | static void sii8620_stop_video(struct sii8620 *ctx) | |
710 | { | |
711 | u8 uninitialized_var(val); | |
712 | ||
713 | sii8620_write_seq_static(ctx, | |
714 | REG_TPI_INTR_EN, 0, | |
715 | REG_HDCP2X_INTR0_MASK, 0, | |
716 | REG_TPI_COPP_DATA2, 0, | |
717 | REG_TPI_INTR_ST0, ~0, | |
718 | ); | |
719 | ||
720 | switch (ctx->sink_type) { | |
721 | case SINK_DVI: | |
722 | val = BIT_TPI_SC_REG_TMDS_OE_POWER_DOWN | |
723 | | BIT_TPI_SC_TPI_AV_MUTE; | |
724 | break; | |
725 | case SINK_HDMI: | |
726 | val = BIT_TPI_SC_REG_TMDS_OE_POWER_DOWN | |
727 | | BIT_TPI_SC_TPI_AV_MUTE | |
728 | | BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI; | |
729 | break; | |
730 | default: | |
731 | return; | |
732 | } | |
733 | ||
734 | sii8620_write(ctx, REG_TPI_SC, val); | |
735 | } | |
736 | ||
737 | static void sii8620_start_hdmi(struct sii8620 *ctx) | |
738 | { | |
739 | sii8620_write_seq_static(ctx, | |
740 | REG_RX_HDMI_CTRL2, VAL_RX_HDMI_CTRL2_DEFVAL | |
741 | | BIT_RX_HDMI_CTRL2_USE_AV_MUTE, | |
742 | REG_VID_OVRRD, BIT_VID_OVRRD_PP_AUTO_DISABLE | |
743 | | BIT_VID_OVRRD_M1080P_OVRRD, | |
744 | REG_VID_MODE, 0, | |
745 | REG_MHL_TOP_CTL, 0x1, | |
746 | REG_MHLTX_CTL6, 0xa0, | |
747 | REG_TPI_INPUT, VAL_TPI_FORMAT(RGB, FULL), | |
748 | REG_TPI_OUTPUT, VAL_TPI_FORMAT(RGB, FULL), | |
749 | ); | |
750 | ||
751 | sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), | |
752 | MHL_DST_LM_CLK_MODE_NORMAL | | |
753 | MHL_DST_LM_PATH_ENABLED); | |
754 | ||
755 | sii8620_set_auto_zone(ctx); | |
756 | ||
757 | sii8620_write(ctx, REG_TPI_SC, BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI); | |
758 | ||
759 | sii8620_write_buf(ctx, REG_TPI_AVI_CHSUM, ctx->avif, | |
760 | ARRAY_SIZE(ctx->avif)); | |
761 | ||
762 | sii8620_write(ctx, REG_PKT_FILTER_0, 0xa1, 0x2); | |
763 | } | |
764 | ||
765 | static void sii8620_start_video(struct sii8620 *ctx) | |
766 | { | |
bb4954c7 | 767 | if (!sii8620_is_mhl3(ctx)) |
ce6e153f AH |
768 | sii8620_stop_video(ctx); |
769 | ||
770 | switch (ctx->sink_type) { | |
771 | case SINK_HDMI: | |
772 | sii8620_start_hdmi(ctx); | |
773 | break; | |
774 | case SINK_DVI: | |
775 | default: | |
776 | break; | |
777 | } | |
778 | } | |
779 | ||
780 | static void sii8620_disable_hpd(struct sii8620 *ctx) | |
781 | { | |
782 | sii8620_setbits(ctx, REG_EDID_CTRL, BIT_EDID_CTRL_EDID_PRIME_VALID, 0); | |
783 | sii8620_write_seq_static(ctx, | |
784 | REG_HPD_CTRL, BIT_HPD_CTRL_HPD_OUT_OVR_EN, | |
785 | REG_INTR8_MASK, 0 | |
786 | ); | |
787 | } | |
788 | ||
789 | static void sii8620_enable_hpd(struct sii8620 *ctx) | |
790 | { | |
791 | sii8620_setbits(ctx, REG_TMDS_CSTAT_P3, | |
792 | BIT_TMDS_CSTAT_P3_SCDT_CLR_AVI_DIS | |
793 | | BIT_TMDS_CSTAT_P3_CLR_AVI, ~0); | |
794 | sii8620_write_seq_static(ctx, | |
795 | REG_HPD_CTRL, BIT_HPD_CTRL_HPD_OUT_OVR_EN | |
796 | | BIT_HPD_CTRL_HPD_HIGH, | |
797 | ); | |
798 | } | |
799 | ||
800 | static void sii8620_enable_gen2_write_burst(struct sii8620 *ctx) | |
801 | { | |
802 | if (ctx->gen2_write_burst) | |
803 | return; | |
804 | ||
805 | sii8620_write_seq_static(ctx, | |
806 | REG_MDT_RCV_TIMEOUT, 100, | |
807 | REG_MDT_RCV_CTRL, BIT_MDT_RCV_CTRL_MDT_RCV_EN | |
808 | ); | |
809 | ctx->gen2_write_burst = 1; | |
810 | } | |
811 | ||
812 | static void sii8620_disable_gen2_write_burst(struct sii8620 *ctx) | |
813 | { | |
814 | if (!ctx->gen2_write_burst) | |
815 | return; | |
816 | ||
817 | sii8620_write_seq_static(ctx, | |
818 | REG_MDT_XMIT_CTRL, 0, | |
819 | REG_MDT_RCV_CTRL, 0 | |
820 | ); | |
821 | ctx->gen2_write_burst = 0; | |
822 | } | |
823 | ||
824 | static void sii8620_start_gen2_write_burst(struct sii8620 *ctx) | |
825 | { | |
826 | sii8620_write_seq_static(ctx, | |
827 | REG_MDT_INT_1_MASK, BIT_MDT_RCV_TIMEOUT | |
828 | | BIT_MDT_RCV_SM_ABORT_PKT_RCVD | BIT_MDT_RCV_SM_ERROR | |
829 | | BIT_MDT_XMIT_TIMEOUT | BIT_MDT_XMIT_SM_ABORT_PKT_RCVD | |
830 | | BIT_MDT_XMIT_SM_ERROR, | |
831 | REG_MDT_INT_0_MASK, BIT_MDT_XFIFO_EMPTY | |
832 | | BIT_MDT_IDLE_AFTER_HAWB_DISABLE | |
833 | | BIT_MDT_RFIFO_DATA_RDY | |
834 | ); | |
835 | sii8620_enable_gen2_write_burst(ctx); | |
836 | } | |
837 | ||
838 | static void sii8620_mhl_discover(struct sii8620 *ctx) | |
839 | { | |
840 | sii8620_write_seq_static(ctx, | |
841 | REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT | |
842 | | BIT_DISC_CTRL9_DISC_PULSE_PROCEED, | |
843 | REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_5K, VAL_PUP_20K), | |
844 | REG_CBUS_DISC_INTR0_MASK, BIT_MHL3_EST_INT | |
845 | | BIT_MHL_EST_INT | |
846 | | BIT_NOT_MHL_EST_INT | |
847 | | BIT_CBUS_MHL3_DISCON_INT | |
848 | | BIT_CBUS_MHL12_DISCON_INT | |
849 | | BIT_RGND_READY_INT, | |
850 | REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X | |
851 | | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL | |
852 | | BIT_MHL_PLL_CTL0_ZONE_MASK_OE, | |
853 | REG_MHL_DP_CTL0, BIT_MHL_DP_CTL0_DP_OE | |
854 | | BIT_MHL_DP_CTL0_TX_OE_OVR, | |
855 | REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE, | |
856 | REG_MHL_DP_CTL1, 0xA2, | |
857 | REG_MHL_DP_CTL2, 0x03, | |
858 | REG_MHL_DP_CTL3, 0x35, | |
859 | REG_MHL_DP_CTL5, 0x02, | |
860 | REG_MHL_DP_CTL6, 0x02, | |
861 | REG_MHL_DP_CTL7, 0x03, | |
862 | REG_COC_CTLC, 0xFF, | |
863 | REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | |
864 | | BIT_DPD_OSC_EN | BIT_DPD_PWRON_HSIC, | |
865 | REG_COC_INTR_MASK, BIT_COC_PLL_LOCK_STATUS_CHANGE | |
866 | | BIT_COC_CALIBRATION_DONE, | |
867 | REG_CBUS_INT_1_MASK, BIT_CBUS_MSC_ABORT_RCVD | |
868 | | BIT_CBUS_CMD_ABORT, | |
869 | REG_CBUS_INT_0_MASK, BIT_CBUS_MSC_MT_DONE | |
870 | | BIT_CBUS_HPD_CHG | |
871 | | BIT_CBUS_MSC_MR_WRITE_STAT | |
872 | | BIT_CBUS_MSC_MR_MSC_MSG | |
873 | | BIT_CBUS_MSC_MR_WRITE_BURST | |
874 | | BIT_CBUS_MSC_MR_SET_INT | |
875 | | BIT_CBUS_MSC_MT_DONE_NACK | |
876 | ); | |
877 | } | |
878 | ||
879 | static void sii8620_peer_specific_init(struct sii8620 *ctx) | |
880 | { | |
bb4954c7 | 881 | if (sii8620_is_mhl3(ctx)) |
ce6e153f AH |
882 | sii8620_write_seq_static(ctx, |
883 | REG_SYS_CTRL1, BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD, | |
884 | REG_EMSCINTRMASK1, | |
885 | BIT_EMSCINTR1_EMSC_TRAINING_COMMA_ERR | |
886 | ); | |
887 | else | |
888 | sii8620_write_seq_static(ctx, | |
889 | REG_HDCP2X_INTR0_MASK, 0x00, | |
890 | REG_EMSCINTRMASK1, 0x00, | |
891 | REG_HDCP2X_INTR0, 0xFF, | |
892 | REG_INTR1, 0xFF, | |
893 | REG_SYS_CTRL1, BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD | |
894 | | BIT_SYS_CTRL1_TX_CTRL_HDMI | |
895 | ); | |
896 | } | |
897 | ||
898 | #define SII8620_MHL_VERSION 0x32 | |
899 | #define SII8620_SCRATCHPAD_SIZE 16 | |
900 | #define SII8620_INT_STAT_SIZE 0x33 | |
901 | ||
902 | static void sii8620_set_dev_cap(struct sii8620 *ctx) | |
903 | { | |
904 | static const u8 devcap[MHL_DCAP_SIZE] = { | |
905 | [MHL_DCAP_MHL_VERSION] = SII8620_MHL_VERSION, | |
906 | [MHL_DCAP_CAT] = MHL_DCAP_CAT_SOURCE | MHL_DCAP_CAT_POWER, | |
907 | [MHL_DCAP_ADOPTER_ID_H] = 0x01, | |
908 | [MHL_DCAP_ADOPTER_ID_L] = 0x41, | |
909 | [MHL_DCAP_VID_LINK_MODE] = MHL_DCAP_VID_LINK_RGB444 | |
910 | | MHL_DCAP_VID_LINK_PPIXEL | |
911 | | MHL_DCAP_VID_LINK_16BPP, | |
912 | [MHL_DCAP_AUD_LINK_MODE] = MHL_DCAP_AUD_LINK_2CH, | |
913 | [MHL_DCAP_VIDEO_TYPE] = MHL_DCAP_VT_GRAPHICS, | |
914 | [MHL_DCAP_LOG_DEV_MAP] = MHL_DCAP_LD_GUI, | |
915 | [MHL_DCAP_BANDWIDTH] = 0x0f, | |
916 | [MHL_DCAP_FEATURE_FLAG] = MHL_DCAP_FEATURE_RCP_SUPPORT | |
917 | | MHL_DCAP_FEATURE_RAP_SUPPORT | |
918 | | MHL_DCAP_FEATURE_SP_SUPPORT, | |
919 | [MHL_DCAP_SCRATCHPAD_SIZE] = SII8620_SCRATCHPAD_SIZE, | |
920 | [MHL_DCAP_INT_STAT_SIZE] = SII8620_INT_STAT_SIZE, | |
921 | }; | |
922 | static const u8 xdcap[MHL_XDC_SIZE] = { | |
923 | [MHL_XDC_ECBUS_SPEEDS] = MHL_XDC_ECBUS_S_075 | |
924 | | MHL_XDC_ECBUS_S_8BIT, | |
925 | [MHL_XDC_TMDS_SPEEDS] = MHL_XDC_TMDS_150 | |
926 | | MHL_XDC_TMDS_300 | MHL_XDC_TMDS_600, | |
927 | [MHL_XDC_ECBUS_ROLES] = MHL_XDC_DEV_HOST, | |
928 | [MHL_XDC_LOG_DEV_MAPX] = MHL_XDC_LD_PHONE, | |
929 | }; | |
930 | ||
931 | sii8620_write_buf(ctx, REG_MHL_DEVCAP_0, devcap, ARRAY_SIZE(devcap)); | |
932 | sii8620_write_buf(ctx, REG_MHL_EXTDEVCAP_0, xdcap, ARRAY_SIZE(xdcap)); | |
933 | } | |
934 | ||
935 | static void sii8620_mhl_init(struct sii8620 *ctx) | |
936 | { | |
937 | sii8620_write_seq_static(ctx, | |
938 | REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), | |
939 | REG_CBUS_MSC_COMPAT_CTRL, | |
940 | BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN, | |
941 | ); | |
942 | ||
943 | sii8620_peer_specific_init(ctx); | |
944 | ||
945 | sii8620_disable_hpd(ctx); | |
946 | ||
947 | sii8620_write_seq_static(ctx, | |
948 | REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO, | |
949 | REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT | |
950 | | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, | |
951 | REG_TMDS0_CCTRL1, 0x90, | |
952 | REG_TMDS_CLK_EN, 0x01, | |
953 | REG_TMDS_CH_EN, 0x11, | |
954 | REG_BGR_BIAS, 0x87, | |
955 | REG_ALICE0_ZONE_CTRL, 0xE8, | |
956 | REG_ALICE0_MODE_CTRL, 0x04, | |
957 | ); | |
958 | sii8620_setbits(ctx, REG_LM_DDC, BIT_LM_DDC_SW_TPI_EN_DISABLED, 0); | |
959 | sii8620_write_seq_static(ctx, | |
960 | REG_TPI_HW_OPT3, 0x76, | |
961 | REG_TMDS_CCTRL, BIT_TMDS_CCTRL_TMDS_OE, | |
962 | REG_TPI_DTD_B2, 79, | |
963 | ); | |
964 | sii8620_set_dev_cap(ctx); | |
965 | sii8620_write_seq_static(ctx, | |
966 | REG_MDT_XMIT_TIMEOUT, 100, | |
967 | REG_MDT_XMIT_CTRL, 0x03, | |
968 | REG_MDT_XFIFO_STAT, 0x00, | |
969 | REG_MDT_RCV_TIMEOUT, 100, | |
970 | REG_CBUS_LINK_CTRL_8, 0x1D, | |
971 | ); | |
972 | ||
973 | sii8620_start_gen2_write_burst(ctx); | |
974 | sii8620_write_seq_static(ctx, | |
975 | REG_BIST_CTRL, 0x00, | |
976 | REG_COC_CTL1, 0x10, | |
977 | REG_COC_CTL2, 0x18, | |
978 | REG_COC_CTLF, 0x07, | |
979 | REG_COC_CTL11, 0xF8, | |
980 | REG_COC_CTL17, 0x61, | |
981 | REG_COC_CTL18, 0x46, | |
982 | REG_COC_CTL19, 0x15, | |
983 | REG_COC_CTL1A, 0x01, | |
984 | REG_MHL_COC_CTL3, BIT_MHL_COC_CTL3_COC_AECHO_EN, | |
985 | REG_MHL_COC_CTL4, 0x2D, | |
986 | REG_MHL_COC_CTL5, 0xF9, | |
987 | REG_MSC_HEARTBEAT_CTRL, 0x27, | |
988 | ); | |
989 | sii8620_disable_gen2_write_burst(ctx); | |
990 | ||
991 | /* currently MHL3 is not supported, so we force version to 0 */ | |
992 | sii8620_mt_write_stat(ctx, MHL_DST_REG(VERSION), 0); | |
993 | sii8620_mt_write_stat(ctx, MHL_DST_REG(CONNECTED_RDY), | |
994 | MHL_DST_CONN_DCAP_RDY | MHL_DST_CONN_XDEVCAPP_SUPP | |
995 | | MHL_DST_CONN_POW_STAT); | |
996 | sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE), MHL_INT_RC_DCAP_CHG); | |
997 | } | |
998 | ||
999 | static void sii8620_set_mode(struct sii8620 *ctx, enum sii8620_mode mode) | |
1000 | { | |
1001 | if (ctx->mode == mode) | |
1002 | return; | |
1003 | ||
1004 | ctx->mode = mode; | |
1005 | ||
1006 | switch (mode) { | |
1007 | case CM_MHL1: | |
1008 | sii8620_write_seq_static(ctx, | |
1009 | REG_CBUS_MSC_COMPAT_CTRL, 0x02, | |
1010 | REG_M3_CTRL, VAL_M3_CTRL_MHL1_2_VALUE, | |
1011 | REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | |
1012 | | BIT_DPD_OSC_EN, | |
1013 | REG_COC_INTR_MASK, 0 | |
1014 | ); | |
1015 | break; | |
1016 | case CM_MHL3: | |
dd123129 AH |
1017 | sii8620_write(ctx, REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE); |
1018 | return; | |
ce6e153f AH |
1019 | case CM_DISCONNECTED: |
1020 | break; | |
1021 | default: | |
1022 | dev_err(ctx->dev, "%s mode %d not supported\n", __func__, mode); | |
1023 | break; | |
3a81e960 | 1024 | } |
ce6e153f AH |
1025 | |
1026 | sii8620_set_auto_zone(ctx); | |
1027 | ||
1028 | if (mode != CM_MHL1) | |
1029 | return; | |
1030 | ||
1031 | sii8620_write_seq_static(ctx, | |
1032 | REG_MHL_DP_CTL0, 0xBC, | |
1033 | REG_MHL_DP_CTL1, 0xBB, | |
1034 | REG_MHL_DP_CTL3, 0x48, | |
1035 | REG_MHL_DP_CTL5, 0x39, | |
1036 | REG_MHL_DP_CTL2, 0x2A, | |
1037 | REG_MHL_DP_CTL6, 0x2A, | |
1038 | REG_MHL_DP_CTL7, 0x08 | |
1039 | ); | |
1040 | } | |
1041 | ||
1042 | static void sii8620_disconnect(struct sii8620 *ctx) | |
1043 | { | |
1044 | sii8620_disable_gen2_write_burst(ctx); | |
1045 | sii8620_stop_video(ctx); | |
1046 | msleep(50); | |
1047 | sii8620_cbus_reset(ctx); | |
1048 | sii8620_set_mode(ctx, CM_DISCONNECTED); | |
1049 | sii8620_write_seq_static(ctx, | |
1050 | REG_COC_CTL0, 0x40, | |
1051 | REG_CBUS3_CNVT, 0x84, | |
1052 | REG_COC_CTL14, 0x00, | |
1053 | REG_COC_CTL0, 0x40, | |
1054 | REG_HRXCTRL3, 0x07, | |
1055 | REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X | |
1056 | | BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL | |
1057 | | BIT_MHL_PLL_CTL0_ZONE_MASK_OE, | |
1058 | REG_MHL_DP_CTL0, BIT_MHL_DP_CTL0_DP_OE | |
1059 | | BIT_MHL_DP_CTL0_TX_OE_OVR, | |
1060 | REG_MHL_DP_CTL1, 0xBB, | |
1061 | REG_MHL_DP_CTL3, 0x48, | |
1062 | REG_MHL_DP_CTL5, 0x3F, | |
1063 | REG_MHL_DP_CTL2, 0x2F, | |
1064 | REG_MHL_DP_CTL6, 0x2A, | |
1065 | REG_MHL_DP_CTL7, 0x03 | |
1066 | ); | |
1067 | sii8620_disable_hpd(ctx); | |
1068 | sii8620_write_seq_static(ctx, | |
1069 | REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE, | |
1070 | REG_MHL_COC_CTL1, 0x07, | |
1071 | REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), | |
1072 | REG_DISC_CTRL8, 0x00, | |
1073 | REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT | |
1074 | | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, | |
1075 | REG_INT_CTRL, 0x00, | |
1076 | REG_MSC_HEARTBEAT_CTRL, 0x27, | |
1077 | REG_DISC_CTRL1, 0x25, | |
1078 | REG_CBUS_DISC_INTR0, (u8)~BIT_RGND_READY_INT, | |
1079 | REG_CBUS_DISC_INTR0_MASK, BIT_RGND_READY_INT, | |
1080 | REG_MDT_INT_1, 0xff, | |
1081 | REG_MDT_INT_1_MASK, 0x00, | |
1082 | REG_MDT_INT_0, 0xff, | |
1083 | REG_MDT_INT_0_MASK, 0x00, | |
1084 | REG_COC_INTR, 0xff, | |
1085 | REG_COC_INTR_MASK, 0x00, | |
1086 | REG_TRXINTH, 0xff, | |
1087 | REG_TRXINTMH, 0x00, | |
1088 | REG_CBUS_INT_0, 0xff, | |
1089 | REG_CBUS_INT_0_MASK, 0x00, | |
1090 | REG_CBUS_INT_1, 0xff, | |
1091 | REG_CBUS_INT_1_MASK, 0x00, | |
1092 | REG_EMSCINTR, 0xff, | |
1093 | REG_EMSCINTRMASK, 0x00, | |
1094 | REG_EMSCINTR1, 0xff, | |
1095 | REG_EMSCINTRMASK1, 0x00, | |
1096 | REG_INTR8, 0xff, | |
1097 | REG_INTR8_MASK, 0x00, | |
1098 | REG_TPI_INTR_ST0, 0xff, | |
1099 | REG_TPI_INTR_EN, 0x00, | |
1100 | REG_HDCP2X_INTR0, 0xff, | |
1101 | REG_HDCP2X_INTR0_MASK, 0x00, | |
1102 | REG_INTR9, 0xff, | |
1103 | REG_INTR9_MASK, 0x00, | |
1104 | REG_INTR3, 0xff, | |
1105 | REG_INTR3_MASK, 0x00, | |
1106 | REG_INTR5, 0xff, | |
1107 | REG_INTR5_MASK, 0x00, | |
1108 | REG_INTR2, 0xff, | |
1109 | REG_INTR2_MASK, 0x00, | |
1110 | ); | |
1111 | memset(ctx->stat, 0, sizeof(ctx->stat)); | |
1112 | memset(ctx->xstat, 0, sizeof(ctx->xstat)); | |
1113 | memset(ctx->devcap, 0, sizeof(ctx->devcap)); | |
1114 | memset(ctx->xdevcap, 0, sizeof(ctx->xdevcap)); | |
1115 | ctx->cbus_status = 0; | |
1116 | ctx->sink_type = SINK_NONE; | |
1117 | kfree(ctx->edid); | |
1118 | ctx->edid = NULL; | |
1119 | sii8620_mt_cleanup(ctx); | |
1120 | } | |
1121 | ||
1122 | static void sii8620_mhl_disconnected(struct sii8620 *ctx) | |
1123 | { | |
1124 | sii8620_write_seq_static(ctx, | |
1125 | REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K), | |
1126 | REG_CBUS_MSC_COMPAT_CTRL, | |
1127 | BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN | |
1128 | ); | |
1129 | sii8620_disconnect(ctx); | |
1130 | } | |
1131 | ||
1132 | static void sii8620_irq_disc(struct sii8620 *ctx) | |
1133 | { | |
1134 | u8 stat = sii8620_readb(ctx, REG_CBUS_DISC_INTR0); | |
1135 | ||
1136 | if (stat & VAL_CBUS_MHL_DISCON) | |
1137 | sii8620_mhl_disconnected(ctx); | |
1138 | ||
1139 | if (stat & BIT_RGND_READY_INT) { | |
1140 | u8 stat2 = sii8620_readb(ctx, REG_DISC_STAT2); | |
1141 | ||
1142 | if ((stat2 & MSK_DISC_STAT2_RGND) == VAL_RGND_1K) { | |
1143 | sii8620_mhl_discover(ctx); | |
1144 | } else { | |
1145 | sii8620_write_seq_static(ctx, | |
1146 | REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT | |
1147 | | BIT_DISC_CTRL9_NOMHL_EST | |
1148 | | BIT_DISC_CTRL9_WAKE_PULSE_BYPASS, | |
1149 | REG_CBUS_DISC_INTR0_MASK, BIT_RGND_READY_INT | |
1150 | | BIT_CBUS_MHL3_DISCON_INT | |
1151 | | BIT_CBUS_MHL12_DISCON_INT | |
1152 | | BIT_NOT_MHL_EST_INT | |
1153 | ); | |
1154 | } | |
1155 | } | |
1156 | if (stat & BIT_MHL_EST_INT) | |
1157 | sii8620_mhl_init(ctx); | |
1158 | ||
1159 | sii8620_write(ctx, REG_CBUS_DISC_INTR0, stat); | |
1160 | } | |
1161 | ||
1162 | static void sii8620_irq_g2wb(struct sii8620 *ctx) | |
1163 | { | |
1164 | u8 stat = sii8620_readb(ctx, REG_MDT_INT_0); | |
1165 | ||
1166 | if (stat & BIT_MDT_IDLE_AFTER_HAWB_DISABLE) | |
1167 | dev_dbg(ctx->dev, "HAWB idle\n"); | |
1168 | ||
1169 | sii8620_write(ctx, REG_MDT_INT_0, stat); | |
1170 | } | |
1171 | ||
1172 | static void sii8620_status_changed_dcap(struct sii8620 *ctx) | |
1173 | { | |
1174 | if (ctx->stat[MHL_DST_CONNECTED_RDY] & MHL_DST_CONN_DCAP_RDY) { | |
1175 | sii8620_set_mode(ctx, CM_MHL1); | |
1176 | sii8620_peer_specific_init(ctx); | |
1177 | sii8620_write(ctx, REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE | |
1178 | | BIT_INTR9_EDID_DONE | BIT_INTR9_EDID_ERROR); | |
1179 | } | |
1180 | } | |
1181 | ||
1182 | static void sii8620_status_changed_path(struct sii8620 *ctx) | |
1183 | { | |
1184 | if (ctx->stat[MHL_DST_LINK_MODE] & MHL_DST_LM_PATH_ENABLED) { | |
1185 | sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), | |
1186 | MHL_DST_LM_CLK_MODE_NORMAL | |
1187 | | MHL_DST_LM_PATH_ENABLED); | |
1188 | sii8620_mt_read_devcap(ctx, false); | |
1189 | } else { | |
1190 | sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), | |
1191 | MHL_DST_LM_CLK_MODE_NORMAL); | |
1192 | } | |
1193 | } | |
1194 | ||
1195 | static void sii8620_msc_mr_write_stat(struct sii8620 *ctx) | |
1196 | { | |
1197 | u8 st[MHL_DST_SIZE], xst[MHL_XDS_SIZE]; | |
1198 | ||
1199 | sii8620_read_buf(ctx, REG_MHL_STAT_0, st, MHL_DST_SIZE); | |
1200 | sii8620_read_buf(ctx, REG_MHL_EXTSTAT_0, xst, MHL_XDS_SIZE); | |
1201 | ||
1202 | sii8620_update_array(ctx->stat, st, MHL_DST_SIZE); | |
1203 | sii8620_update_array(ctx->xstat, xst, MHL_XDS_SIZE); | |
1204 | ||
1205 | if (st[MHL_DST_CONNECTED_RDY] & MHL_DST_CONN_DCAP_RDY) | |
1206 | sii8620_status_changed_dcap(ctx); | |
1207 | ||
1208 | if (st[MHL_DST_LINK_MODE] & MHL_DST_LM_PATH_ENABLED) | |
1209 | sii8620_status_changed_path(ctx); | |
1210 | } | |
1211 | ||
1212 | static void sii8620_msc_mr_set_int(struct sii8620 *ctx) | |
1213 | { | |
1214 | u8 ints[MHL_INT_SIZE]; | |
1215 | ||
1216 | sii8620_read_buf(ctx, REG_MHL_INT_0, ints, MHL_INT_SIZE); | |
1217 | sii8620_write_buf(ctx, REG_MHL_INT_0, ints, MHL_INT_SIZE); | |
1218 | } | |
1219 | ||
1220 | static struct sii8620_mt_msg *sii8620_msc_msg_first(struct sii8620 *ctx) | |
1221 | { | |
1222 | struct device *dev = ctx->dev; | |
1223 | ||
1224 | if (list_empty(&ctx->mt_queue)) { | |
1225 | dev_err(dev, "unexpected MSC MT response\n"); | |
1226 | return NULL; | |
1227 | } | |
1228 | ||
1229 | return list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, node); | |
1230 | } | |
1231 | ||
1232 | static void sii8620_msc_mt_done(struct sii8620 *ctx) | |
1233 | { | |
1234 | struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx); | |
1235 | ||
1236 | if (!msg) | |
1237 | return; | |
1238 | ||
1239 | msg->ret = sii8620_readb(ctx, REG_MSC_MT_RCVD_DATA0); | |
1240 | ctx->mt_state = MT_STATE_DONE; | |
1241 | } | |
1242 | ||
1243 | static void sii8620_msc_mr_msc_msg(struct sii8620 *ctx) | |
1244 | { | |
1245 | struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx); | |
1246 | u8 buf[2]; | |
1247 | ||
1248 | if (!msg) | |
1249 | return; | |
1250 | ||
1251 | sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2); | |
1252 | ||
1253 | switch (buf[0]) { | |
1254 | case MHL_MSC_MSG_RAPK: | |
1255 | msg->ret = buf[1]; | |
1256 | ctx->mt_state = MT_STATE_DONE; | |
1257 | break; | |
1258 | default: | |
1259 | dev_err(ctx->dev, "%s message type %d,%d not supported", | |
1260 | __func__, buf[0], buf[1]); | |
1261 | } | |
1262 | } | |
1263 | ||
1264 | static void sii8620_irq_msc(struct sii8620 *ctx) | |
1265 | { | |
1266 | u8 stat = sii8620_readb(ctx, REG_CBUS_INT_0); | |
1267 | ||
1268 | if (stat & ~BIT_CBUS_HPD_CHG) | |
1269 | sii8620_write(ctx, REG_CBUS_INT_0, stat & ~BIT_CBUS_HPD_CHG); | |
1270 | ||
1271 | if (stat & BIT_CBUS_HPD_CHG) { | |
1272 | u8 cbus_stat = sii8620_readb(ctx, REG_CBUS_STATUS); | |
1273 | ||
1274 | if ((cbus_stat ^ ctx->cbus_status) & BIT_CBUS_STATUS_CBUS_HPD) { | |
1275 | sii8620_write(ctx, REG_CBUS_INT_0, BIT_CBUS_HPD_CHG); | |
1276 | } else { | |
1277 | stat ^= BIT_CBUS_STATUS_CBUS_HPD; | |
1278 | cbus_stat ^= BIT_CBUS_STATUS_CBUS_HPD; | |
1279 | } | |
1280 | ctx->cbus_status = cbus_stat; | |
1281 | } | |
1282 | ||
1283 | if (stat & BIT_CBUS_MSC_MR_WRITE_STAT) | |
1284 | sii8620_msc_mr_write_stat(ctx); | |
1285 | ||
1286 | if (stat & BIT_CBUS_MSC_MR_SET_INT) | |
1287 | sii8620_msc_mr_set_int(ctx); | |
1288 | ||
1289 | if (stat & BIT_CBUS_MSC_MT_DONE) | |
1290 | sii8620_msc_mt_done(ctx); | |
1291 | ||
1292 | if (stat & BIT_CBUS_MSC_MR_MSC_MSG) | |
1293 | sii8620_msc_mr_msc_msg(ctx); | |
1294 | } | |
1295 | ||
1296 | static void sii8620_irq_coc(struct sii8620 *ctx) | |
1297 | { | |
1298 | u8 stat = sii8620_readb(ctx, REG_COC_INTR); | |
1299 | ||
1300 | sii8620_write(ctx, REG_COC_INTR, stat); | |
1301 | } | |
1302 | ||
1303 | static void sii8620_irq_merr(struct sii8620 *ctx) | |
1304 | { | |
1305 | u8 stat = sii8620_readb(ctx, REG_CBUS_INT_1); | |
1306 | ||
1307 | sii8620_write(ctx, REG_CBUS_INT_1, stat); | |
1308 | } | |
1309 | ||
1310 | static void sii8620_irq_edid(struct sii8620 *ctx) | |
1311 | { | |
1312 | u8 stat = sii8620_readb(ctx, REG_INTR9); | |
1313 | ||
1314 | sii8620_write(ctx, REG_INTR9, stat); | |
1315 | ||
1316 | if (stat & BIT_INTR9_DEVCAP_DONE) | |
1317 | ctx->mt_state = MT_STATE_DONE; | |
1318 | } | |
1319 | ||
1320 | static void sii8620_scdt_high(struct sii8620 *ctx) | |
1321 | { | |
1322 | sii8620_write_seq_static(ctx, | |
1323 | REG_INTR8_MASK, BIT_CEA_NEW_AVI | BIT_CEA_NEW_VSI, | |
1324 | REG_TPI_SC, BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI, | |
1325 | ); | |
1326 | } | |
1327 | ||
1328 | static void sii8620_scdt_low(struct sii8620 *ctx) | |
1329 | { | |
1330 | sii8620_write(ctx, REG_TMDS_CSTAT_P3, | |
1331 | BIT_TMDS_CSTAT_P3_SCDT_CLR_AVI_DIS | | |
1332 | BIT_TMDS_CSTAT_P3_CLR_AVI); | |
1333 | ||
1334 | sii8620_stop_video(ctx); | |
1335 | ||
1336 | sii8620_write(ctx, REG_INTR8_MASK, 0); | |
1337 | } | |
1338 | ||
1339 | static void sii8620_irq_scdt(struct sii8620 *ctx) | |
1340 | { | |
1341 | u8 stat = sii8620_readb(ctx, REG_INTR5); | |
1342 | ||
1343 | if (stat & BIT_INTR_SCDT_CHANGE) { | |
1344 | u8 cstat = sii8620_readb(ctx, REG_TMDS_CSTAT_P3); | |
1345 | ||
1346 | if (cstat & BIT_TMDS_CSTAT_P3_SCDT) | |
1347 | sii8620_scdt_high(ctx); | |
1348 | else | |
1349 | sii8620_scdt_low(ctx); | |
1350 | } | |
1351 | ||
1352 | sii8620_write(ctx, REG_INTR5, stat); | |
1353 | } | |
1354 | ||
1355 | static void sii8620_new_vsi(struct sii8620 *ctx) | |
1356 | { | |
1357 | u8 vsif[11]; | |
1358 | ||
1359 | sii8620_write(ctx, REG_RX_HDMI_CTRL2, | |
1360 | VAL_RX_HDMI_CTRL2_DEFVAL | | |
1361 | BIT_RX_HDMI_CTRL2_VSI_MON_SEL_VSI); | |
1362 | sii8620_read_buf(ctx, REG_RX_HDMI_MON_PKT_HEADER1, vsif, | |
1363 | ARRAY_SIZE(vsif)); | |
1364 | } | |
1365 | ||
1366 | static void sii8620_new_avi(struct sii8620 *ctx) | |
1367 | { | |
1368 | sii8620_write(ctx, REG_RX_HDMI_CTRL2, VAL_RX_HDMI_CTRL2_DEFVAL); | |
1369 | sii8620_read_buf(ctx, REG_RX_HDMI_MON_PKT_HEADER1, ctx->avif, | |
1370 | ARRAY_SIZE(ctx->avif)); | |
1371 | } | |
1372 | ||
1373 | static void sii8620_irq_infr(struct sii8620 *ctx) | |
1374 | { | |
1375 | u8 stat = sii8620_readb(ctx, REG_INTR8) | |
1376 | & (BIT_CEA_NEW_VSI | BIT_CEA_NEW_AVI); | |
1377 | ||
1378 | sii8620_write(ctx, REG_INTR8, stat); | |
1379 | ||
1380 | if (stat & BIT_CEA_NEW_VSI) | |
1381 | sii8620_new_vsi(ctx); | |
1382 | ||
1383 | if (stat & BIT_CEA_NEW_AVI) | |
1384 | sii8620_new_avi(ctx); | |
1385 | ||
1386 | if (stat & (BIT_CEA_NEW_VSI | BIT_CEA_NEW_AVI)) | |
1387 | sii8620_start_video(ctx); | |
1388 | } | |
1389 | ||
1390 | /* endian agnostic, non-volatile version of test_bit */ | |
1391 | static bool sii8620_test_bit(unsigned int nr, const u8 *addr) | |
1392 | { | |
1393 | return 1 & (addr[nr / BITS_PER_BYTE] >> (nr % BITS_PER_BYTE)); | |
1394 | } | |
1395 | ||
1396 | static irqreturn_t sii8620_irq_thread(int irq, void *data) | |
1397 | { | |
1398 | static const struct { | |
1399 | int bit; | |
1400 | void (*handler)(struct sii8620 *ctx); | |
1401 | } irq_vec[] = { | |
1402 | { BIT_FAST_INTR_STAT_DISC, sii8620_irq_disc }, | |
1403 | { BIT_FAST_INTR_STAT_G2WB, sii8620_irq_g2wb }, | |
1404 | { BIT_FAST_INTR_STAT_COC, sii8620_irq_coc }, | |
1405 | { BIT_FAST_INTR_STAT_MSC, sii8620_irq_msc }, | |
1406 | { BIT_FAST_INTR_STAT_MERR, sii8620_irq_merr }, | |
1407 | { BIT_FAST_INTR_STAT_EDID, sii8620_irq_edid }, | |
1408 | { BIT_FAST_INTR_STAT_SCDT, sii8620_irq_scdt }, | |
1409 | { BIT_FAST_INTR_STAT_INFR, sii8620_irq_infr }, | |
1410 | }; | |
1411 | struct sii8620 *ctx = data; | |
1412 | u8 stats[LEN_FAST_INTR_STAT]; | |
1413 | int i, ret; | |
1414 | ||
1415 | mutex_lock(&ctx->lock); | |
1416 | ||
1417 | sii8620_read_buf(ctx, REG_FAST_INTR_STAT, stats, ARRAY_SIZE(stats)); | |
1418 | for (i = 0; i < ARRAY_SIZE(irq_vec); ++i) | |
1419 | if (sii8620_test_bit(irq_vec[i].bit, stats)) | |
1420 | irq_vec[i].handler(ctx); | |
1421 | ||
1422 | sii8620_mt_work(ctx); | |
1423 | ||
1424 | ret = sii8620_clear_error(ctx); | |
1425 | if (ret) { | |
1426 | dev_err(ctx->dev, "Error during IRQ handling, %d.\n", ret); | |
1427 | sii8620_mhl_disconnected(ctx); | |
1428 | } | |
1429 | mutex_unlock(&ctx->lock); | |
1430 | ||
1431 | return IRQ_HANDLED; | |
1432 | } | |
1433 | ||
1434 | static void sii8620_cable_in(struct sii8620 *ctx) | |
1435 | { | |
1436 | struct device *dev = ctx->dev; | |
1437 | u8 ver[5]; | |
1438 | int ret; | |
1439 | ||
1440 | ret = sii8620_hw_on(ctx); | |
1441 | if (ret) { | |
1442 | dev_err(dev, "Error powering on, %d.\n", ret); | |
1443 | return; | |
1444 | } | |
1445 | sii8620_hw_reset(ctx); | |
1446 | ||
1447 | sii8620_read_buf(ctx, REG_VND_IDL, ver, ARRAY_SIZE(ver)); | |
1448 | ret = sii8620_clear_error(ctx); | |
1449 | if (ret) { | |
1450 | dev_err(dev, "Error accessing I2C bus, %d.\n", ret); | |
1451 | return; | |
1452 | } | |
1453 | ||
1454 | dev_info(dev, "ChipID %02x%02x:%02x%02x rev %02x.\n", ver[1], ver[0], | |
1455 | ver[3], ver[2], ver[4]); | |
1456 | ||
1457 | sii8620_write(ctx, REG_DPD, | |
1458 | BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | BIT_DPD_OSC_EN); | |
1459 | ||
1460 | sii8620_xtal_set_rate(ctx); | |
1461 | sii8620_disconnect(ctx); | |
1462 | ||
1463 | sii8620_write_seq_static(ctx, | |
1464 | REG_MHL_CBUS_CTL0, VAL_MHL_CBUS_CTL0_CBUS_DRV_SEL_STRONG | |
1465 | | VAL_MHL_CBUS_CTL0_CBUS_RGND_VBIAS_734, | |
1466 | REG_MHL_CBUS_CTL1, VAL_MHL_CBUS_CTL1_1115_OHM, | |
1467 | REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12 | BIT_DPD_OSC_EN, | |
1468 | ); | |
1469 | ||
1470 | ret = sii8620_clear_error(ctx); | |
1471 | if (ret) { | |
1472 | dev_err(dev, "Error accessing I2C bus, %d.\n", ret); | |
1473 | return; | |
1474 | } | |
1475 | ||
1476 | enable_irq(to_i2c_client(ctx->dev)->irq); | |
1477 | } | |
1478 | ||
1479 | static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge) | |
1480 | { | |
1481 | return container_of(bridge, struct sii8620, bridge); | |
1482 | } | |
1483 | ||
1484 | static bool sii8620_mode_fixup(struct drm_bridge *bridge, | |
1485 | const struct drm_display_mode *mode, | |
1486 | struct drm_display_mode *adjusted_mode) | |
1487 | { | |
1488 | struct sii8620 *ctx = bridge_to_sii8620(bridge); | |
1489 | bool ret = false; | |
1490 | int max_clock = 74250; | |
1491 | ||
1492 | mutex_lock(&ctx->lock); | |
1493 | ||
1494 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) | |
1495 | goto out; | |
1496 | ||
1497 | if (ctx->devcap[MHL_DCAP_VID_LINK_MODE] & MHL_DCAP_VID_LINK_PPIXEL) | |
1498 | max_clock = 300000; | |
1499 | ||
1500 | ret = mode->clock <= max_clock; | |
1501 | ||
1502 | out: | |
1503 | mutex_unlock(&ctx->lock); | |
1504 | ||
1505 | return ret; | |
1506 | } | |
1507 | ||
1508 | static const struct drm_bridge_funcs sii8620_bridge_funcs = { | |
1509 | .mode_fixup = sii8620_mode_fixup, | |
1510 | }; | |
1511 | ||
1512 | static int sii8620_probe(struct i2c_client *client, | |
1513 | const struct i2c_device_id *id) | |
1514 | { | |
1515 | struct device *dev = &client->dev; | |
1516 | struct sii8620 *ctx; | |
1517 | int ret; | |
1518 | ||
1519 | ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); | |
1520 | if (!ctx) | |
1521 | return -ENOMEM; | |
1522 | ||
1523 | ctx->dev = dev; | |
1524 | mutex_init(&ctx->lock); | |
1525 | INIT_LIST_HEAD(&ctx->mt_queue); | |
1526 | ||
1527 | ctx->clk_xtal = devm_clk_get(dev, "xtal"); | |
1528 | if (IS_ERR(ctx->clk_xtal)) { | |
1529 | dev_err(dev, "failed to get xtal clock from DT\n"); | |
1530 | return PTR_ERR(ctx->clk_xtal); | |
1531 | } | |
1532 | ||
1533 | if (!client->irq) { | |
1534 | dev_err(dev, "no irq provided\n"); | |
1535 | return -EINVAL; | |
1536 | } | |
1537 | irq_set_status_flags(client->irq, IRQ_NOAUTOEN); | |
1538 | ret = devm_request_threaded_irq(dev, client->irq, NULL, | |
1539 | sii8620_irq_thread, | |
1540 | IRQF_TRIGGER_HIGH | IRQF_ONESHOT, | |
1541 | "sii8620", ctx); | |
1542 | ||
1543 | ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); | |
1544 | if (IS_ERR(ctx->gpio_reset)) { | |
1545 | dev_err(dev, "failed to get reset gpio from DT\n"); | |
1546 | return PTR_ERR(ctx->gpio_reset); | |
1547 | } | |
1548 | ||
1549 | ctx->supplies[0].supply = "cvcc10"; | |
1550 | ctx->supplies[1].supply = "iovcc18"; | |
1551 | ret = devm_regulator_bulk_get(dev, 2, ctx->supplies); | |
1552 | if (ret) | |
1553 | return ret; | |
1554 | ||
1555 | i2c_set_clientdata(client, ctx); | |
1556 | ||
1557 | ctx->bridge.funcs = &sii8620_bridge_funcs; | |
1558 | ctx->bridge.of_node = dev->of_node; | |
1559 | drm_bridge_add(&ctx->bridge); | |
1560 | ||
1561 | sii8620_cable_in(ctx); | |
1562 | ||
1563 | return 0; | |
1564 | } | |
1565 | ||
1566 | static int sii8620_remove(struct i2c_client *client) | |
1567 | { | |
1568 | struct sii8620 *ctx = i2c_get_clientdata(client); | |
1569 | ||
1570 | disable_irq(to_i2c_client(ctx->dev)->irq); | |
1571 | drm_bridge_remove(&ctx->bridge); | |
1572 | sii8620_hw_off(ctx); | |
1573 | ||
1574 | return 0; | |
1575 | } | |
1576 | ||
1577 | static const struct of_device_id sii8620_dt_match[] = { | |
1578 | { .compatible = "sil,sii8620" }, | |
1579 | { }, | |
1580 | }; | |
1581 | MODULE_DEVICE_TABLE(of, sii8620_dt_match); | |
1582 | ||
1583 | static const struct i2c_device_id sii8620_id[] = { | |
1584 | { "sii8620", 0 }, | |
1585 | { }, | |
1586 | }; | |
1587 | ||
1588 | MODULE_DEVICE_TABLE(i2c, sii8620_id); | |
1589 | static struct i2c_driver sii8620_driver = { | |
1590 | .driver = { | |
1591 | .name = "sii8620", | |
ce6e153f AH |
1592 | .of_match_table = of_match_ptr(sii8620_dt_match), |
1593 | }, | |
1594 | .probe = sii8620_probe, | |
1595 | .remove = sii8620_remove, | |
1596 | .id_table = sii8620_id, | |
1597 | }; | |
1598 | ||
1599 | module_i2c_driver(sii8620_driver); | |
1600 | MODULE_LICENSE("GPL v2"); |