aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonathan Liu <net147@gmail.com>2017-07-02 03:27:10 -0400
committerMaxime Ripard <maxime.ripard@free-electrons.com>2017-07-17 02:21:39 -0400
commitf0a3dd33ba685bc50f78455aec832ebcc129a687 (patch)
treefaf350bde9ed1d8728981dd7f066f0631c1cfe2a
parent934d1431929bb5ecde68fb38fe4b2a6f4ab0fba6 (diff)
drm/sun4i: hdmi: Implement I2C adapter for A10s DDC bus
The documentation for drm_do_get_edid in drivers/gpu/drm/drm_edid.c states: "As in the general case the DDC bus is accessible by the kernel at the I2C level, drivers must make all reasonable efforts to expose it as an I2C adapter and use drm_get_edid() instead of abusing this function." Exposing the DDC bus as an I2C adapter is more beneficial as it can be used for purposes other than reading the EDID such as modifying the EDID or using the HDMI DDC pins as an I2C bus through the I2C dev interface from userspace (e.g. i2c-tools). Implement this for A10s. Signed-off-by: Jonathan Liu <net147@gmail.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
-rw-r--r--drivers/gpu/drm/sun4i/Makefile1
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_hdmi.h24
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c101
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c220
4 files changed, 256 insertions, 90 deletions
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index e29fd3a2ba9c..43c753cafc88 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -2,6 +2,7 @@ sun4i-drm-y += sun4i_drv.o
2sun4i-drm-y += sun4i_framebuffer.o 2sun4i-drm-y += sun4i_framebuffer.o
3 3
4sun4i-drm-hdmi-y += sun4i_hdmi_enc.o 4sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
5sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o
5sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o 6sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
6sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o 7sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
7 8
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
index 2f2f2ff1ea63..0957ff2076ac 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -96,6 +96,7 @@
96#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31) 96#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31)
97#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30) 97#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30)
98#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8) 98#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8)
99#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE (1 << 8)
99#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8) 100#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8)
100#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) 101#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0)
101 102
@@ -105,14 +106,34 @@
105#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) 106#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8)
106#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff) 107#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff)
107 108
109#define SUN4I_HDMI_DDC_INT_STATUS_REG 0x50c
110#define SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION BIT(7)
111#define SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW BIT(6)
112#define SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW BIT(5)
113#define SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST BIT(4)
114#define SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR BIT(3)
115#define SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR BIT(2)
116#define SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR BIT(1)
117#define SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE BIT(0)
118
108#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510 119#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510
109#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) 120#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31)
121#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) (((n) & 0xf) << 4)
122#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK GENMASK(7, 4)
123#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX (BIT(4) - 1)
124#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) ((n) & 0xf)
125#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK GENMASK(3, 0)
126#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MAX (BIT(4) - 1)
110 127
111#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518 128#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518
129
112#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c 130#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c
131#define SUN4I_HDMI_DDC_BYTE_COUNT_MAX (BIT(10) - 1)
113 132
114#define SUN4I_HDMI_DDC_CMD_REG 0x520 133#define SUN4I_HDMI_DDC_CMD_REG 0x520
115#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6 134#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6
135#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 5
136#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 3
116 137
117#define SUN4I_HDMI_DDC_CLK_REG 0x528 138#define SUN4I_HDMI_DDC_CLK_REG 0x528
118#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3) 139#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3)
@@ -146,6 +167,8 @@ struct sun4i_hdmi {
146 struct clk *ddc_clk; 167 struct clk *ddc_clk;
147 struct clk *tmds_clk; 168 struct clk *tmds_clk;
148 169
170 struct i2c_adapter *i2c;
171
149 struct sun4i_drv *drv; 172 struct sun4i_drv *drv;
150 173
151 bool hdmi_monitor; 174 bool hdmi_monitor;
@@ -153,5 +176,6 @@ struct sun4i_hdmi {
153 176
154int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); 177int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
155int sun4i_tmds_create(struct sun4i_hdmi *hdmi); 178int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
179int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi);
156 180
157#endif /* _SUN4I_HDMI_H_ */ 181#endif /* _SUN4I_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index d3398f6250ef..b74607feb35c 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -29,8 +29,6 @@
29#include "sun4i_hdmi.h" 29#include "sun4i_hdmi.h"
30#include "sun4i_tcon.h" 30#include "sun4i_tcon.h"
31 31
32#define DDC_SEGMENT_ADDR 0x30
33
34static inline struct sun4i_hdmi * 32static inline struct sun4i_hdmi *
35drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) 33drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
36{ 34{
@@ -184,93 +182,13 @@ static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
184 .destroy = drm_encoder_cleanup, 182 .destroy = drm_encoder_cleanup,
185}; 183};
186 184
187static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
188 unsigned int blk, unsigned int offset,
189 u8 *buf, unsigned int count)
190{
191 unsigned long reg;
192 int i;
193
194 reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
195 reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
196 writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
197 hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
198
199 writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
200 SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
201 SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
202 SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
203 hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
204
205 reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
206 writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
207 hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
208
209 writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
210 writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
211 hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
212
213 reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
214 writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
215 hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
216
217 if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
218 !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
219 100, 100000))
220 return -EIO;
221
222 for (i = 0; i < count; i++)
223 buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
224
225 return 0;
226}
227
228static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
229 size_t length)
230{
231 struct sun4i_hdmi *hdmi = data;
232 int retry = 2, i;
233
234 do {
235 for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
236 unsigned char offset = blk * EDID_LENGTH + i;
237 unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
238 length - i);
239 int ret;
240
241 ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
242 buf + i, count);
243 if (ret)
244 return ret;
245 }
246 } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
247
248 return 0;
249}
250
251static int sun4i_hdmi_get_modes(struct drm_connector *connector) 185static int sun4i_hdmi_get_modes(struct drm_connector *connector)
252{ 186{
253 struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); 187 struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
254 unsigned long reg;
255 struct edid *edid; 188 struct edid *edid;
256 int ret; 189 int ret;
257 190
258 /* Reset i2c controller */ 191 edid = drm_get_edid(connector, hdmi->i2c);
259 writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
260 hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
261 if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
262 !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
263 100, 2000))
264 return -EIO;
265
266 writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
267 SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
268 hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
269
270 clk_prepare_enable(hdmi->ddc_clk);
271 clk_set_rate(hdmi->ddc_clk, 100000);
272
273 edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
274 if (!edid) 192 if (!edid)
275 return 0; 193 return 0;
276 194
@@ -282,8 +200,6 @@ static int sun4i_hdmi_get_modes(struct drm_connector *connector)
282 ret = drm_add_edid_modes(connector, edid); 200 ret = drm_add_edid_modes(connector, edid);
283 kfree(edid); 201 kfree(edid);
284 202
285 clk_disable_unprepare(hdmi->ddc_clk);
286
287 return ret; 203 return ret;
288} 204}
289 205
@@ -407,9 +323,9 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
407 SUN4I_HDMI_PLL_CTRL_PLL_EN; 323 SUN4I_HDMI_PLL_CTRL_PLL_EN;
408 writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); 324 writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
409 325
410 ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk); 326 ret = sun4i_hdmi_i2c_create(dev, hdmi);
411 if (ret) { 327 if (ret) {
412 dev_err(dev, "Couldn't create the DDC clock\n"); 328 dev_err(dev, "Couldn't create the HDMI I2C adapter\n");
413 return ret; 329 return ret;
414 } 330 }
415 331
@@ -422,13 +338,15 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
422 NULL); 338 NULL);
423 if (ret) { 339 if (ret) {
424 dev_err(dev, "Couldn't initialise the HDMI encoder\n"); 340 dev_err(dev, "Couldn't initialise the HDMI encoder\n");
425 return ret; 341 goto err_del_i2c_adapter;
426 } 342 }
427 343
428 hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm, 344 hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
429 dev->of_node); 345 dev->of_node);
430 if (!hdmi->encoder.possible_crtcs) 346 if (!hdmi->encoder.possible_crtcs) {
431 return -EPROBE_DEFER; 347 ret = -EPROBE_DEFER;
348 goto err_del_i2c_adapter;
349 }
432 350
433 drm_connector_helper_add(&hdmi->connector, 351 drm_connector_helper_add(&hdmi->connector,
434 &sun4i_hdmi_connector_helper_funcs); 352 &sun4i_hdmi_connector_helper_funcs);
@@ -451,6 +369,8 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
451 369
452err_cleanup_connector: 370err_cleanup_connector:
453 drm_encoder_cleanup(&hdmi->encoder); 371 drm_encoder_cleanup(&hdmi->encoder);
372err_del_i2c_adapter:
373 i2c_del_adapter(hdmi->i2c);
454 return ret; 374 return ret;
455} 375}
456 376
@@ -461,6 +381,7 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
461 381
462 drm_connector_cleanup(&hdmi->connector); 382 drm_connector_cleanup(&hdmi->connector);
463 drm_encoder_cleanup(&hdmi->encoder); 383 drm_encoder_cleanup(&hdmi->encoder);
384 i2c_del_adapter(hdmi->i2c);
464} 385}
465 386
466static const struct component_ops sun4i_hdmi_ops = { 387static const struct component_ops sun4i_hdmi_ops = {
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c
new file mode 100644
index 000000000000..2e42d09ab42e
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c
@@ -0,0 +1,220 @@
1/*
2 * Copyright (C) 2016 Maxime Ripard <maxime.ripard@free-electrons.com>
3 * Copyright (C) 2017 Jonathan Liu <net147@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 */
10
11#include <linux/clk.h>
12#include <linux/i2c.h>
13#include <linux/iopoll.h>
14
15#include "sun4i_hdmi.h"
16
17#define SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK ( \
18 SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION | \
19 SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW | \
20 SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW | \
21 SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR | \
22 SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR | \
23 SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR \
24)
25
26/* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */
27#define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX
28/* FIFO request bit is set when FIFO level is below TX_THRESHOLD during write */
29#define TX_THRESHOLD 1
30
31static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read)
32{
33 /*
34 * 1 byte takes 9 clock cycles (8 bits + 1 ACK) = 90 us for 100 kHz
35 * clock. As clock rate is fixed, just round it up to 100 us.
36 */
37 const unsigned long byte_time_ns = 100;
38 const u32 mask = SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
39 SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
40 SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE;
41 u32 reg;
42
43 /* Limit transfer length by FIFO threshold */
44 len = min_t(int, len, read ? (RX_THRESHOLD + 1) :
45 (SUN4I_HDMI_DDC_FIFO_SIZE - TX_THRESHOLD + 1));
46
47 /* Wait until error, FIFO request bit set or transfer complete */
48 if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG, reg,
49 reg & mask, len * byte_time_ns, 100000))
50 return -ETIMEDOUT;
51
52 if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK)
53 return -EIO;
54
55 if (read)
56 readsb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
57 else
58 writesb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
59
60 /* Clear FIFO request bit */
61 writel(SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST,
62 hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
63
64 return len;
65}
66
67static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg)
68{
69 int i, len;
70 u32 reg;
71
72 /* Set FIFO direction */
73 reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
74 reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
75 reg |= (msg->flags & I2C_M_RD) ?
76 SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
77 SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
78 writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
79
80 /* Set I2C address */
81 writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr),
82 hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
83
84 /* Set FIFO RX/TX thresholds and clear FIFO */
85 reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
86 reg |= SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR;
87 reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK;
88 reg |= SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(RX_THRESHOLD);
89 reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK;
90 reg |= SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(TX_THRESHOLD);
91 writel(reg, hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
92 if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG,
93 reg,
94 !(reg & SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR),
95 100, 2000))
96 return -EIO;
97
98 /* Set transfer length */
99 writel(msg->len, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
100
101 /* Set command */
102 writel(msg->flags & I2C_M_RD ?
103 SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
104 SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE,
105 hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
106
107 /* Clear interrupt status bits */
108 writel(SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
109 SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
110 SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE,
111 hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
112
113 /* Start command */
114 reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
115 writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
116 hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
117
118 /* Transfer bytes */
119 for (i = 0; i < msg->len; i += len) {
120 len = fifo_transfer(hdmi, msg->buf + i, msg->len - i,
121 msg->flags & I2C_M_RD);
122 if (len <= 0)
123 return len;
124 }
125
126 /* Wait for command to finish */
127 if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG,
128 reg,
129 !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
130 100, 100000))
131 return -EIO;
132
133 /* Check for errors */
134 reg = readl(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
135 if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) ||
136 !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) {
137 return -EIO;
138 }
139
140 return 0;
141}
142
143static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap,
144 struct i2c_msg *msgs, int num)
145{
146 struct sun4i_hdmi *hdmi = i2c_get_adapdata(adap);
147 u32 reg;
148 int err, i, ret = num;
149
150 for (i = 0; i < num; i++) {
151 if (!msgs[i].len)
152 return -EINVAL;
153 if (msgs[i].len > SUN4I_HDMI_DDC_BYTE_COUNT_MAX)
154 return -EINVAL;
155 }
156
157 /* Reset I2C controller */
158 writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
159 hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
160 if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
161 !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
162 100, 2000))
163 return -EIO;
164
165 writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
166 SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
167 hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
168
169 clk_prepare_enable(hdmi->ddc_clk);
170 clk_set_rate(hdmi->ddc_clk, 100000);
171
172 for (i = 0; i < num; i++) {
173 err = xfer_msg(hdmi, &msgs[i]);
174 if (err) {
175 ret = err;
176 break;
177 }
178 }
179
180 clk_disable_unprepare(hdmi->ddc_clk);
181 return ret;
182}
183
184static u32 sun4i_hdmi_i2c_func(struct i2c_adapter *adap)
185{
186 return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
187}
188
189static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = {
190 .master_xfer = sun4i_hdmi_i2c_xfer,
191 .functionality = sun4i_hdmi_i2c_func,
192};
193
194int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi)
195{
196 struct i2c_adapter *adap;
197 int ret = 0;
198
199 ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
200 if (ret)
201 return ret;
202
203 adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
204 if (!adap)
205 return -ENOMEM;
206
207 adap->owner = THIS_MODULE;
208 adap->class = I2C_CLASS_DDC;
209 adap->algo = &sun4i_hdmi_i2c_algorithm;
210 strlcpy(adap->name, "sun4i_hdmi_i2c adapter", sizeof(adap->name));
211 i2c_set_adapdata(adap, hdmi);
212
213 ret = i2c_add_adapter(adap);
214 if (ret)
215 return ret;
216
217 hdmi->i2c = adap;
218
219 return ret;
220}