1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016 Maxime Ripard |
4 | * |
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/component.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/iopoll.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/reset.h> |
18 | |
19 | #include <drm/drm_atomic.h> |
20 | #include <drm/drm_atomic_helper.h> |
21 | #include <drm/drm_edid.h> |
22 | #include <drm/drm_encoder.h> |
23 | #include <drm/drm_of.h> |
24 | #include <drm/drm_panel.h> |
25 | #include <drm/drm_print.h> |
26 | #include <drm/drm_probe_helper.h> |
27 | #include <drm/drm_simple_kms_helper.h> |
28 | |
29 | #include "sun4i_backend.h" |
30 | #include "sun4i_crtc.h" |
31 | #include "sun4i_drv.h" |
32 | #include "sun4i_hdmi.h" |
33 | |
34 | #define drm_encoder_to_sun4i_hdmi(e) \ |
35 | container_of_const(e, struct sun4i_hdmi, encoder) |
36 | |
37 | #define drm_connector_to_sun4i_hdmi(c) \ |
38 | container_of_const(c, struct sun4i_hdmi, connector) |
39 | |
40 | static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi, |
41 | struct drm_display_mode *mode) |
42 | { |
43 | struct hdmi_avi_infoframe frame; |
44 | u8 buffer[17]; |
45 | int i, ret; |
46 | |
47 | ret = drm_hdmi_avi_infoframe_from_display_mode(frame: &frame, |
48 | connector: &hdmi->connector, mode); |
49 | if (ret < 0) { |
50 | DRM_ERROR("Failed to get infoframes from mode\n" ); |
51 | return ret; |
52 | } |
53 | |
54 | ret = hdmi_avi_infoframe_pack(frame: &frame, buffer, size: sizeof(buffer)); |
55 | if (ret < 0) { |
56 | DRM_ERROR("Failed to pack infoframes\n" ); |
57 | return ret; |
58 | } |
59 | |
60 | for (i = 0; i < sizeof(buffer); i++) |
61 | writeb(val: buffer[i], addr: hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i)); |
62 | |
63 | return 0; |
64 | } |
65 | |
66 | static void sun4i_hdmi_disable(struct drm_encoder *encoder, |
67 | struct drm_atomic_state *state) |
68 | { |
69 | struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); |
70 | u32 val; |
71 | |
72 | DRM_DEBUG_DRIVER("Disabling the HDMI Output\n" ); |
73 | |
74 | val = readl(addr: hdmi->base + SUN4I_HDMI_VID_CTRL_REG); |
75 | val &= ~SUN4I_HDMI_VID_CTRL_ENABLE; |
76 | writel(val, addr: hdmi->base + SUN4I_HDMI_VID_CTRL_REG); |
77 | |
78 | clk_disable_unprepare(clk: hdmi->tmds_clk); |
79 | } |
80 | |
81 | static void sun4i_hdmi_enable(struct drm_encoder *encoder, |
82 | struct drm_atomic_state *state) |
83 | { |
84 | struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; |
85 | struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); |
86 | struct drm_display_info *display = &hdmi->connector.display_info; |
87 | unsigned int x, y; |
88 | u32 val = 0; |
89 | |
90 | DRM_DEBUG_DRIVER("Enabling the HDMI Output\n" ); |
91 | |
92 | clk_set_rate(clk: hdmi->mod_clk, rate: mode->crtc_clock * 1000); |
93 | clk_set_rate(clk: hdmi->tmds_clk, rate: mode->crtc_clock * 1000); |
94 | |
95 | /* Set input sync enable */ |
96 | writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC, |
97 | addr: hdmi->base + SUN4I_HDMI_UNKNOWN_REG); |
98 | |
99 | /* |
100 | * Setup output pad (?) controls |
101 | * |
102 | * This is done here instead of at probe/bind time because |
103 | * the controller seems to toggle some of the bits on its own. |
104 | * |
105 | * We can't just initialize the register there, we need to |
106 | * protect the clock bits that have already been read out and |
107 | * cached by the clock framework. |
108 | */ |
109 | val = readl(addr: hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); |
110 | val &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; |
111 | val |= hdmi->variant->pad_ctrl1_init_val; |
112 | writel(val, addr: hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); |
113 | val = readl(addr: hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); |
114 | |
115 | /* Setup timing registers */ |
116 | writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) | |
117 | SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay), |
118 | addr: hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG); |
119 | |
120 | x = mode->htotal - mode->hsync_start; |
121 | y = mode->vtotal - mode->vsync_start; |
122 | writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), |
123 | addr: hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG); |
124 | |
125 | x = mode->hsync_start - mode->hdisplay; |
126 | y = mode->vsync_start - mode->vdisplay; |
127 | writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), |
128 | addr: hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG); |
129 | |
130 | x = mode->hsync_end - mode->hsync_start; |
131 | y = mode->vsync_end - mode->vsync_start; |
132 | writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), |
133 | addr: hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG); |
134 | |
135 | val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK; |
136 | if (mode->flags & DRM_MODE_FLAG_PHSYNC) |
137 | val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC; |
138 | |
139 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
140 | val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC; |
141 | |
142 | writel(val, addr: hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG); |
143 | |
144 | clk_prepare_enable(clk: hdmi->tmds_clk); |
145 | |
146 | sun4i_hdmi_setup_avi_infoframes(hdmi, mode); |
147 | val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); |
148 | val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END); |
149 | writel(val, addr: hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0)); |
150 | |
151 | val = SUN4I_HDMI_VID_CTRL_ENABLE; |
152 | if (display->is_hdmi) |
153 | val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE; |
154 | |
155 | writel(val, addr: hdmi->base + SUN4I_HDMI_VID_CTRL_REG); |
156 | } |
157 | |
158 | static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = { |
159 | .atomic_disable = sun4i_hdmi_disable, |
160 | .atomic_enable = sun4i_hdmi_enable, |
161 | }; |
162 | |
163 | static enum drm_mode_status |
164 | sun4i_hdmi_connector_clock_valid(const struct drm_connector *connector, |
165 | const struct drm_display_mode *mode, |
166 | unsigned long long clock) |
167 | { |
168 | const struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); |
169 | unsigned long diff = div_u64(dividend: clock, divisor: 200); /* +-0.5% allowed by HDMI spec */ |
170 | long rounded_rate; |
171 | |
172 | if (mode->flags & DRM_MODE_FLAG_DBLCLK) |
173 | return MODE_BAD; |
174 | |
175 | /* 165 MHz is the typical max pixelclock frequency for HDMI <= 1.2 */ |
176 | if (clock > 165000000) |
177 | return MODE_CLOCK_HIGH; |
178 | |
179 | rounded_rate = clk_round_rate(clk: hdmi->tmds_clk, rate: clock); |
180 | if (rounded_rate > 0 && |
181 | max_t(unsigned long, rounded_rate, clock) - |
182 | min_t(unsigned long, rounded_rate, clock) < diff) |
183 | return MODE_OK; |
184 | |
185 | return MODE_NOCLOCK; |
186 | } |
187 | |
188 | static int sun4i_hdmi_connector_atomic_check(struct drm_connector *connector, |
189 | struct drm_atomic_state *state) |
190 | { |
191 | struct drm_connector_state *conn_state = |
192 | drm_atomic_get_new_connector_state(state, connector); |
193 | struct drm_crtc *crtc = conn_state->crtc; |
194 | struct drm_crtc_state *crtc_state = crtc->state; |
195 | struct drm_display_mode *mode = &crtc_state->adjusted_mode; |
196 | enum drm_mode_status status; |
197 | |
198 | status = sun4i_hdmi_connector_clock_valid(connector, mode, |
199 | clock: mode->clock * 1000); |
200 | if (status != MODE_OK) |
201 | return -EINVAL; |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | static enum drm_mode_status |
207 | sun4i_hdmi_connector_mode_valid(struct drm_connector *connector, |
208 | struct drm_display_mode *mode) |
209 | { |
210 | return sun4i_hdmi_connector_clock_valid(connector, mode, |
211 | clock: mode->clock * 1000); |
212 | } |
213 | |
214 | static int sun4i_hdmi_get_modes(struct drm_connector *connector) |
215 | { |
216 | struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); |
217 | struct edid *edid; |
218 | int ret; |
219 | |
220 | edid = drm_get_edid(connector, adapter: hdmi->ddc_i2c ?: hdmi->i2c); |
221 | if (!edid) |
222 | return 0; |
223 | |
224 | DRM_DEBUG_DRIVER("Monitor is %s monitor\n" , |
225 | connector->display_info.is_hdmi ? "an HDMI" : "a DVI" ); |
226 | |
227 | drm_connector_update_edid_property(connector, edid); |
228 | cec_s_phys_addr_from_edid(adap: hdmi->cec_adap, edid); |
229 | ret = drm_add_edid_modes(connector, edid); |
230 | kfree(objp: edid); |
231 | |
232 | return ret; |
233 | } |
234 | |
235 | static struct i2c_adapter *sun4i_hdmi_get_ddc(struct device *dev) |
236 | { |
237 | struct device_node *phandle, *remote; |
238 | struct i2c_adapter *ddc; |
239 | |
240 | remote = of_graph_get_remote_node(node: dev->of_node, port: 1, endpoint: -1); |
241 | if (!remote) |
242 | return ERR_PTR(error: -EINVAL); |
243 | |
244 | phandle = of_parse_phandle(np: remote, phandle_name: "ddc-i2c-bus" , index: 0); |
245 | of_node_put(node: remote); |
246 | if (!phandle) |
247 | return ERR_PTR(error: -ENODEV); |
248 | |
249 | ddc = of_get_i2c_adapter_by_node(node: phandle); |
250 | of_node_put(node: phandle); |
251 | if (!ddc) |
252 | return ERR_PTR(error: -EPROBE_DEFER); |
253 | |
254 | return ddc; |
255 | } |
256 | |
257 | static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = { |
258 | .atomic_check = sun4i_hdmi_connector_atomic_check, |
259 | .mode_valid = sun4i_hdmi_connector_mode_valid, |
260 | .get_modes = sun4i_hdmi_get_modes, |
261 | }; |
262 | |
263 | static enum drm_connector_status |
264 | sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force) |
265 | { |
266 | struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); |
267 | unsigned long reg; |
268 | |
269 | reg = readl(addr: hdmi->base + SUN4I_HDMI_HPD_REG); |
270 | if (!(reg & SUN4I_HDMI_HPD_HIGH)) { |
271 | cec_phys_addr_invalidate(adap: hdmi->cec_adap); |
272 | return connector_status_disconnected; |
273 | } |
274 | |
275 | return connector_status_connected; |
276 | } |
277 | |
278 | static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = { |
279 | .detect = sun4i_hdmi_connector_detect, |
280 | .fill_modes = drm_helper_probe_single_connector_modes, |
281 | .destroy = drm_connector_cleanup, |
282 | .reset = drm_atomic_helper_connector_reset, |
283 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
284 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
285 | }; |
286 | |
287 | #ifdef CONFIG_DRM_SUN4I_HDMI_CEC |
288 | static int sun4i_hdmi_cec_pin_read(struct cec_adapter *adap) |
289 | { |
290 | struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); |
291 | |
292 | return readl(addr: hdmi->base + SUN4I_HDMI_CEC) & SUN4I_HDMI_CEC_RX; |
293 | } |
294 | |
295 | static void sun4i_hdmi_cec_pin_low(struct cec_adapter *adap) |
296 | { |
297 | struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); |
298 | |
299 | /* Start driving the CEC pin low */ |
300 | writel(SUN4I_HDMI_CEC_ENABLE, addr: hdmi->base + SUN4I_HDMI_CEC); |
301 | } |
302 | |
303 | static void sun4i_hdmi_cec_pin_high(struct cec_adapter *adap) |
304 | { |
305 | struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); |
306 | |
307 | /* |
308 | * Stop driving the CEC pin, the pull up will take over |
309 | * unless another CEC device is driving the pin low. |
310 | */ |
311 | writel(val: 0, addr: hdmi->base + SUN4I_HDMI_CEC); |
312 | } |
313 | |
314 | static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = { |
315 | .read = sun4i_hdmi_cec_pin_read, |
316 | .low = sun4i_hdmi_cec_pin_low, |
317 | .high = sun4i_hdmi_cec_pin_high, |
318 | }; |
319 | #endif |
320 | |
321 | #define SUN4I_HDMI_PAD_CTRL1_MASK (GENMASK(24, 7) | GENMASK(5, 0)) |
322 | #define SUN4I_HDMI_PLL_CTRL_MASK (GENMASK(31, 8) | GENMASK(3, 0)) |
323 | |
324 | /* Only difference from sun5i is AMP is 4 instead of 6 */ |
325 | static const struct sun4i_hdmi_variant sun4i_variant = { |
326 | .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | |
327 | SUN4I_HDMI_PAD_CTRL0_CKEN | |
328 | SUN4I_HDMI_PAD_CTRL0_PWENG | |
329 | SUN4I_HDMI_PAD_CTRL0_PWEND | |
330 | SUN4I_HDMI_PAD_CTRL0_PWENC | |
331 | SUN4I_HDMI_PAD_CTRL0_LDODEN | |
332 | SUN4I_HDMI_PAD_CTRL0_LDOCEN | |
333 | SUN4I_HDMI_PAD_CTRL0_BIASEN, |
334 | .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(4) | |
335 | SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | |
336 | SUN4I_HDMI_PAD_CTRL1_REG_DENCK | |
337 | SUN4I_HDMI_PAD_CTRL1_REG_DEN | |
338 | SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | |
339 | SUN4I_HDMI_PAD_CTRL1_EMP_OPT | |
340 | SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | |
341 | SUN4I_HDMI_PAD_CTRL1_AMP_OPT, |
342 | .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | |
343 | SUN4I_HDMI_PLL_CTRL_CS(7) | |
344 | SUN4I_HDMI_PLL_CTRL_CP_S(15) | |
345 | SUN4I_HDMI_PLL_CTRL_S(7) | |
346 | SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | |
347 | SUN4I_HDMI_PLL_CTRL_SDIV2 | |
348 | SUN4I_HDMI_PLL_CTRL_LDO2_EN | |
349 | SUN4I_HDMI_PLL_CTRL_LDO1_EN | |
350 | SUN4I_HDMI_PLL_CTRL_HV_IS_33 | |
351 | SUN4I_HDMI_PLL_CTRL_BWS | |
352 | SUN4I_HDMI_PLL_CTRL_PLL_EN, |
353 | |
354 | .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), |
355 | .ddc_clk_pre_divider = 2, |
356 | .ddc_clk_m_offset = 1, |
357 | |
358 | .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), |
359 | .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), |
360 | .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), |
361 | .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), |
362 | .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), |
363 | .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), |
364 | .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), |
365 | .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), |
366 | .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), |
367 | .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), |
368 | .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), |
369 | .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), |
370 | .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), |
371 | |
372 | .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, |
373 | .ddc_fifo_has_dir = true, |
374 | }; |
375 | |
376 | static const struct sun4i_hdmi_variant sun5i_variant = { |
377 | .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | |
378 | SUN4I_HDMI_PAD_CTRL0_CKEN | |
379 | SUN4I_HDMI_PAD_CTRL0_PWENG | |
380 | SUN4I_HDMI_PAD_CTRL0_PWEND | |
381 | SUN4I_HDMI_PAD_CTRL0_PWENC | |
382 | SUN4I_HDMI_PAD_CTRL0_LDODEN | |
383 | SUN4I_HDMI_PAD_CTRL0_LDOCEN | |
384 | SUN4I_HDMI_PAD_CTRL0_BIASEN, |
385 | .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | |
386 | SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | |
387 | SUN4I_HDMI_PAD_CTRL1_REG_DENCK | |
388 | SUN4I_HDMI_PAD_CTRL1_REG_DEN | |
389 | SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | |
390 | SUN4I_HDMI_PAD_CTRL1_EMP_OPT | |
391 | SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | |
392 | SUN4I_HDMI_PAD_CTRL1_AMP_OPT, |
393 | .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | |
394 | SUN4I_HDMI_PLL_CTRL_CS(7) | |
395 | SUN4I_HDMI_PLL_CTRL_CP_S(15) | |
396 | SUN4I_HDMI_PLL_CTRL_S(7) | |
397 | SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | |
398 | SUN4I_HDMI_PLL_CTRL_SDIV2 | |
399 | SUN4I_HDMI_PLL_CTRL_LDO2_EN | |
400 | SUN4I_HDMI_PLL_CTRL_LDO1_EN | |
401 | SUN4I_HDMI_PLL_CTRL_HV_IS_33 | |
402 | SUN4I_HDMI_PLL_CTRL_BWS | |
403 | SUN4I_HDMI_PLL_CTRL_PLL_EN, |
404 | |
405 | .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), |
406 | .ddc_clk_pre_divider = 2, |
407 | .ddc_clk_m_offset = 1, |
408 | |
409 | .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), |
410 | .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), |
411 | .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), |
412 | .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), |
413 | .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), |
414 | .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), |
415 | .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), |
416 | .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), |
417 | .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), |
418 | .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), |
419 | .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), |
420 | .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), |
421 | .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), |
422 | |
423 | .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, |
424 | .ddc_fifo_has_dir = true, |
425 | }; |
426 | |
427 | static const struct sun4i_hdmi_variant sun6i_variant = { |
428 | .has_ddc_parent_clk = true, |
429 | .has_reset_control = true, |
430 | .pad_ctrl0_init_val = 0xff | |
431 | SUN4I_HDMI_PAD_CTRL0_TXEN | |
432 | SUN4I_HDMI_PAD_CTRL0_CKEN | |
433 | SUN4I_HDMI_PAD_CTRL0_PWENG | |
434 | SUN4I_HDMI_PAD_CTRL0_PWEND | |
435 | SUN4I_HDMI_PAD_CTRL0_PWENC | |
436 | SUN4I_HDMI_PAD_CTRL0_LDODEN | |
437 | SUN4I_HDMI_PAD_CTRL0_LDOCEN, |
438 | .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | |
439 | SUN4I_HDMI_PAD_CTRL1_REG_EMP(4) | |
440 | SUN4I_HDMI_PAD_CTRL1_REG_DENCK | |
441 | SUN4I_HDMI_PAD_CTRL1_REG_DEN | |
442 | SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | |
443 | SUN4I_HDMI_PAD_CTRL1_EMP_OPT | |
444 | SUN4I_HDMI_PAD_CTRL1_PWSDT | |
445 | SUN4I_HDMI_PAD_CTRL1_PWSCK | |
446 | SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | |
447 | SUN4I_HDMI_PAD_CTRL1_AMP_OPT | |
448 | SUN4I_HDMI_PAD_CTRL1_UNKNOWN, |
449 | .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | |
450 | SUN4I_HDMI_PLL_CTRL_CS(3) | |
451 | SUN4I_HDMI_PLL_CTRL_CP_S(10) | |
452 | SUN4I_HDMI_PLL_CTRL_S(4) | |
453 | SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | |
454 | SUN4I_HDMI_PLL_CTRL_SDIV2 | |
455 | SUN4I_HDMI_PLL_CTRL_LDO2_EN | |
456 | SUN4I_HDMI_PLL_CTRL_LDO1_EN | |
457 | SUN4I_HDMI_PLL_CTRL_HV_IS_33 | |
458 | SUN4I_HDMI_PLL_CTRL_PLL_EN, |
459 | |
460 | .ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG, 0, 6), |
461 | .ddc_clk_pre_divider = 1, |
462 | .ddc_clk_m_offset = 2, |
463 | |
464 | .tmds_clk_div_offset = 1, |
465 | |
466 | .field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 0, 0), |
467 | .field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 27, 27), |
468 | .field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 31, 31), |
469 | .field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 31), |
470 | .field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 7), |
471 | .field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG, 0, 8), |
472 | .field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 18, 18), |
473 | .field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), |
474 | .field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), |
475 | .field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 16, 25), |
476 | .field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2), |
477 | .field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6), |
478 | .field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4), |
479 | |
480 | .ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG, |
481 | .ddc_fifo_thres_incl = true, |
482 | }; |
483 | |
484 | static const struct regmap_config sun4i_hdmi_regmap_config = { |
485 | .reg_bits = 32, |
486 | .val_bits = 32, |
487 | .reg_stride = 4, |
488 | .max_register = 0x580, |
489 | }; |
490 | |
491 | static int sun4i_hdmi_bind(struct device *dev, struct device *master, |
492 | void *data) |
493 | { |
494 | struct platform_device *pdev = to_platform_device(dev); |
495 | struct drm_device *drm = data; |
496 | struct cec_connector_info conn_info; |
497 | struct sun4i_drv *drv = drm->dev_private; |
498 | struct sun4i_hdmi *hdmi; |
499 | u32 reg; |
500 | int ret; |
501 | |
502 | hdmi = devm_kzalloc(dev, size: sizeof(*hdmi), GFP_KERNEL); |
503 | if (!hdmi) |
504 | return -ENOMEM; |
505 | dev_set_drvdata(dev, data: hdmi); |
506 | hdmi->dev = dev; |
507 | hdmi->drv = drv; |
508 | |
509 | hdmi->variant = of_device_get_match_data(dev); |
510 | if (!hdmi->variant) |
511 | return -EINVAL; |
512 | |
513 | hdmi->base = devm_platform_ioremap_resource(pdev, index: 0); |
514 | if (IS_ERR(ptr: hdmi->base)) { |
515 | dev_err(dev, "Couldn't map the HDMI encoder registers\n" ); |
516 | return PTR_ERR(ptr: hdmi->base); |
517 | } |
518 | |
519 | if (hdmi->variant->has_reset_control) { |
520 | hdmi->reset = devm_reset_control_get(dev, NULL); |
521 | if (IS_ERR(ptr: hdmi->reset)) { |
522 | dev_err(dev, "Couldn't get the HDMI reset control\n" ); |
523 | return PTR_ERR(ptr: hdmi->reset); |
524 | } |
525 | |
526 | ret = reset_control_deassert(rstc: hdmi->reset); |
527 | if (ret) { |
528 | dev_err(dev, "Couldn't deassert HDMI reset\n" ); |
529 | return ret; |
530 | } |
531 | } |
532 | |
533 | hdmi->bus_clk = devm_clk_get(dev, id: "ahb" ); |
534 | if (IS_ERR(ptr: hdmi->bus_clk)) { |
535 | dev_err(dev, "Couldn't get the HDMI bus clock\n" ); |
536 | ret = PTR_ERR(ptr: hdmi->bus_clk); |
537 | goto err_assert_reset; |
538 | } |
539 | clk_prepare_enable(clk: hdmi->bus_clk); |
540 | |
541 | hdmi->mod_clk = devm_clk_get(dev, id: "mod" ); |
542 | if (IS_ERR(ptr: hdmi->mod_clk)) { |
543 | dev_err(dev, "Couldn't get the HDMI mod clock\n" ); |
544 | ret = PTR_ERR(ptr: hdmi->mod_clk); |
545 | goto err_disable_bus_clk; |
546 | } |
547 | clk_prepare_enable(clk: hdmi->mod_clk); |
548 | |
549 | hdmi->pll0_clk = devm_clk_get(dev, id: "pll-0" ); |
550 | if (IS_ERR(ptr: hdmi->pll0_clk)) { |
551 | dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n" ); |
552 | ret = PTR_ERR(ptr: hdmi->pll0_clk); |
553 | goto err_disable_mod_clk; |
554 | } |
555 | |
556 | hdmi->pll1_clk = devm_clk_get(dev, id: "pll-1" ); |
557 | if (IS_ERR(ptr: hdmi->pll1_clk)) { |
558 | dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n" ); |
559 | ret = PTR_ERR(ptr: hdmi->pll1_clk); |
560 | goto err_disable_mod_clk; |
561 | } |
562 | |
563 | hdmi->regmap = devm_regmap_init_mmio(dev, hdmi->base, |
564 | &sun4i_hdmi_regmap_config); |
565 | if (IS_ERR(ptr: hdmi->regmap)) { |
566 | dev_err(dev, "Couldn't create HDMI encoder regmap\n" ); |
567 | ret = PTR_ERR(ptr: hdmi->regmap); |
568 | goto err_disable_mod_clk; |
569 | } |
570 | |
571 | ret = sun4i_tmds_create(hdmi); |
572 | if (ret) { |
573 | dev_err(dev, "Couldn't create the TMDS clock\n" ); |
574 | goto err_disable_mod_clk; |
575 | } |
576 | |
577 | if (hdmi->variant->has_ddc_parent_clk) { |
578 | hdmi->ddc_parent_clk = devm_clk_get(dev, id: "ddc" ); |
579 | if (IS_ERR(ptr: hdmi->ddc_parent_clk)) { |
580 | dev_err(dev, "Couldn't get the HDMI DDC clock\n" ); |
581 | ret = PTR_ERR(ptr: hdmi->ddc_parent_clk); |
582 | goto err_disable_mod_clk; |
583 | } |
584 | } else { |
585 | hdmi->ddc_parent_clk = hdmi->tmds_clk; |
586 | } |
587 | |
588 | writel(SUN4I_HDMI_CTRL_ENABLE, addr: hdmi->base + SUN4I_HDMI_CTRL_REG); |
589 | |
590 | writel(val: hdmi->variant->pad_ctrl0_init_val, |
591 | addr: hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG); |
592 | |
593 | reg = readl(addr: hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); |
594 | reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK; |
595 | reg |= hdmi->variant->pll_ctrl_init_val; |
596 | writel(val: reg, addr: hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); |
597 | |
598 | ret = sun4i_hdmi_i2c_create(dev, hdmi); |
599 | if (ret) { |
600 | dev_err(dev, "Couldn't create the HDMI I2C adapter\n" ); |
601 | goto err_disable_mod_clk; |
602 | } |
603 | |
604 | hdmi->ddc_i2c = sun4i_hdmi_get_ddc(dev); |
605 | if (IS_ERR(ptr: hdmi->ddc_i2c)) { |
606 | ret = PTR_ERR(ptr: hdmi->ddc_i2c); |
607 | if (ret == -ENODEV) |
608 | hdmi->ddc_i2c = NULL; |
609 | else |
610 | goto err_del_i2c_adapter; |
611 | } |
612 | |
613 | drm_encoder_helper_add(encoder: &hdmi->encoder, |
614 | funcs: &sun4i_hdmi_helper_funcs); |
615 | ret = drm_simple_encoder_init(dev: drm, encoder: &hdmi->encoder, |
616 | DRM_MODE_ENCODER_TMDS); |
617 | if (ret) { |
618 | dev_err(dev, "Couldn't initialise the HDMI encoder\n" ); |
619 | goto err_put_ddc_i2c; |
620 | } |
621 | |
622 | hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(dev: drm, |
623 | port: dev->of_node); |
624 | if (!hdmi->encoder.possible_crtcs) { |
625 | ret = -EPROBE_DEFER; |
626 | goto err_put_ddc_i2c; |
627 | } |
628 | |
629 | #ifdef CONFIG_DRM_SUN4I_HDMI_CEC |
630 | hdmi->cec_adap = cec_pin_allocate_adapter(pin_ops: &sun4i_hdmi_cec_pin_ops, |
631 | priv: hdmi, name: "sun4i" , CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO); |
632 | ret = PTR_ERR_OR_ZERO(ptr: hdmi->cec_adap); |
633 | if (ret < 0) |
634 | goto err_cleanup_connector; |
635 | writel(readl(addr: hdmi->base + SUN4I_HDMI_CEC) & ~SUN4I_HDMI_CEC_TX, |
636 | addr: hdmi->base + SUN4I_HDMI_CEC); |
637 | #endif |
638 | |
639 | drm_connector_helper_add(connector: &hdmi->connector, |
640 | funcs: &sun4i_hdmi_connector_helper_funcs); |
641 | ret = drm_connector_init_with_ddc(dev: drm, connector: &hdmi->connector, |
642 | funcs: &sun4i_hdmi_connector_funcs, |
643 | DRM_MODE_CONNECTOR_HDMIA, |
644 | ddc: hdmi->ddc_i2c); |
645 | if (ret) { |
646 | dev_err(dev, |
647 | "Couldn't initialise the HDMI connector\n" ); |
648 | goto err_cleanup_connector; |
649 | } |
650 | cec_fill_conn_info_from_drm(conn_info: &conn_info, connector: &hdmi->connector); |
651 | cec_s_conn_info(adap: hdmi->cec_adap, conn_info: &conn_info); |
652 | |
653 | /* There is no HPD interrupt, so we need to poll the controller */ |
654 | hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | |
655 | DRM_CONNECTOR_POLL_DISCONNECT; |
656 | |
657 | ret = cec_register_adapter(adap: hdmi->cec_adap, parent: dev); |
658 | if (ret < 0) |
659 | goto err_cleanup_connector; |
660 | drm_connector_attach_encoder(connector: &hdmi->connector, encoder: &hdmi->encoder); |
661 | |
662 | return 0; |
663 | |
664 | err_cleanup_connector: |
665 | cec_delete_adapter(adap: hdmi->cec_adap); |
666 | drm_encoder_cleanup(encoder: &hdmi->encoder); |
667 | err_put_ddc_i2c: |
668 | i2c_put_adapter(adap: hdmi->ddc_i2c); |
669 | err_del_i2c_adapter: |
670 | i2c_del_adapter(adap: hdmi->i2c); |
671 | err_disable_mod_clk: |
672 | clk_disable_unprepare(clk: hdmi->mod_clk); |
673 | err_disable_bus_clk: |
674 | clk_disable_unprepare(clk: hdmi->bus_clk); |
675 | err_assert_reset: |
676 | reset_control_assert(rstc: hdmi->reset); |
677 | return ret; |
678 | } |
679 | |
680 | static void sun4i_hdmi_unbind(struct device *dev, struct device *master, |
681 | void *data) |
682 | { |
683 | struct sun4i_hdmi *hdmi = dev_get_drvdata(dev); |
684 | |
685 | cec_unregister_adapter(adap: hdmi->cec_adap); |
686 | i2c_del_adapter(adap: hdmi->i2c); |
687 | i2c_put_adapter(adap: hdmi->ddc_i2c); |
688 | clk_disable_unprepare(clk: hdmi->mod_clk); |
689 | clk_disable_unprepare(clk: hdmi->bus_clk); |
690 | } |
691 | |
692 | static const struct component_ops sun4i_hdmi_ops = { |
693 | .bind = sun4i_hdmi_bind, |
694 | .unbind = sun4i_hdmi_unbind, |
695 | }; |
696 | |
697 | static int sun4i_hdmi_probe(struct platform_device *pdev) |
698 | { |
699 | return component_add(&pdev->dev, &sun4i_hdmi_ops); |
700 | } |
701 | |
702 | static void sun4i_hdmi_remove(struct platform_device *pdev) |
703 | { |
704 | component_del(&pdev->dev, &sun4i_hdmi_ops); |
705 | } |
706 | |
707 | static const struct of_device_id sun4i_hdmi_of_table[] = { |
708 | { .compatible = "allwinner,sun4i-a10-hdmi" , .data = &sun4i_variant, }, |
709 | { .compatible = "allwinner,sun5i-a10s-hdmi" , .data = &sun5i_variant, }, |
710 | { .compatible = "allwinner,sun6i-a31-hdmi" , .data = &sun6i_variant, }, |
711 | { } |
712 | }; |
713 | MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table); |
714 | |
715 | static struct platform_driver sun4i_hdmi_driver = { |
716 | .probe = sun4i_hdmi_probe, |
717 | .remove_new = sun4i_hdmi_remove, |
718 | .driver = { |
719 | .name = "sun4i-hdmi" , |
720 | .of_match_table = sun4i_hdmi_of_table, |
721 | }, |
722 | }; |
723 | module_platform_driver(sun4i_hdmi_driver); |
724 | |
725 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>" ); |
726 | MODULE_DESCRIPTION("Allwinner A10 HDMI Driver" ); |
727 | MODULE_LICENSE("GPL" ); |
728 | |