1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016 BayLibre, SAS |
4 | * Author: Neil Armstrong <narmstrong@baylibre.com> |
5 | * Copyright (C) 2015 Amlogic, Inc. All rights reserved. |
6 | * Copyright (C) 2014 Endless Mobile |
7 | * |
8 | * Written by: |
9 | * Jasper St. Pierre <jstpierre@mecheye.net> |
10 | */ |
11 | |
12 | #include <linux/export.h> |
13 | #include <linux/of_graph.h> |
14 | |
15 | #include <drm/drm_atomic_helper.h> |
16 | #include <drm/drm_bridge.h> |
17 | #include <drm/drm_bridge_connector.h> |
18 | #include <drm/drm_device.h> |
19 | #include <drm/drm_edid.h> |
20 | #include <drm/drm_probe_helper.h> |
21 | #include <drm/drm_simple_kms_helper.h> |
22 | |
23 | #include "meson_registers.h" |
24 | #include "meson_vclk.h" |
25 | #include "meson_encoder_cvbs.h" |
26 | |
27 | /* HHI VDAC Registers */ |
28 | #define HHI_VDAC_CNTL0 0x2F4 /* 0xbd offset in data sheet */ |
29 | #define HHI_VDAC_CNTL0_G12A 0x2EC /* 0xbd offset in data sheet */ |
30 | #define HHI_VDAC_CNTL1 0x2F8 /* 0xbe offset in data sheet */ |
31 | #define HHI_VDAC_CNTL1_G12A 0x2F0 /* 0xbe offset in data sheet */ |
32 | |
33 | struct meson_encoder_cvbs { |
34 | struct drm_encoder encoder; |
35 | struct drm_bridge bridge; |
36 | struct drm_bridge *next_bridge; |
37 | struct meson_drm *priv; |
38 | }; |
39 | |
40 | #define bridge_to_meson_encoder_cvbs(x) \ |
41 | container_of(x, struct meson_encoder_cvbs, bridge) |
42 | |
43 | /* Supported Modes */ |
44 | |
45 | struct meson_cvbs_mode meson_cvbs_modes[MESON_CVBS_MODES_COUNT] = { |
46 | { /* PAL */ |
47 | .enci = &meson_cvbs_enci_pal, |
48 | .mode = { |
49 | DRM_MODE("720x576i" , DRM_MODE_TYPE_DRIVER, 13500, |
50 | 720, 732, 795, 864, 0, 576, 580, 586, 625, 0, |
51 | DRM_MODE_FLAG_INTERLACE), |
52 | .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, |
53 | }, |
54 | }, |
55 | { /* NTSC */ |
56 | .enci = &meson_cvbs_enci_ntsc, |
57 | .mode = { |
58 | DRM_MODE("720x480i" , DRM_MODE_TYPE_DRIVER, 13500, |
59 | 720, 739, 801, 858, 0, 480, 488, 494, 525, 0, |
60 | DRM_MODE_FLAG_INTERLACE), |
61 | .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, |
62 | }, |
63 | }, |
64 | }; |
65 | |
66 | static const struct meson_cvbs_mode * |
67 | meson_cvbs_get_mode(const struct drm_display_mode *req_mode) |
68 | { |
69 | int i; |
70 | |
71 | for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) { |
72 | struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i]; |
73 | |
74 | if (drm_mode_match(mode1: req_mode, mode2: &meson_mode->mode, |
75 | DRM_MODE_MATCH_TIMINGS | |
76 | DRM_MODE_MATCH_CLOCK | |
77 | DRM_MODE_MATCH_FLAGS | |
78 | DRM_MODE_MATCH_3D_FLAGS)) |
79 | return meson_mode; |
80 | } |
81 | |
82 | return NULL; |
83 | } |
84 | |
85 | static int meson_encoder_cvbs_attach(struct drm_bridge *bridge, |
86 | enum drm_bridge_attach_flags flags) |
87 | { |
88 | struct meson_encoder_cvbs *meson_encoder_cvbs = |
89 | bridge_to_meson_encoder_cvbs(bridge); |
90 | |
91 | return drm_bridge_attach(encoder: bridge->encoder, bridge: meson_encoder_cvbs->next_bridge, |
92 | previous: &meson_encoder_cvbs->bridge, flags); |
93 | } |
94 | |
95 | static int meson_encoder_cvbs_get_modes(struct drm_bridge *bridge, |
96 | struct drm_connector *connector) |
97 | { |
98 | struct meson_encoder_cvbs *meson_encoder_cvbs = |
99 | bridge_to_meson_encoder_cvbs(bridge); |
100 | struct meson_drm *priv = meson_encoder_cvbs->priv; |
101 | struct drm_display_mode *mode; |
102 | int i; |
103 | |
104 | for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) { |
105 | struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i]; |
106 | |
107 | mode = drm_mode_duplicate(dev: priv->drm, mode: &meson_mode->mode); |
108 | if (!mode) { |
109 | dev_err(priv->dev, "Failed to create a new display mode\n" ); |
110 | return 0; |
111 | } |
112 | |
113 | drm_mode_probed_add(connector, mode); |
114 | } |
115 | |
116 | return i; |
117 | } |
118 | |
119 | static enum drm_mode_status |
120 | meson_encoder_cvbs_mode_valid(struct drm_bridge *bridge, |
121 | const struct drm_display_info *display_info, |
122 | const struct drm_display_mode *mode) |
123 | { |
124 | if (meson_cvbs_get_mode(req_mode: mode)) |
125 | return MODE_OK; |
126 | |
127 | return MODE_BAD; |
128 | } |
129 | |
130 | static int meson_encoder_cvbs_atomic_check(struct drm_bridge *bridge, |
131 | struct drm_bridge_state *bridge_state, |
132 | struct drm_crtc_state *crtc_state, |
133 | struct drm_connector_state *conn_state) |
134 | { |
135 | if (meson_cvbs_get_mode(req_mode: &crtc_state->mode)) |
136 | return 0; |
137 | |
138 | return -EINVAL; |
139 | } |
140 | |
141 | static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge, |
142 | struct drm_bridge_state *bridge_state) |
143 | { |
144 | struct meson_encoder_cvbs *encoder_cvbs = bridge_to_meson_encoder_cvbs(bridge); |
145 | struct drm_atomic_state *state = bridge_state->base.state; |
146 | struct meson_drm *priv = encoder_cvbs->priv; |
147 | const struct meson_cvbs_mode *meson_mode; |
148 | struct drm_connector_state *conn_state; |
149 | struct drm_crtc_state *crtc_state; |
150 | struct drm_connector *connector; |
151 | |
152 | connector = drm_atomic_get_new_connector_for_encoder(state, encoder: bridge->encoder); |
153 | if (WARN_ON(!connector)) |
154 | return; |
155 | |
156 | conn_state = drm_atomic_get_new_connector_state(state, connector); |
157 | if (WARN_ON(!conn_state)) |
158 | return; |
159 | |
160 | crtc_state = drm_atomic_get_new_crtc_state(state, crtc: conn_state->crtc); |
161 | if (WARN_ON(!crtc_state)) |
162 | return; |
163 | |
164 | meson_mode = meson_cvbs_get_mode(req_mode: &crtc_state->adjusted_mode); |
165 | if (WARN_ON(!meson_mode)) |
166 | return; |
167 | |
168 | meson_venci_cvbs_mode_set(priv, mode: meson_mode->enci); |
169 | |
170 | /* Setup 27MHz vclk2 for ENCI and VDAC */ |
171 | meson_vclk_setup(priv, target: MESON_VCLK_TARGET_CVBS, |
172 | MESON_VCLK_CVBS, MESON_VCLK_CVBS, |
173 | MESON_VCLK_CVBS, MESON_VCLK_CVBS, |
174 | hdmi_use_enci: true); |
175 | |
176 | /* VDAC0 source is not from ATV */ |
177 | writel_bits_relaxed(VENC_VDAC_SEL_ATV_DMD, 0, |
178 | priv->io_base + _REG(VENC_VDAC_DACSEL0)); |
179 | |
180 | if (meson_vpu_is_compatible(priv, family: VPU_COMPATIBLE_GXBB)) { |
181 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL0, val: 1); |
182 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL1, val: 0); |
183 | } else if (meson_vpu_is_compatible(priv, family: VPU_COMPATIBLE_GXM) || |
184 | meson_vpu_is_compatible(priv, family: VPU_COMPATIBLE_GXL)) { |
185 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL0, val: 0xf0001); |
186 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL1, val: 0); |
187 | } else if (meson_vpu_is_compatible(priv, family: VPU_COMPATIBLE_G12A)) { |
188 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL0_G12A, val: 0x906001); |
189 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL1_G12A, val: 0); |
190 | } |
191 | } |
192 | |
193 | static void meson_encoder_cvbs_atomic_disable(struct drm_bridge *bridge, |
194 | struct drm_bridge_state *bridge_state) |
195 | { |
196 | struct meson_encoder_cvbs *meson_encoder_cvbs = |
197 | bridge_to_meson_encoder_cvbs(bridge); |
198 | struct meson_drm *priv = meson_encoder_cvbs->priv; |
199 | |
200 | /* Disable CVBS VDAC */ |
201 | if (meson_vpu_is_compatible(priv, family: VPU_COMPATIBLE_G12A)) { |
202 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL0_G12A, val: 0); |
203 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL1_G12A, val: 0); |
204 | } else { |
205 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL0, val: 0); |
206 | regmap_write(map: priv->hhi, HHI_VDAC_CNTL1, val: 8); |
207 | } |
208 | } |
209 | |
210 | static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = { |
211 | .attach = meson_encoder_cvbs_attach, |
212 | .mode_valid = meson_encoder_cvbs_mode_valid, |
213 | .get_modes = meson_encoder_cvbs_get_modes, |
214 | .atomic_enable = meson_encoder_cvbs_atomic_enable, |
215 | .atomic_disable = meson_encoder_cvbs_atomic_disable, |
216 | .atomic_check = meson_encoder_cvbs_atomic_check, |
217 | .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, |
218 | .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, |
219 | .atomic_reset = drm_atomic_helper_bridge_reset, |
220 | }; |
221 | |
222 | int meson_encoder_cvbs_probe(struct meson_drm *priv) |
223 | { |
224 | struct drm_device *drm = priv->drm; |
225 | struct meson_encoder_cvbs *meson_encoder_cvbs; |
226 | struct drm_connector *connector; |
227 | struct device_node *remote; |
228 | int ret; |
229 | |
230 | meson_encoder_cvbs = devm_kzalloc(dev: priv->dev, size: sizeof(*meson_encoder_cvbs), GFP_KERNEL); |
231 | if (!meson_encoder_cvbs) |
232 | return -ENOMEM; |
233 | |
234 | /* CVBS Connector Bridge */ |
235 | remote = of_graph_get_remote_node(node: priv->dev->of_node, port: 0, endpoint: 0); |
236 | if (!remote) { |
237 | dev_info(drm->dev, "CVBS Output connector not available\n" ); |
238 | return 0; |
239 | } |
240 | |
241 | meson_encoder_cvbs->next_bridge = of_drm_find_bridge(np: remote); |
242 | of_node_put(node: remote); |
243 | if (!meson_encoder_cvbs->next_bridge) |
244 | return dev_err_probe(dev: priv->dev, err: -EPROBE_DEFER, |
245 | fmt: "Failed to find CVBS Connector bridge\n" ); |
246 | |
247 | /* CVBS Encoder Bridge */ |
248 | meson_encoder_cvbs->bridge.funcs = &meson_encoder_cvbs_bridge_funcs; |
249 | meson_encoder_cvbs->bridge.of_node = priv->dev->of_node; |
250 | meson_encoder_cvbs->bridge.type = DRM_MODE_CONNECTOR_Composite; |
251 | meson_encoder_cvbs->bridge.ops = DRM_BRIDGE_OP_MODES; |
252 | meson_encoder_cvbs->bridge.interlace_allowed = true; |
253 | |
254 | drm_bridge_add(bridge: &meson_encoder_cvbs->bridge); |
255 | |
256 | meson_encoder_cvbs->priv = priv; |
257 | |
258 | /* Encoder */ |
259 | ret = drm_simple_encoder_init(dev: priv->drm, encoder: &meson_encoder_cvbs->encoder, |
260 | DRM_MODE_ENCODER_TVDAC); |
261 | if (ret) |
262 | return dev_err_probe(dev: priv->dev, err: ret, |
263 | fmt: "Failed to init CVBS encoder\n" ); |
264 | |
265 | meson_encoder_cvbs->encoder.possible_crtcs = BIT(0); |
266 | |
267 | /* Attach CVBS Encoder Bridge to Encoder */ |
268 | ret = drm_bridge_attach(encoder: &meson_encoder_cvbs->encoder, bridge: &meson_encoder_cvbs->bridge, NULL, |
269 | flags: DRM_BRIDGE_ATTACH_NO_CONNECTOR); |
270 | if (ret) { |
271 | dev_err(priv->dev, "Failed to attach bridge: %d\n" , ret); |
272 | return ret; |
273 | } |
274 | |
275 | /* Initialize & attach Bridge Connector */ |
276 | connector = drm_bridge_connector_init(drm: priv->drm, encoder: &meson_encoder_cvbs->encoder); |
277 | if (IS_ERR(ptr: connector)) |
278 | return dev_err_probe(dev: priv->dev, err: PTR_ERR(ptr: connector), |
279 | fmt: "Unable to create CVBS bridge connector\n" ); |
280 | |
281 | drm_connector_attach_encoder(connector, encoder: &meson_encoder_cvbs->encoder); |
282 | |
283 | priv->encoders[MESON_ENC_CVBS] = meson_encoder_cvbs; |
284 | |
285 | return 0; |
286 | } |
287 | |
288 | void meson_encoder_cvbs_remove(struct meson_drm *priv) |
289 | { |
290 | struct meson_encoder_cvbs *meson_encoder_cvbs; |
291 | |
292 | if (priv->encoders[MESON_ENC_CVBS]) { |
293 | meson_encoder_cvbs = priv->encoders[MESON_ENC_CVBS]; |
294 | drm_bridge_remove(bridge: &meson_encoder_cvbs->bridge); |
295 | } |
296 | } |
297 | |