172317eaaSNeil Armstrong // SPDX-License-Identifier: GPL-2.0-or-later
272317eaaSNeil Armstrong /*
372317eaaSNeil Armstrong * Copyright (C) 2016 BayLibre, SAS
472317eaaSNeil Armstrong * Author: Neil Armstrong <narmstrong@baylibre.com>
572317eaaSNeil Armstrong * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
672317eaaSNeil Armstrong * Copyright (C) 2014 Endless Mobile
772317eaaSNeil Armstrong *
872317eaaSNeil Armstrong * Written by:
972317eaaSNeil Armstrong * Jasper St. Pierre <jstpierre@mecheye.net>
1072317eaaSNeil Armstrong */
1172317eaaSNeil Armstrong
1272317eaaSNeil Armstrong #include <linux/export.h>
1372317eaaSNeil Armstrong #include <linux/of_graph.h>
1472317eaaSNeil Armstrong
1572317eaaSNeil Armstrong #include <drm/drm_atomic_helper.h>
16318ba02cSNeil Armstrong #include <drm/drm_bridge.h>
17318ba02cSNeil Armstrong #include <drm/drm_bridge_connector.h>
1872317eaaSNeil Armstrong #include <drm/drm_device.h>
1972317eaaSNeil Armstrong #include <drm/drm_edid.h>
2072317eaaSNeil Armstrong #include <drm/drm_probe_helper.h>
21318ba02cSNeil Armstrong #include <drm/drm_simple_kms_helper.h>
2272317eaaSNeil Armstrong
2372317eaaSNeil Armstrong #include "meson_registers.h"
2472317eaaSNeil Armstrong #include "meson_vclk.h"
2572317eaaSNeil Armstrong #include "meson_encoder_cvbs.h"
2672317eaaSNeil Armstrong
2772317eaaSNeil Armstrong /* HHI VDAC Registers */
2872317eaaSNeil Armstrong #define HHI_VDAC_CNTL0 0x2F4 /* 0xbd offset in data sheet */
2972317eaaSNeil Armstrong #define HHI_VDAC_CNTL0_G12A 0x2EC /* 0xbd offset in data sheet */
3072317eaaSNeil Armstrong #define HHI_VDAC_CNTL1 0x2F8 /* 0xbe offset in data sheet */
3172317eaaSNeil Armstrong #define HHI_VDAC_CNTL1_G12A 0x2F0 /* 0xbe offset in data sheet */
3272317eaaSNeil Armstrong
3372317eaaSNeil Armstrong struct meson_encoder_cvbs {
3472317eaaSNeil Armstrong struct drm_encoder encoder;
35318ba02cSNeil Armstrong struct drm_bridge bridge;
36318ba02cSNeil Armstrong struct drm_bridge *next_bridge;
3772317eaaSNeil Armstrong struct meson_drm *priv;
3872317eaaSNeil Armstrong };
3972317eaaSNeil Armstrong
40318ba02cSNeil Armstrong #define bridge_to_meson_encoder_cvbs(x) \
41318ba02cSNeil Armstrong container_of(x, struct meson_encoder_cvbs, bridge)
4272317eaaSNeil Armstrong
4372317eaaSNeil Armstrong /* Supported Modes */
4472317eaaSNeil Armstrong
4572317eaaSNeil Armstrong struct meson_cvbs_mode meson_cvbs_modes[MESON_CVBS_MODES_COUNT] = {
4672317eaaSNeil Armstrong { /* PAL */
4772317eaaSNeil Armstrong .enci = &meson_cvbs_enci_pal,
4872317eaaSNeil Armstrong .mode = {
4972317eaaSNeil Armstrong DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500,
5072317eaaSNeil Armstrong 720, 732, 795, 864, 0, 576, 580, 586, 625, 0,
5172317eaaSNeil Armstrong DRM_MODE_FLAG_INTERLACE),
5272317eaaSNeil Armstrong .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3,
5372317eaaSNeil Armstrong },
5472317eaaSNeil Armstrong },
5572317eaaSNeil Armstrong { /* NTSC */
5672317eaaSNeil Armstrong .enci = &meson_cvbs_enci_ntsc,
5772317eaaSNeil Armstrong .mode = {
5872317eaaSNeil Armstrong DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500,
5972317eaaSNeil Armstrong 720, 739, 801, 858, 0, 480, 488, 494, 525, 0,
6072317eaaSNeil Armstrong DRM_MODE_FLAG_INTERLACE),
6172317eaaSNeil Armstrong .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3,
6272317eaaSNeil Armstrong },
6372317eaaSNeil Armstrong },
6472317eaaSNeil Armstrong };
6572317eaaSNeil Armstrong
6672317eaaSNeil Armstrong static const struct meson_cvbs_mode *
meson_cvbs_get_mode(const struct drm_display_mode * req_mode)6772317eaaSNeil Armstrong meson_cvbs_get_mode(const struct drm_display_mode *req_mode)
6872317eaaSNeil Armstrong {
6972317eaaSNeil Armstrong int i;
7072317eaaSNeil Armstrong
7172317eaaSNeil Armstrong for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) {
7272317eaaSNeil Armstrong struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i];
7372317eaaSNeil Armstrong
7472317eaaSNeil Armstrong if (drm_mode_match(req_mode, &meson_mode->mode,
7572317eaaSNeil Armstrong DRM_MODE_MATCH_TIMINGS |
7672317eaaSNeil Armstrong DRM_MODE_MATCH_CLOCK |
7772317eaaSNeil Armstrong DRM_MODE_MATCH_FLAGS |
7872317eaaSNeil Armstrong DRM_MODE_MATCH_3D_FLAGS))
7972317eaaSNeil Armstrong return meson_mode;
8072317eaaSNeil Armstrong }
8172317eaaSNeil Armstrong
8272317eaaSNeil Armstrong return NULL;
8372317eaaSNeil Armstrong }
8472317eaaSNeil Armstrong
meson_encoder_cvbs_attach(struct drm_bridge * bridge,enum drm_bridge_attach_flags flags)85318ba02cSNeil Armstrong static int meson_encoder_cvbs_attach(struct drm_bridge *bridge,
86318ba02cSNeil Armstrong enum drm_bridge_attach_flags flags)
8772317eaaSNeil Armstrong {
88318ba02cSNeil Armstrong struct meson_encoder_cvbs *meson_encoder_cvbs =
89318ba02cSNeil Armstrong bridge_to_meson_encoder_cvbs(bridge);
90318ba02cSNeil Armstrong
91318ba02cSNeil Armstrong return drm_bridge_attach(bridge->encoder, meson_encoder_cvbs->next_bridge,
92318ba02cSNeil Armstrong &meson_encoder_cvbs->bridge, flags);
9372317eaaSNeil Armstrong }
9472317eaaSNeil Armstrong
meson_encoder_cvbs_get_modes(struct drm_bridge * bridge,struct drm_connector * connector)95318ba02cSNeil Armstrong static int meson_encoder_cvbs_get_modes(struct drm_bridge *bridge,
96318ba02cSNeil Armstrong struct drm_connector *connector)
9772317eaaSNeil Armstrong {
98318ba02cSNeil Armstrong struct meson_encoder_cvbs *meson_encoder_cvbs =
99318ba02cSNeil Armstrong bridge_to_meson_encoder_cvbs(bridge);
100318ba02cSNeil Armstrong struct meson_drm *priv = meson_encoder_cvbs->priv;
10172317eaaSNeil Armstrong struct drm_display_mode *mode;
10272317eaaSNeil Armstrong int i;
10372317eaaSNeil Armstrong
10472317eaaSNeil Armstrong for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) {
10572317eaaSNeil Armstrong struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i];
10672317eaaSNeil Armstrong
107318ba02cSNeil Armstrong mode = drm_mode_duplicate(priv->drm, &meson_mode->mode);
10872317eaaSNeil Armstrong if (!mode) {
109318ba02cSNeil Armstrong dev_err(priv->dev, "Failed to create a new display mode\n");
11072317eaaSNeil Armstrong return 0;
11172317eaaSNeil Armstrong }
11272317eaaSNeil Armstrong
11372317eaaSNeil Armstrong drm_mode_probed_add(connector, mode);
11472317eaaSNeil Armstrong }
11572317eaaSNeil Armstrong
11672317eaaSNeil Armstrong return i;
11772317eaaSNeil Armstrong }
11872317eaaSNeil Armstrong
119*6c4e4d35SNathan Chancellor static enum drm_mode_status
meson_encoder_cvbs_mode_valid(struct drm_bridge * bridge,const struct drm_display_info * display_info,const struct drm_display_mode * mode)120*6c4e4d35SNathan Chancellor meson_encoder_cvbs_mode_valid(struct drm_bridge *bridge,
121318ba02cSNeil Armstrong const struct drm_display_info *display_info,
122318ba02cSNeil Armstrong const struct drm_display_mode *mode)
12372317eaaSNeil Armstrong {
124318ba02cSNeil Armstrong if (meson_cvbs_get_mode(mode))
12572317eaaSNeil Armstrong return MODE_OK;
126318ba02cSNeil Armstrong
127318ba02cSNeil Armstrong return MODE_BAD;
12872317eaaSNeil Armstrong }
12972317eaaSNeil Armstrong
meson_encoder_cvbs_atomic_check(struct drm_bridge * bridge,struct drm_bridge_state * bridge_state,struct drm_crtc_state * crtc_state,struct drm_connector_state * conn_state)130318ba02cSNeil Armstrong static int meson_encoder_cvbs_atomic_check(struct drm_bridge *bridge,
131318ba02cSNeil Armstrong struct drm_bridge_state *bridge_state,
13272317eaaSNeil Armstrong struct drm_crtc_state *crtc_state,
13372317eaaSNeil Armstrong struct drm_connector_state *conn_state)
13472317eaaSNeil Armstrong {
13572317eaaSNeil Armstrong if (meson_cvbs_get_mode(&crtc_state->mode))
13672317eaaSNeil Armstrong return 0;
13772317eaaSNeil Armstrong
13872317eaaSNeil Armstrong return -EINVAL;
13972317eaaSNeil Armstrong }
14072317eaaSNeil Armstrong
meson_encoder_cvbs_atomic_enable(struct drm_bridge * bridge,struct drm_bridge_state * bridge_state)141318ba02cSNeil Armstrong static void meson_encoder_cvbs_atomic_enable(struct drm_bridge *bridge,
142318ba02cSNeil Armstrong struct drm_bridge_state *bridge_state)
14372317eaaSNeil Armstrong {
144318ba02cSNeil Armstrong struct meson_encoder_cvbs *encoder_cvbs = bridge_to_meson_encoder_cvbs(bridge);
145318ba02cSNeil Armstrong struct drm_atomic_state *state = bridge_state->base.state;
146318ba02cSNeil Armstrong struct meson_drm *priv = encoder_cvbs->priv;
147318ba02cSNeil Armstrong const struct meson_cvbs_mode *meson_mode;
148318ba02cSNeil Armstrong struct drm_connector_state *conn_state;
149318ba02cSNeil Armstrong struct drm_crtc_state *crtc_state;
150318ba02cSNeil Armstrong struct drm_connector *connector;
15172317eaaSNeil Armstrong
152318ba02cSNeil Armstrong connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
153318ba02cSNeil Armstrong if (WARN_ON(!connector))
154318ba02cSNeil Armstrong return;
15572317eaaSNeil Armstrong
156318ba02cSNeil Armstrong conn_state = drm_atomic_get_new_connector_state(state, connector);
157318ba02cSNeil Armstrong if (WARN_ON(!conn_state))
158318ba02cSNeil Armstrong return;
159318ba02cSNeil Armstrong
160318ba02cSNeil Armstrong crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
161318ba02cSNeil Armstrong if (WARN_ON(!crtc_state))
162318ba02cSNeil Armstrong return;
163318ba02cSNeil Armstrong
164318ba02cSNeil Armstrong meson_mode = meson_cvbs_get_mode(&crtc_state->adjusted_mode);
165318ba02cSNeil Armstrong if (WARN_ON(!meson_mode))
166318ba02cSNeil Armstrong return;
167318ba02cSNeil Armstrong
168318ba02cSNeil Armstrong meson_venci_cvbs_mode_set(priv, meson_mode->enci);
169318ba02cSNeil Armstrong
170318ba02cSNeil Armstrong /* Setup 27MHz vclk2 for ENCI and VDAC */
171318ba02cSNeil Armstrong meson_vclk_setup(priv, MESON_VCLK_TARGET_CVBS,
172318ba02cSNeil Armstrong MESON_VCLK_CVBS, MESON_VCLK_CVBS,
173318ba02cSNeil Armstrong MESON_VCLK_CVBS, MESON_VCLK_CVBS,
174318ba02cSNeil Armstrong true);
17572317eaaSNeil Armstrong
17672317eaaSNeil Armstrong /* VDAC0 source is not from ATV */
17772317eaaSNeil Armstrong writel_bits_relaxed(VENC_VDAC_SEL_ATV_DMD, 0,
17872317eaaSNeil Armstrong priv->io_base + _REG(VENC_VDAC_DACSEL0));
17972317eaaSNeil Armstrong
18072317eaaSNeil Armstrong if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXBB)) {
18172317eaaSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL0, 1);
18272317eaaSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0);
18372317eaaSNeil Armstrong } else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
18472317eaaSNeil Armstrong meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL)) {
18572317eaaSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0xf0001);
18672317eaaSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0);
18772317eaaSNeil Armstrong } else if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {
18872317eaaSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0x906001);
18972317eaaSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 0);
19072317eaaSNeil Armstrong }
19172317eaaSNeil Armstrong }
19272317eaaSNeil Armstrong
meson_encoder_cvbs_atomic_disable(struct drm_bridge * bridge,struct drm_bridge_state * bridge_state)193318ba02cSNeil Armstrong static void meson_encoder_cvbs_atomic_disable(struct drm_bridge *bridge,
194318ba02cSNeil Armstrong struct drm_bridge_state *bridge_state)
19572317eaaSNeil Armstrong {
19672317eaaSNeil Armstrong struct meson_encoder_cvbs *meson_encoder_cvbs =
197318ba02cSNeil Armstrong bridge_to_meson_encoder_cvbs(bridge);
19872317eaaSNeil Armstrong struct meson_drm *priv = meson_encoder_cvbs->priv;
19972317eaaSNeil Armstrong
200318ba02cSNeil Armstrong /* Disable CVBS VDAC */
201318ba02cSNeil Armstrong if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) {
202318ba02cSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL0_G12A, 0);
203318ba02cSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL1_G12A, 0);
204318ba02cSNeil Armstrong } else {
205318ba02cSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0);
206318ba02cSNeil Armstrong regmap_write(priv->hhi, HHI_VDAC_CNTL1, 8);
20772317eaaSNeil Armstrong }
20872317eaaSNeil Armstrong }
20972317eaaSNeil Armstrong
210318ba02cSNeil Armstrong static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = {
211318ba02cSNeil Armstrong .attach = meson_encoder_cvbs_attach,
212318ba02cSNeil Armstrong .mode_valid = meson_encoder_cvbs_mode_valid,
213318ba02cSNeil Armstrong .get_modes = meson_encoder_cvbs_get_modes,
214318ba02cSNeil Armstrong .atomic_enable = meson_encoder_cvbs_atomic_enable,
215318ba02cSNeil Armstrong .atomic_disable = meson_encoder_cvbs_atomic_disable,
216318ba02cSNeil Armstrong .atomic_check = meson_encoder_cvbs_atomic_check,
217318ba02cSNeil Armstrong .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
218318ba02cSNeil Armstrong .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
219318ba02cSNeil Armstrong .atomic_reset = drm_atomic_helper_bridge_reset,
22072317eaaSNeil Armstrong };
22172317eaaSNeil Armstrong
meson_encoder_cvbs_init(struct meson_drm * priv)22272317eaaSNeil Armstrong int meson_encoder_cvbs_init(struct meson_drm *priv)
22372317eaaSNeil Armstrong {
22472317eaaSNeil Armstrong struct drm_device *drm = priv->drm;
22572317eaaSNeil Armstrong struct meson_encoder_cvbs *meson_encoder_cvbs;
22672317eaaSNeil Armstrong struct drm_connector *connector;
227318ba02cSNeil Armstrong struct device_node *remote;
22872317eaaSNeil Armstrong int ret;
22972317eaaSNeil Armstrong
230318ba02cSNeil Armstrong meson_encoder_cvbs = devm_kzalloc(priv->dev, sizeof(*meson_encoder_cvbs), GFP_KERNEL);
231318ba02cSNeil Armstrong if (!meson_encoder_cvbs)
232318ba02cSNeil Armstrong return -ENOMEM;
233318ba02cSNeil Armstrong
234318ba02cSNeil Armstrong /* CVBS Connector Bridge */
235318ba02cSNeil Armstrong remote = of_graph_get_remote_node(priv->dev->of_node, 0, 0);
236318ba02cSNeil Armstrong if (!remote) {
23772317eaaSNeil Armstrong dev_info(drm->dev, "CVBS Output connector not available\n");
23872317eaaSNeil Armstrong return 0;
23972317eaaSNeil Armstrong }
24072317eaaSNeil Armstrong
241318ba02cSNeil Armstrong meson_encoder_cvbs->next_bridge = of_drm_find_bridge(remote);
2427d255ddbSMiaoqian Lin of_node_put(remote);
243318ba02cSNeil Armstrong if (!meson_encoder_cvbs->next_bridge) {
244318ba02cSNeil Armstrong dev_err(priv->dev, "Failed to find CVBS Connector bridge\n");
245318ba02cSNeil Armstrong return -EPROBE_DEFER;
246318ba02cSNeil Armstrong }
247318ba02cSNeil Armstrong
248318ba02cSNeil Armstrong /* CVBS Encoder Bridge */
249318ba02cSNeil Armstrong meson_encoder_cvbs->bridge.funcs = &meson_encoder_cvbs_bridge_funcs;
250318ba02cSNeil Armstrong meson_encoder_cvbs->bridge.of_node = priv->dev->of_node;
251318ba02cSNeil Armstrong meson_encoder_cvbs->bridge.type = DRM_MODE_CONNECTOR_Composite;
252318ba02cSNeil Armstrong meson_encoder_cvbs->bridge.ops = DRM_BRIDGE_OP_MODES;
253318ba02cSNeil Armstrong meson_encoder_cvbs->bridge.interlace_allowed = true;
254318ba02cSNeil Armstrong
255318ba02cSNeil Armstrong drm_bridge_add(&meson_encoder_cvbs->bridge);
25672317eaaSNeil Armstrong
25772317eaaSNeil Armstrong meson_encoder_cvbs->priv = priv;
25872317eaaSNeil Armstrong
25972317eaaSNeil Armstrong /* Encoder */
260318ba02cSNeil Armstrong ret = drm_simple_encoder_init(priv->drm, &meson_encoder_cvbs->encoder,
261318ba02cSNeil Armstrong DRM_MODE_ENCODER_TVDAC);
26272317eaaSNeil Armstrong if (ret) {
263318ba02cSNeil Armstrong dev_err(priv->dev, "Failed to init CVBS encoder: %d\n", ret);
26472317eaaSNeil Armstrong return ret;
26572317eaaSNeil Armstrong }
26672317eaaSNeil Armstrong
267318ba02cSNeil Armstrong meson_encoder_cvbs->encoder.possible_crtcs = BIT(0);
26872317eaaSNeil Armstrong
269318ba02cSNeil Armstrong /* Attach CVBS Encoder Bridge to Encoder */
270318ba02cSNeil Armstrong ret = drm_bridge_attach(&meson_encoder_cvbs->encoder, &meson_encoder_cvbs->bridge, NULL,
271318ba02cSNeil Armstrong DRM_BRIDGE_ATTACH_NO_CONNECTOR);
272318ba02cSNeil Armstrong if (ret) {
273318ba02cSNeil Armstrong dev_err(priv->dev, "Failed to attach bridge: %d\n", ret);
274318ba02cSNeil Armstrong return ret;
275318ba02cSNeil Armstrong }
276318ba02cSNeil Armstrong
277318ba02cSNeil Armstrong /* Initialize & attach Bridge Connector */
278318ba02cSNeil Armstrong connector = drm_bridge_connector_init(priv->drm, &meson_encoder_cvbs->encoder);
279318ba02cSNeil Armstrong if (IS_ERR(connector)) {
280318ba02cSNeil Armstrong dev_err(priv->dev, "Unable to create CVBS bridge connector\n");
281318ba02cSNeil Armstrong return PTR_ERR(connector);
282318ba02cSNeil Armstrong }
283318ba02cSNeil Armstrong drm_connector_attach_encoder(connector, &meson_encoder_cvbs->encoder);
28472317eaaSNeil Armstrong
28509847723SAdrián Larumbe priv->encoders[MESON_ENC_CVBS] = meson_encoder_cvbs;
28609847723SAdrián Larumbe
28772317eaaSNeil Armstrong return 0;
28872317eaaSNeil Armstrong }
28909847723SAdrián Larumbe
meson_encoder_cvbs_remove(struct meson_drm * priv)29009847723SAdrián Larumbe void meson_encoder_cvbs_remove(struct meson_drm *priv)
29109847723SAdrián Larumbe {
29209847723SAdrián Larumbe struct meson_encoder_cvbs *meson_encoder_cvbs;
29309847723SAdrián Larumbe
29409847723SAdrián Larumbe if (priv->encoders[MESON_ENC_CVBS]) {
29509847723SAdrián Larumbe meson_encoder_cvbs = priv->encoders[MESON_ENC_CVBS];
29609847723SAdrián Larumbe drm_bridge_remove(&meson_encoder_cvbs->bridge);
29709847723SAdrián Larumbe }
29809847723SAdrián Larumbe }
299