+
+/*
+ * Authors:
+ * Sui Jingfeng <suijingfeng@xxxxxxxxxxx>
+ */
+
+#include <drm/drm_print.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_connector.h>
+
+#include <video/videomode.h>
+#include <video/of_display_timing.h>
+
+#include "lsdc_drv.h"
+#include "lsdc_i2c.h"
+#include "lsdc_connector.h"
+
+
+static int lsdc_get_modes_from_edid(struct drm_connector *connector)
+{
+ struct drm_device *ddev = connector->dev;
+ struct lsdc_connector *lconn = to_lsdc_connector(connector);
+ struct edid *edid_p = (struct edid *)lconn->edid_data;
+ int num = drm_add_edid_modes(connector, edid_p);
+
+ if (num)
+ drm_connector_update_edid_property(connector, edid_p);
+
+ drm_dbg_kms(ddev, "%d modes added\n", num);
+
+ return num;
+}
+
+
+static int lsdc_get_modes_from_timings(struct drm_connector *connector)
+{
+ struct drm_device *ddev = connector->dev;
+ struct lsdc_connector *lconn = to_lsdc_connector(connector);
+ struct display_timings *disp_tim = lconn->disp_tim;
+ unsigned int num = 0;
+ unsigned int i;
+
+ for (i = 0; i < disp_tim->num_timings; i++) {
+ const struct display_timing *dt = disp_tim->timings[i];
+ struct drm_display_mode *mode;
+ struct videomode vm;
+
+ videomode_from_timing(dt, &vm);
+ mode = drm_mode_create(ddev);
+ if (!mode) {
+ drm_err(ddev, "failed to add mode %ux%u\n",
+ dt->hactive.typ, dt->vactive.typ);
+ continue;
+ }
+
+ drm_display_mode_from_videomode(&vm, mode);
+
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+
+ if (i == disp_tim->native_mode)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_probed_add(connector, mode);
+ num++;
+ }
+
+ drm_dbg_kms(ddev, "%d modes added\n", num);
+
+ return num;
+}
+
+
+static int lsdc_get_modes_from_ddc(struct drm_connector *connector,
+ struct i2c_adapter *ddc)
+{
+ unsigned int num = 0;
+ struct edid *edid;
+
+ edid = drm_get_edid(connector, ddc);
+ if (edid) {
+ drm_connector_update_edid_property(connector, edid);
+ num = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ return num;
+}
+
+
+static int lsdc_get_modes(struct drm_connector *connector)
+{
+ struct lsdc_connector *lconn = to_lsdc_connector(connector);
+ unsigned int num = 0;
+
+ if (lconn->has_edid)
+ return lsdc_get_modes_from_edid(connector);
+
+ if (lconn->has_disp_tim)
+ return lsdc_get_modes_from_timings(connector);
+
+ if (IS_ERR_OR_NULL(lconn->ddc) == false)
+ return lsdc_get_modes_from_ddc(connector, lconn->ddc);
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) {
+ num = drm_add_modes_noedid(connector,
+ connector->dev->mode_config.max_width,
+ connector->dev->mode_config.max_height);
+
+ drm_set_preferred_mode(connector, 1024, 768);
+
+ return num;
+ }
+
+
+ /*
+ * In case we cannot retrieve the EDIDs (broken or missing i2c
+ * bus), fallback on the XGA standards, because we are for board
+ * bringup.
+ */
+ num = drm_add_modes_noedid(connector, 1920, 1200);
+
+ /* And prefer a mode pretty much anyone can handle */
+ drm_set_preferred_mode(connector, 1024, 768);
+
+ return num;
+
+}
+
+
+static enum drm_connector_status
+lsdc_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct lsdc_connector *lconn = to_lsdc_connector(connector);
+
+ if (lconn->has_edid == true)
+ return connector_status_connected;
+
+ if (lconn->has_disp_tim == true)
+ return connector_status_connected;
+
+ if (IS_ERR_OR_NULL(lconn->ddc) == false)
+ return drm_probe_ddc(lconn->ddc);
+
+ if (lconn->ddc && drm_probe_ddc(lconn->ddc))
+ return connector_status_connected;
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
+ return connector_status_connected;
+
+ if ((connector->connector_type == DRM_MODE_CONNECTOR_DVIA) ||
+ (connector->connector_type == DRM_MODE_CONNECTOR_DVID) ||
+ (connector->connector_type == DRM_MODE_CONNECTOR_DVII))
+ return connector_status_disconnected;
+
+ if ((connector->connector_type == DRM_MODE_CONNECTOR_HDMIA) ||
+ (connector->connector_type == DRM_MODE_CONNECTOR_HDMIB))
+ return connector_status_disconnected;
+
+ return connector_status_unknown;
+}
+
+
+/*
+ * @connector: point to the drm_connector structure
+ *
+ * Clean up connector resources
+ */
+static void lsdc_connector_destroy(struct drm_connector *connector)
+{
+ struct drm_device *ddev = connector->dev;
+ struct lsdc_connector *lconn = to_lsdc_connector(connector);
+
+ if (lconn) {
+ if (lconn->ddc)
+ lsdc_destroy_i2c(connector->dev, lconn->ddc);
+
+ drm_info(ddev, "destroying connector%u\n", lconn->index);
+
+ devm_kfree(ddev->dev, lconn);
+ }
+
+ drm_connector_cleanup(connector);
+}
+
+
+static const struct drm_connector_helper_funcs lsdc_connector_helpers = {
+ .get_modes = lsdc_get_modes,
+};
+
+/*
+ * These provide the minimum set of functions required to handle a connector
+ *
+ * Control connectors on a given device.
+ *
+ * Each CRTC may have one or more connectors attached to it.
+ * The functions below allow the core DRM code to control
+ * connectors, enumerate available modes, etc.
+ */
+static const struct drm_connector_funcs lsdc_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = lsdc_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = lsdc_connector_destroy,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+
+/* Get the simple EDID data from the device tree
+ * the length must be EDID_LENGTH, since it is simple.
+ *
+ * @np: device node contain edid data
+ * @edid_data: where the edid data to store to
+ */
+static bool lsdc_get_edid_from_dtb(struct device_node *np,
+ unsigned char *edid_data)
+{
+ int length;
+ const void *prop;
+
+ if (np == NULL)
+ return false;
+
+ prop = of_get_property(np, "edid", &length);
+ if (prop && (length == EDID_LENGTH)) {
+ memcpy(edid_data, prop, EDID_LENGTH);
+ return true;
+ }
+
+ return false;
+}