1 /*
2 * Copyright (c) 2018 Loongson Technology Co., Ltd.
3 * Authors:
4 * Chen Zhu <zhuchen@loongson.cn>
5 * Yaling Fang <fangyaling@loongson.cn>
6 * Dandan Zhang <zhangdandan@loongson.cn>
7 * Huacai Chen <chenhc@lemote.com>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
12 */
13
14 #include <linux/export.h>
15 #include <linux/i2c.h>
16 #include <linux/i2c-algo-bit.h>
17 #include <linux/pm_runtime.h>
18 #include <drm/drm_atomic_helper.h>
19 #include <drm/drm_crtc_helper.h>
20 #include <drm/drm_probe_helper.h>
21 #include <drm/drm_edid.h>
22
23 #include "loongson_drv.h"
24
25 /**
26 * loongson_connector_best_encoder
27 *
28 * @connector: point to the drm_connector structure
29 *
30 * Select the best encoder for the given connector. Used by both the helpers in
31 * drm_atomic_helper_check_modeset() and legacy CRTC helpers
32 */
loongson_connector_best_encoder(struct drm_connector *connector)33 static struct drm_encoder *loongson_connector_best_encoder(struct drm_connector
34 *connector)
35 {
36 struct drm_encoder *encoder;
37
38 /* There is only one encoder per connector */
39 drm_connector_for_each_possible_encoder(connector, encoder)
40 return encoder;
41
42 return NULL;
43 }
44
45 /**
46 * loongson_get_modes
47 *
48 * @connetcor: central DRM connector control structure
49 *
50 * Fill in all modes currently valid for the sink into the connector->probed_modes list.
51 * It should also update the EDID property by calling drm_connector_update_edid_property().
52 */
loongson_get_modes(struct drm_connector *connector)53 static int loongson_get_modes(struct drm_connector *connector)
54 {
55 int id, ret = 0;
56 enum loongson_edid_method ledid_method;
57 struct edid *edid = NULL;
58 struct loongson_connector *lconnector = to_loongson_connector(connector);
59 struct i2c_adapter *adapter = lconnector->i2c->adapter;
60
61 id = drm_connector_index(connector);
62
63 ledid_method = lconnector->edid_method;
64 switch (ledid_method) {
65 case via_i2c:
66 case via_encoder:
67 default:
68 edid = drm_get_edid(connector, adapter);
69 break;
70 case via_vbios:
71 edid = kmalloc(EDID_LENGTH * 2, GFP_KERNEL);
72 memcpy(edid, lconnector->vbios_edid, EDID_LENGTH * 2);
73 }
74
75 if (edid) {
76 drm_connector_update_edid_property(connector, edid);
77 ret = drm_add_edid_modes(connector, edid);
78 kfree(edid);
79 } else {
80 ret += drm_add_modes_noedid(connector, 1920, 1080);
81 drm_set_preferred_mode(connector, 1024, 768);
82 }
83
84 return ret;
85 }
86
is_connected(struct loongson_connector *lconnector)87 static bool is_connected(struct loongson_connector *lconnector)
88 {
89 unsigned char start = 0x0;
90 struct i2c_adapter *adapter;
91 struct i2c_msg msgs = {
92 .addr = DDC_ADDR,
93 .len = 1,
94 .flags = 0,
95 .buf = &start,
96 };
97
98 if (!lconnector->i2c)
99 return false;
100
101 adapter = lconnector->i2c->adapter;
102 if (i2c_transfer(adapter, &msgs, 1) < 1) {
103 DRM_DEBUG_KMS("display-%d not connect\n", lconnector->id);
104 return false;
105 }
106
107 return true;
108 }
109
110 /**
111 * loongson_connector_detect
112 *
113 * @connector: point to drm_connector
114 * @force: bool
115 *
116 * Check to see if anything is attached to the connector.
117 * The parameter force is set to false whilst polling,
118 * true when checking the connector due to a user request
119 */
loongson_connector_detect(struct drm_connector *connector, bool force)120 static enum drm_connector_status loongson_connector_detect(struct drm_connector
121 *connector, bool force)
122 {
123 int r;
124 enum drm_connector_status ret = connector_status_connected;
125 struct loongson_connector *lconnector = to_loongson_connector(connector);
126
127 DRM_DEBUG("loongson_connector_detect connector_id=%d, ledid_method=%d\n",
128 drm_connector_index(connector), lconnector->edid_method);
129
130 if (lconnector->edid_method != via_vbios) {
131 r = pm_runtime_get_sync(connector->dev->dev);
132 if (r < 0)
133 return connector_status_disconnected;
134
135 if (is_connected(lconnector))
136 ret = connector_status_connected;
137 else
138 ret = connector_status_disconnected;
139
140 pm_runtime_mark_last_busy(connector->dev->dev);
141 pm_runtime_put_autosuspend(connector->dev->dev);
142 }
143
144 return ret;
145 }
146
147 /**
148 * These provide the minimum set of functions required to handle a connector
149 *
150 * Helper operations for connectors.These functions are used
151 * by the atomic and legacy modeset helpers and by the probe helpers.
152 */
153 static const struct drm_connector_helper_funcs loongson_connector_helper_funcs = {
154 .get_modes = loongson_get_modes,
155 .best_encoder = loongson_connector_best_encoder,
156 };
157
158 /**
159 * These provide the minimum set of functions required to handle a connector
160 *
161 * Control connectors on a given device.
162 * The functions below allow the core DRM code to control connectors,
163 * enumerate available modes and so on.
164 */
165 static const struct drm_connector_funcs loongson_connector_funcs = {
166 .dpms = drm_helper_connector_dpms,
167 .detect = loongson_connector_detect,
168 .fill_modes = drm_helper_probe_single_connector_modes,
169 .destroy = drm_connector_cleanup,
170 .reset = drm_atomic_helper_connector_reset,
171 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
172 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
173 };
174
175 static const unsigned short normal_i2c[] = { 0x50, I2C_CLIENT_END };
176
177
178 /**
179 * loongson_connector_init
180 *
181 * @dev: drm device
182 * @connector_id:
183 *
184 * Vga is the interface between host and monitor
185 * This function is to init vga
186 */
loongson_connector_init(struct drm_device *dev, unsigned int index)187 struct drm_connector *loongson_connector_init(struct drm_device *dev, unsigned int index)
188 {
189 struct i2c_adapter *adapter;
190 struct i2c_client *ddc_client;
191 struct drm_connector *connector;
192 struct loongson_encoder *loongson_encoder;
193 struct loongson_connector *loongson_connector;
194 struct loongson_drm_device *ldev = (struct loongson_drm_device*)dev->dev_private;
195
196 const struct i2c_board_info ddc_info = {
197 .type = "ddc-dev",
198 .addr = DDC_ADDR,
199 .flags = I2C_CLASS_DDC,
200 };
201
202 loongson_encoder = ldev->mode_info[index].encoder;
203 adapter = loongson_encoder->i2c->adapter;
204 ddc_client = i2c_new_client_device(adapter, &ddc_info);
205 if (IS_ERR(ddc_client)) {
206 i2c_del_adapter(adapter);
207 DRM_ERROR("Failed to create standard ddc client\n");
208 return NULL;
209 }
210
211 loongson_connector = kzalloc(sizeof(struct loongson_connector), GFP_KERNEL);
212 if (!loongson_connector)
213 return NULL;
214
215 ldev->connector_active0 = 0;
216 ldev->connector_active1 = 0;
217 loongson_connector->id = index;
218 loongson_connector->ldev = ldev;
219 loongson_connector->type = get_connector_type(ldev, index);
220 loongson_connector->i2c_id = get_connector_i2cid(ldev, index);
221 loongson_connector->hotplug = get_hotplug_mode(ldev, index);
222 loongson_connector->edid_method = get_edid_method(ldev, index);
223 if (loongson_connector->edid_method == via_vbios)
224 loongson_connector->vbios_edid = get_vbios_edid(ldev, index);
225
226 loongson_connector->i2c = &ldev->i2c_bus[index];
227 if (!loongson_connector->i2c)
228 DRM_INFO("connector-%d match i2c-%d err\n", index,
229 loongson_connector->i2c_id);
230
231 connector = &loongson_connector->base;
232
233 drm_connector_helper_add(connector, &loongson_connector_helper_funcs);
234
235 drm_connector_init(dev, connector,
236 &loongson_connector_funcs, loongson_connector->type);
237
238 drm_connector_register(connector);
239
240 return connector;
241 }
242