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_of.h> |
12 | #include <drm/drm_panel.h> |
13 | #include <drm/drm_print.h> |
14 | #include <drm/drm_probe_helper.h> |
15 | #include <drm/drm_simple_kms_helper.h> |
16 | |
17 | #include "sun4i_crtc.h" |
18 | #include "sun4i_tcon.h" |
19 | #include "sun4i_lvds.h" |
20 | |
21 | struct sun4i_lvds { |
22 | struct drm_connector connector; |
23 | struct drm_encoder encoder; |
24 | |
25 | struct drm_panel *panel; |
26 | }; |
27 | |
28 | static inline struct sun4i_lvds * |
29 | drm_connector_to_sun4i_lvds(struct drm_connector *connector) |
30 | { |
31 | return container_of(connector, struct sun4i_lvds, |
32 | connector); |
33 | } |
34 | |
35 | static inline struct sun4i_lvds * |
36 | drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder) |
37 | { |
38 | return container_of(encoder, struct sun4i_lvds, |
39 | encoder); |
40 | } |
41 | |
42 | static int sun4i_lvds_get_modes(struct drm_connector *connector) |
43 | { |
44 | struct sun4i_lvds *lvds = |
45 | drm_connector_to_sun4i_lvds(connector); |
46 | |
47 | return drm_panel_get_modes(panel: lvds->panel, connector); |
48 | } |
49 | |
50 | static const struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = { |
51 | .get_modes = sun4i_lvds_get_modes, |
52 | }; |
53 | |
54 | static void |
55 | sun4i_lvds_connector_destroy(struct drm_connector *connector) |
56 | { |
57 | drm_connector_cleanup(connector); |
58 | } |
59 | |
60 | static const struct drm_connector_funcs sun4i_lvds_con_funcs = { |
61 | .fill_modes = drm_helper_probe_single_connector_modes, |
62 | .destroy = sun4i_lvds_connector_destroy, |
63 | .reset = drm_atomic_helper_connector_reset, |
64 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
65 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
66 | }; |
67 | |
68 | static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder) |
69 | { |
70 | struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); |
71 | |
72 | DRM_DEBUG_DRIVER("Enabling LVDS output\n" ); |
73 | |
74 | if (lvds->panel) { |
75 | drm_panel_prepare(panel: lvds->panel); |
76 | drm_panel_enable(panel: lvds->panel); |
77 | } |
78 | } |
79 | |
80 | static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder) |
81 | { |
82 | struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); |
83 | |
84 | DRM_DEBUG_DRIVER("Disabling LVDS output\n" ); |
85 | |
86 | if (lvds->panel) { |
87 | drm_panel_disable(panel: lvds->panel); |
88 | drm_panel_unprepare(panel: lvds->panel); |
89 | } |
90 | } |
91 | |
92 | static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = { |
93 | .disable = sun4i_lvds_encoder_disable, |
94 | .enable = sun4i_lvds_encoder_enable, |
95 | }; |
96 | |
97 | int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon) |
98 | { |
99 | struct drm_encoder *encoder; |
100 | struct drm_bridge *bridge; |
101 | struct sun4i_lvds *lvds; |
102 | int ret; |
103 | |
104 | lvds = devm_kzalloc(dev: drm->dev, size: sizeof(*lvds), GFP_KERNEL); |
105 | if (!lvds) |
106 | return -ENOMEM; |
107 | encoder = &lvds->encoder; |
108 | |
109 | ret = drm_of_find_panel_or_bridge(np: tcon->dev->of_node, port: 1, endpoint: 0, |
110 | panel: &lvds->panel, bridge: &bridge); |
111 | if (ret) { |
112 | dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n" ); |
113 | return 0; |
114 | } |
115 | |
116 | drm_encoder_helper_add(encoder: &lvds->encoder, |
117 | funcs: &sun4i_lvds_enc_helper_funcs); |
118 | ret = drm_simple_encoder_init(dev: drm, encoder: &lvds->encoder, |
119 | DRM_MODE_ENCODER_LVDS); |
120 | if (ret) { |
121 | dev_err(drm->dev, "Couldn't initialise the lvds encoder\n" ); |
122 | goto err_out; |
123 | } |
124 | |
125 | /* The LVDS encoder can only work with the TCON channel 0 */ |
126 | lvds->encoder.possible_crtcs = drm_crtc_mask(crtc: &tcon->crtc->crtc); |
127 | |
128 | if (lvds->panel) { |
129 | drm_connector_helper_add(connector: &lvds->connector, |
130 | funcs: &sun4i_lvds_con_helper_funcs); |
131 | ret = drm_connector_init(dev: drm, connector: &lvds->connector, |
132 | funcs: &sun4i_lvds_con_funcs, |
133 | DRM_MODE_CONNECTOR_LVDS); |
134 | if (ret) { |
135 | dev_err(drm->dev, "Couldn't initialise the lvds connector\n" ); |
136 | goto err_cleanup_connector; |
137 | } |
138 | |
139 | drm_connector_attach_encoder(connector: &lvds->connector, |
140 | encoder: &lvds->encoder); |
141 | } |
142 | |
143 | if (bridge) { |
144 | ret = drm_bridge_attach(encoder, bridge, NULL, flags: 0); |
145 | if (ret) |
146 | goto err_cleanup_connector; |
147 | } |
148 | |
149 | return 0; |
150 | |
151 | err_cleanup_connector: |
152 | drm_encoder_cleanup(encoder: &lvds->encoder); |
153 | err_out: |
154 | return ret; |
155 | } |
156 | EXPORT_SYMBOL(sun4i_lvds_init); |
157 | |