1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2023 Loongson Technology Corporation Limited |
4 | */ |
5 | |
6 | #include <drm/drm_atomic_helper.h> |
7 | #include <drm/drm_edid.h> |
8 | #include <drm/drm_probe_helper.h> |
9 | |
10 | #include "lsdc_drv.h" |
11 | #include "lsdc_output.h" |
12 | |
13 | /* |
14 | * The display controller in the LS7A1000 exports two DVO interfaces, thus |
15 | * external encoder is required, except connected to the DPI panel directly. |
16 | * |
17 | * ___________________ _________ |
18 | * | -------| | | |
19 | * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display | |
20 | * | _ _ -------| ^ ^ |_________| |
21 | * | | | | | +------+ | | | |
22 | * | |_| |_| | i2c6 | <--------+-------------+ |
23 | * | +------+ | |
24 | * | | |
25 | * | DC in LS7A1000 | |
26 | * | | |
27 | * | _ _ +------+ | |
28 | * | | | | | | i2c7 | <--------+-------------+ |
29 | * | |_| |_| +------+ | | | _________ |
30 | * | -------| | | | | |
31 | * | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | Panel | |
32 | * | -------| |_________| |
33 | * |___________________| |
34 | * |
35 | * Currently, we assume the external encoders connected to the DVO are |
36 | * transparent. Loongson's DVO interface can directly drive RGB888 panels. |
37 | * |
38 | * TODO: Add support for non-transparent encoders |
39 | */ |
40 | |
41 | static int ls7a1000_dpi_connector_get_modes(struct drm_connector *conn) |
42 | { |
43 | unsigned int num = 0; |
44 | struct edid *edid; |
45 | |
46 | if (conn->ddc) { |
47 | edid = drm_get_edid(connector: conn, adapter: conn->ddc); |
48 | if (edid) { |
49 | drm_connector_update_edid_property(connector: conn, edid); |
50 | num = drm_add_edid_modes(connector: conn, edid); |
51 | kfree(objp: edid); |
52 | } |
53 | |
54 | return num; |
55 | } |
56 | |
57 | num = drm_add_modes_noedid(connector: conn, hdisplay: 1920, vdisplay: 1200); |
58 | |
59 | drm_set_preferred_mode(connector: conn, hpref: 1024, vpref: 768); |
60 | |
61 | return num; |
62 | } |
63 | |
64 | static struct drm_encoder * |
65 | ls7a1000_dpi_connector_get_best_encoder(struct drm_connector *connector, |
66 | struct drm_atomic_state *state) |
67 | { |
68 | struct lsdc_output *output = connector_to_lsdc_output(connector); |
69 | |
70 | return &output->encoder; |
71 | } |
72 | |
73 | static const struct drm_connector_helper_funcs |
74 | ls7a1000_dpi_connector_helpers = { |
75 | .atomic_best_encoder = ls7a1000_dpi_connector_get_best_encoder, |
76 | .get_modes = ls7a1000_dpi_connector_get_modes, |
77 | }; |
78 | |
79 | static enum drm_connector_status |
80 | ls7a1000_dpi_connector_detect(struct drm_connector *connector, bool force) |
81 | { |
82 | struct i2c_adapter *ddc = connector->ddc; |
83 | |
84 | if (ddc) { |
85 | if (drm_probe_ddc(adapter: ddc)) |
86 | return connector_status_connected; |
87 | |
88 | return connector_status_disconnected; |
89 | } |
90 | |
91 | return connector_status_unknown; |
92 | } |
93 | |
94 | static const struct drm_connector_funcs ls7a1000_dpi_connector_funcs = { |
95 | .detect = ls7a1000_dpi_connector_detect, |
96 | .fill_modes = drm_helper_probe_single_connector_modes, |
97 | .destroy = drm_connector_cleanup, |
98 | .reset = drm_atomic_helper_connector_reset, |
99 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
100 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state |
101 | }; |
102 | |
103 | static void ls7a1000_pipe0_encoder_reset(struct drm_encoder *encoder) |
104 | { |
105 | struct drm_device *ddev = encoder->dev; |
106 | struct lsdc_device *ldev = to_lsdc(ddev); |
107 | |
108 | /* |
109 | * We need this for S3 support, screen will not lightup if don't set |
110 | * this register correctly. |
111 | */ |
112 | lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, |
113 | PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN); |
114 | } |
115 | |
116 | static void ls7a1000_pipe1_encoder_reset(struct drm_encoder *encoder) |
117 | { |
118 | struct drm_device *ddev = encoder->dev; |
119 | struct lsdc_device *ldev = to_lsdc(ddev); |
120 | |
121 | /* |
122 | * We need this for S3 support, screen will not lightup if don't set |
123 | * this register correctly. |
124 | */ |
125 | |
126 | /* DVO */ |
127 | lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, |
128 | BIT(31) | PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN); |
129 | } |
130 | |
131 | static const struct drm_encoder_funcs ls7a1000_encoder_funcs[2] = { |
132 | { |
133 | .reset = ls7a1000_pipe0_encoder_reset, |
134 | .destroy = drm_encoder_cleanup, |
135 | }, |
136 | { |
137 | .reset = ls7a1000_pipe1_encoder_reset, |
138 | .destroy = drm_encoder_cleanup, |
139 | }, |
140 | }; |
141 | |
142 | int ls7a1000_output_init(struct drm_device *ddev, |
143 | struct lsdc_display_pipe *dispipe, |
144 | struct i2c_adapter *ddc, |
145 | unsigned int index) |
146 | { |
147 | struct lsdc_output *output = &dispipe->output; |
148 | struct drm_encoder *encoder = &output->encoder; |
149 | struct drm_connector *connector = &output->connector; |
150 | int ret; |
151 | |
152 | ret = drm_encoder_init(dev: ddev, encoder, funcs: &ls7a1000_encoder_funcs[index], |
153 | DRM_MODE_ENCODER_TMDS, name: "encoder-%u" , index); |
154 | if (ret) |
155 | return ret; |
156 | |
157 | encoder->possible_crtcs = BIT(index); |
158 | |
159 | ret = drm_connector_init_with_ddc(dev: ddev, connector, |
160 | funcs: &ls7a1000_dpi_connector_funcs, |
161 | DRM_MODE_CONNECTOR_DPI, ddc); |
162 | if (ret) |
163 | return ret; |
164 | |
165 | drm_info(ddev, "display pipe-%u has a DVO\n" , index); |
166 | |
167 | drm_connector_helper_add(connector, funcs: &ls7a1000_dpi_connector_helpers); |
168 | |
169 | drm_connector_attach_encoder(connector, encoder); |
170 | |
171 | connector->polled = DRM_CONNECTOR_POLL_CONNECT | |
172 | DRM_CONNECTOR_POLL_DISCONNECT; |
173 | |
174 | connector->interlace_allowed = 0; |
175 | connector->doublescan_allowed = 0; |
176 | |
177 | return 0; |
178 | } |
179 | |