xref: /linux/drivers/gpu/drm/sun4i/sun4i_lvds.c (revision 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2017 Free Electrons
4  * Maxime Ripard <maxime.ripard@free-electrons.com>
5  */
6 
7 #include <linux/clk.h>
8 
9 #include <drm/drm_atomic_helper.h>
10 #include <drm/drm_bridge.h>
11 #include <drm/drm_encoder.h>
12 #include <drm/drm_of.h>
13 #include <drm/drm_panel.h>
14 #include <drm/drm_print.h>
15 #include <drm/drm_probe_helper.h>
16 
17 #include "sun4i_crtc.h"
18 #include "sun4i_tcon.h"
19 #include "sun4i_lvds.h"
20 
21 static void sun4i_panel_put_action(void *data)
22 {
23 	drm_panel_put(data);
24 }
25 
26 struct sun4i_lvds {
27 	struct drm_connector	connector;
28 	struct drm_encoder	encoder;
29 
30 	struct drm_panel	*panel;
31 };
32 
33 static inline struct sun4i_lvds *
34 drm_connector_to_sun4i_lvds(struct drm_connector *connector)
35 {
36 	return container_of(connector, struct sun4i_lvds,
37 			    connector);
38 }
39 
40 static inline struct sun4i_lvds *
41 drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder)
42 {
43 	return container_of(encoder, struct sun4i_lvds,
44 			    encoder);
45 }
46 
47 static int sun4i_lvds_get_modes(struct drm_connector *connector)
48 {
49 	struct sun4i_lvds *lvds =
50 		drm_connector_to_sun4i_lvds(connector);
51 
52 	return drm_panel_get_modes(lvds->panel, connector);
53 }
54 
55 static const struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = {
56 	.get_modes	= sun4i_lvds_get_modes,
57 };
58 
59 static void
60 sun4i_lvds_connector_destroy(struct drm_connector *connector)
61 {
62 	drm_connector_cleanup(connector);
63 }
64 
65 static const struct drm_connector_funcs sun4i_lvds_con_funcs = {
66 	.fill_modes		= drm_helper_probe_single_connector_modes,
67 	.destroy		= sun4i_lvds_connector_destroy,
68 	.reset			= drm_atomic_helper_connector_reset,
69 	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
70 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
71 };
72 
73 static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder)
74 {
75 	struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
76 
77 	DRM_DEBUG_DRIVER("Enabling LVDS output\n");
78 
79 	if (lvds->panel) {
80 		drm_panel_prepare(lvds->panel);
81 		drm_panel_enable(lvds->panel);
82 	}
83 }
84 
85 static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder)
86 {
87 	struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
88 
89 	DRM_DEBUG_DRIVER("Disabling LVDS output\n");
90 
91 	if (lvds->panel) {
92 		drm_panel_disable(lvds->panel);
93 		drm_panel_unprepare(lvds->panel);
94 	}
95 }
96 
97 static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = {
98 	.destroy = drm_encoder_cleanup,
99 };
100 
101 static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = {
102 	.disable	= sun4i_lvds_encoder_disable,
103 	.enable		= sun4i_lvds_encoder_enable,
104 };
105 
106 int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon)
107 {
108 	struct drm_encoder *encoder;
109 	struct drm_bridge *bridge;
110 	struct sun4i_lvds *lvds;
111 	int ret;
112 
113 	lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL);
114 	if (!lvds)
115 		return -ENOMEM;
116 	encoder = &lvds->encoder;
117 
118 	ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0,
119 					  &lvds->panel, &bridge);
120 	if (ret) {
121 		dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n");
122 		return 0;
123 	}
124 
125 	if (lvds->panel) {
126 		ret = devm_add_action_or_reset(tcon->dev,
127 					       sun4i_panel_put_action,
128 					       lvds->panel);
129 		if (ret)
130 			return ret;
131 	}
132 
133 	drm_encoder_helper_add(&lvds->encoder,
134 			       &sun4i_lvds_enc_helper_funcs);
135 	ret = drm_encoder_init(drm, &lvds->encoder, &sun4i_lvds_enc_funcs,
136 			       DRM_MODE_ENCODER_LVDS, NULL);
137 	if (ret) {
138 		dev_err(drm->dev, "Couldn't initialise the lvds encoder\n");
139 		goto err_out;
140 	}
141 
142 	/* The LVDS encoder can only work with the TCON channel 0 */
143 	lvds->encoder.possible_crtcs = drm_crtc_mask(&tcon->crtc->crtc);
144 
145 	if (lvds->panel) {
146 		drm_connector_helper_add(&lvds->connector,
147 					 &sun4i_lvds_con_helper_funcs);
148 		ret = drm_connector_init(drm, &lvds->connector,
149 					 &sun4i_lvds_con_funcs,
150 					 DRM_MODE_CONNECTOR_LVDS);
151 		if (ret) {
152 			dev_err(drm->dev, "Couldn't initialise the lvds connector\n");
153 			goto err_cleanup_connector;
154 		}
155 
156 		drm_connector_attach_encoder(&lvds->connector,
157 						  &lvds->encoder);
158 	}
159 
160 	if (bridge) {
161 		ret = drm_bridge_attach(encoder, bridge, NULL, 0);
162 		if (ret)
163 			goto err_cleanup_connector;
164 	}
165 
166 	return 0;
167 
168 err_cleanup_connector:
169 	drm_encoder_cleanup(&lvds->encoder);
170 err_out:
171 	return ret;
172 }
173 EXPORT_SYMBOL(sun4i_lvds_init);
174