18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Copyright (c) 2018 Loongson Technology Co., Ltd.
38c2ecf20Sopenharmony_ci * Authors:
48c2ecf20Sopenharmony_ci *	Chen Zhu <zhuchen@loongson.cn>
58c2ecf20Sopenharmony_ci *	Yaling Fang <fangyaling@loongson.cn>
68c2ecf20Sopenharmony_ci *	Dandan Zhang <zhangdandan@loongson.cn>
78c2ecf20Sopenharmony_ci *	Huacai Chen <chenhc@lemote.com>
88c2ecf20Sopenharmony_ci * This program is free software; you can redistribute  it and/or modify it
98c2ecf20Sopenharmony_ci * under  the terms of  the GNU General  Public License as published by the
108c2ecf20Sopenharmony_ci * Free Software Foundation;  either version 2 of the  License, or (at your
118c2ecf20Sopenharmony_ci * option) any later version.
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/export.h>
158c2ecf20Sopenharmony_ci#include <linux/i2c.h>
168c2ecf20Sopenharmony_ci#include <linux/i2c-algo-bit.h>
178c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h>
188c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h>
198c2ecf20Sopenharmony_ci#include <drm/drm_crtc_helper.h>
208c2ecf20Sopenharmony_ci#include <drm/drm_probe_helper.h>
218c2ecf20Sopenharmony_ci#include <drm/drm_edid.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include "loongson_drv.h"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/**
268c2ecf20Sopenharmony_ci * loongson_connector_best_encoder
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci * @connector: point to the drm_connector structure
298c2ecf20Sopenharmony_ci *
308c2ecf20Sopenharmony_ci * Select the best encoder for the given connector. Used by both the helpers in
318c2ecf20Sopenharmony_ci * drm_atomic_helper_check_modeset() and legacy CRTC helpers
328c2ecf20Sopenharmony_ci */
338c2ecf20Sopenharmony_cistatic struct drm_encoder *loongson_connector_best_encoder(struct drm_connector
348c2ecf20Sopenharmony_ci						  *connector)
358c2ecf20Sopenharmony_ci{
368c2ecf20Sopenharmony_ci	struct drm_encoder *encoder;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	/* There is only one encoder per connector */
398c2ecf20Sopenharmony_ci	drm_connector_for_each_possible_encoder(connector, encoder)
408c2ecf20Sopenharmony_ci		return encoder;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	return NULL;
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/**
468c2ecf20Sopenharmony_ci * loongson_get_modes
478c2ecf20Sopenharmony_ci *
488c2ecf20Sopenharmony_ci * @connetcor: central DRM connector control structure
498c2ecf20Sopenharmony_ci *
508c2ecf20Sopenharmony_ci * Fill in all modes currently valid for the sink into the connector->probed_modes list.
518c2ecf20Sopenharmony_ci * It should also update the EDID property by calling drm_connector_update_edid_property().
528c2ecf20Sopenharmony_ci */
538c2ecf20Sopenharmony_cistatic int loongson_get_modes(struct drm_connector *connector)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	int id, ret = 0;
568c2ecf20Sopenharmony_ci	enum loongson_edid_method ledid_method;
578c2ecf20Sopenharmony_ci	struct edid *edid = NULL;
588c2ecf20Sopenharmony_ci	struct loongson_connector *lconnector = to_loongson_connector(connector);
598c2ecf20Sopenharmony_ci	struct i2c_adapter *adapter = lconnector->i2c->adapter;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	id = drm_connector_index(connector);
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	ledid_method = lconnector->edid_method;
648c2ecf20Sopenharmony_ci	switch (ledid_method) {
658c2ecf20Sopenharmony_ci	case via_i2c:
668c2ecf20Sopenharmony_ci	case via_encoder:
678c2ecf20Sopenharmony_ci	default:
688c2ecf20Sopenharmony_ci		edid = drm_get_edid(connector, adapter);
698c2ecf20Sopenharmony_ci		break;
708c2ecf20Sopenharmony_ci	case via_vbios:
718c2ecf20Sopenharmony_ci		edid = kmalloc(EDID_LENGTH * 2, GFP_KERNEL);
728c2ecf20Sopenharmony_ci		memcpy(edid, lconnector->vbios_edid, EDID_LENGTH * 2);
738c2ecf20Sopenharmony_ci	}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	if (edid) {
768c2ecf20Sopenharmony_ci		drm_connector_update_edid_property(connector, edid);
778c2ecf20Sopenharmony_ci		ret = drm_add_edid_modes(connector, edid);
788c2ecf20Sopenharmony_ci		kfree(edid);
798c2ecf20Sopenharmony_ci	} else {
808c2ecf20Sopenharmony_ci		ret += drm_add_modes_noedid(connector, 1920, 1080);
818c2ecf20Sopenharmony_ci		drm_set_preferred_mode(connector, 1024, 768);
828c2ecf20Sopenharmony_ci	}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	return ret;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic bool is_connected(struct loongson_connector *lconnector)
888c2ecf20Sopenharmony_ci{
898c2ecf20Sopenharmony_ci	unsigned char start = 0x0;
908c2ecf20Sopenharmony_ci	struct i2c_adapter *adapter;
918c2ecf20Sopenharmony_ci	struct i2c_msg msgs = {
928c2ecf20Sopenharmony_ci		.addr = DDC_ADDR,
938c2ecf20Sopenharmony_ci		.len = 1,
948c2ecf20Sopenharmony_ci		.flags = 0,
958c2ecf20Sopenharmony_ci		.buf = &start,
968c2ecf20Sopenharmony_ci	};
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	if (!lconnector->i2c)
998c2ecf20Sopenharmony_ci		return false;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	adapter = lconnector->i2c->adapter;
1028c2ecf20Sopenharmony_ci	if (i2c_transfer(adapter, &msgs, 1) < 1) {
1038c2ecf20Sopenharmony_ci		DRM_DEBUG_KMS("display-%d not connect\n", lconnector->id);
1048c2ecf20Sopenharmony_ci		return false;
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	return true;
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci/**
1118c2ecf20Sopenharmony_ci * loongson_connector_detect
1128c2ecf20Sopenharmony_ci *
1138c2ecf20Sopenharmony_ci * @connector: point to drm_connector
1148c2ecf20Sopenharmony_ci * @force: bool
1158c2ecf20Sopenharmony_ci *
1168c2ecf20Sopenharmony_ci * Check to see if anything is attached to the connector.
1178c2ecf20Sopenharmony_ci * The parameter force is set to false whilst polling,
1188c2ecf20Sopenharmony_ci * true when checking the connector due to a user request
1198c2ecf20Sopenharmony_ci */
1208c2ecf20Sopenharmony_cistatic enum drm_connector_status loongson_connector_detect(struct drm_connector
1218c2ecf20Sopenharmony_ci						   *connector, bool force)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	int r;
1248c2ecf20Sopenharmony_ci	enum drm_connector_status ret = connector_status_connected;
1258c2ecf20Sopenharmony_ci	struct loongson_connector *lconnector = to_loongson_connector(connector);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	DRM_DEBUG("loongson_connector_detect connector_id=%d, ledid_method=%d\n",
1288c2ecf20Sopenharmony_ci			drm_connector_index(connector), lconnector->edid_method);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	if (lconnector->edid_method != via_vbios) {
1318c2ecf20Sopenharmony_ci		r = pm_runtime_get_sync(connector->dev->dev);
1328c2ecf20Sopenharmony_ci		if (r < 0)
1338c2ecf20Sopenharmony_ci			return connector_status_disconnected;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci		if (is_connected(lconnector))
1368c2ecf20Sopenharmony_ci			ret = connector_status_connected;
1378c2ecf20Sopenharmony_ci		else
1388c2ecf20Sopenharmony_ci			ret = connector_status_disconnected;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci		pm_runtime_mark_last_busy(connector->dev->dev);
1418c2ecf20Sopenharmony_ci		pm_runtime_put_autosuspend(connector->dev->dev);
1428c2ecf20Sopenharmony_ci	}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	return ret;
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci/**
1488c2ecf20Sopenharmony_ci * These provide the minimum set of functions required to handle a connector
1498c2ecf20Sopenharmony_ci *
1508c2ecf20Sopenharmony_ci * Helper operations for connectors.These functions are used
1518c2ecf20Sopenharmony_ci * by the atomic and legacy modeset helpers and by the probe helpers.
1528c2ecf20Sopenharmony_ci */
1538c2ecf20Sopenharmony_cistatic const struct drm_connector_helper_funcs loongson_connector_helper_funcs = {
1548c2ecf20Sopenharmony_ci        .get_modes = loongson_get_modes,
1558c2ecf20Sopenharmony_ci        .best_encoder = loongson_connector_best_encoder,
1568c2ecf20Sopenharmony_ci};
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci/**
1598c2ecf20Sopenharmony_ci * These provide the minimum set of functions required to handle a connector
1608c2ecf20Sopenharmony_ci *
1618c2ecf20Sopenharmony_ci * Control connectors on a given device.
1628c2ecf20Sopenharmony_ci * The functions below allow the core DRM code to control connectors,
1638c2ecf20Sopenharmony_ci * enumerate available modes and so on.
1648c2ecf20Sopenharmony_ci */
1658c2ecf20Sopenharmony_cistatic const struct drm_connector_funcs loongson_connector_funcs = {
1668c2ecf20Sopenharmony_ci	.dpms = drm_helper_connector_dpms,
1678c2ecf20Sopenharmony_ci	.detect = loongson_connector_detect,
1688c2ecf20Sopenharmony_ci	.fill_modes = drm_helper_probe_single_connector_modes,
1698c2ecf20Sopenharmony_ci	.destroy = drm_connector_cleanup,
1708c2ecf20Sopenharmony_ci	.reset = drm_atomic_helper_connector_reset,
1718c2ecf20Sopenharmony_ci	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
1728c2ecf20Sopenharmony_ci	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
1738c2ecf20Sopenharmony_ci};
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cistatic const unsigned short normal_i2c[] = { 0x50, I2C_CLIENT_END };
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci/**
1798c2ecf20Sopenharmony_ci * loongson_connector_init
1808c2ecf20Sopenharmony_ci *
1818c2ecf20Sopenharmony_ci * @dev: drm device
1828c2ecf20Sopenharmony_ci * @connector_id:
1838c2ecf20Sopenharmony_ci *
1848c2ecf20Sopenharmony_ci * Vga is the interface between host and monitor
1858c2ecf20Sopenharmony_ci * This function is to init vga
1868c2ecf20Sopenharmony_ci */
1878c2ecf20Sopenharmony_cistruct drm_connector *loongson_connector_init(struct drm_device *dev, unsigned int index)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	struct i2c_adapter *adapter;
1908c2ecf20Sopenharmony_ci	struct i2c_client *ddc_client;
1918c2ecf20Sopenharmony_ci	struct drm_connector *connector;
1928c2ecf20Sopenharmony_ci	struct loongson_encoder *loongson_encoder;
1938c2ecf20Sopenharmony_ci	struct loongson_connector *loongson_connector;
1948c2ecf20Sopenharmony_ci	struct loongson_drm_device *ldev = (struct loongson_drm_device*)dev->dev_private;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	const struct i2c_board_info ddc_info = {
1978c2ecf20Sopenharmony_ci		.type = "ddc-dev",
1988c2ecf20Sopenharmony_ci		.addr = DDC_ADDR,
1998c2ecf20Sopenharmony_ci		.flags = I2C_CLASS_DDC,
2008c2ecf20Sopenharmony_ci	};
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	loongson_encoder = ldev->mode_info[index].encoder;
2038c2ecf20Sopenharmony_ci	adapter = loongson_encoder->i2c->adapter;
2048c2ecf20Sopenharmony_ci	ddc_client = i2c_new_client_device(adapter, &ddc_info);
2058c2ecf20Sopenharmony_ci	if (IS_ERR(ddc_client)) {
2068c2ecf20Sopenharmony_ci		i2c_del_adapter(adapter);
2078c2ecf20Sopenharmony_ci		DRM_ERROR("Failed to create standard ddc client\n");
2088c2ecf20Sopenharmony_ci		return NULL;
2098c2ecf20Sopenharmony_ci	}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	loongson_connector = kzalloc(sizeof(struct loongson_connector), GFP_KERNEL);
2128c2ecf20Sopenharmony_ci	if (!loongson_connector)
2138c2ecf20Sopenharmony_ci		return NULL;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	ldev->connector_active0 = 0;
2168c2ecf20Sopenharmony_ci	ldev->connector_active1 = 0;
2178c2ecf20Sopenharmony_ci	loongson_connector->id = index;
2188c2ecf20Sopenharmony_ci	loongson_connector->ldev = ldev;
2198c2ecf20Sopenharmony_ci	loongson_connector->type = get_connector_type(ldev, index);
2208c2ecf20Sopenharmony_ci	loongson_connector->i2c_id = get_connector_i2cid(ldev, index);
2218c2ecf20Sopenharmony_ci	loongson_connector->hotplug = get_hotplug_mode(ldev, index);
2228c2ecf20Sopenharmony_ci	loongson_connector->edid_method = get_edid_method(ldev, index);
2238c2ecf20Sopenharmony_ci	if (loongson_connector->edid_method == via_vbios)
2248c2ecf20Sopenharmony_ci		loongson_connector->vbios_edid = get_vbios_edid(ldev, index);
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	loongson_connector->i2c = &ldev->i2c_bus[index];
2278c2ecf20Sopenharmony_ci	if (!loongson_connector->i2c)
2288c2ecf20Sopenharmony_ci		DRM_INFO("connector-%d match i2c-%d err\n", index,
2298c2ecf20Sopenharmony_ci			 loongson_connector->i2c_id);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	connector = &loongson_connector->base;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	drm_connector_helper_add(connector, &loongson_connector_helper_funcs);
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	drm_connector_init(dev, connector,
2368c2ecf20Sopenharmony_ci			   &loongson_connector_funcs, loongson_connector->type);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	drm_connector_register(connector);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	return connector;
2418c2ecf20Sopenharmony_ci}
242