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 */
33static 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 */
53static 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
87static 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 */
120static 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 */
153static 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 */
165static 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
175static 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 */
187struct 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