--- /dev/null
+/*
+ * drivers/amlogic/drm/am_meson_lcd.c
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#include <drm/drm_modeset_helper.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_mipi_dsi.h>
+#include <video/display_timing.h>
+#include <linux/amlogic/media/vout/lcd/lcd_vout.h>
+#include <linux/amlogic/media/vout/lcd/lcd_notify.h>
+
+#include "meson_drv.h"
+#include "am_meson_lcd.h"
+
+struct am_drm_lcd_s {
+ struct drm_panel panel;
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+ struct mipi_dsi_host dsi_host;
+ struct drm_device *drm;
+ struct aml_lcd_drv_s *lcd_drv;
+ struct drm_display_mode *mode;
+ struct display_timing *timing;
+};
+
+static struct am_drm_lcd_s *am_drm_lcd;
+
+static struct drm_display_mode am_lcd_mode = {
+ .name = "panel",
+ .status = 0,
+ .clock = 74250,
+ .hdisplay = 1280,
+ .hsync_start = 1390,
+ .hsync_end = 1430,
+ .htotal = 1650,
+ .hskew = 0,
+ .vdisplay = 720,
+ .vsync_start = 725,
+ .vsync_end = 730,
+ .vtotal = 750,
+ .vscan = 0,
+ .vrefresh = 60,
+};
+
+static struct display_timing am_lcd_timing = {
+ .pixelclock = { 55000000, 65000000, 75000000 },
+ .hactive = { 1024, 1024, 1024 },
+ .hfront_porch = { 40, 40, 40 },
+ .hback_porch = { 220, 220, 220 },
+ .hsync_len = { 20, 60, 100 },
+ .vactive = { 768, 768, 768 },
+ .vfront_porch = { 7, 7, 7 },
+ .vback_porch = { 21, 21, 21 },
+ .vsync_len = { 10, 10, 10 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+/* ***************************************************************** */
+/* drm driver function */
+/* ***************************************************************** */
+#if 0
+static inline struct am_drm_lcd_s *host_to_lcd(struct mipi_dsi_host *host)
+{
+ return container_of(host, struct am_drm_lcd_s, dsi_host);
+}
+#endif
+
+static inline struct am_drm_lcd_s *con_to_lcd(struct drm_connector *con)
+{
+ return container_of(con, struct am_drm_lcd_s, connector);
+}
+
+static inline struct am_drm_lcd_s *encoder_to_lcd(struct drm_encoder *encoder)
+{
+ return container_of(encoder, struct am_drm_lcd_s, encoder);
+}
+
+static inline struct am_drm_lcd_s *panel_to_lcd(struct drm_panel *panel)
+{
+ return container_of(panel, struct am_drm_lcd_s, panel);
+}
+
+static int am_lcd_connector_get_modes(struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+ struct am_drm_lcd_s *lcd;
+ int count = 0;
+
+ lcd = con_to_lcd(connector);
+
+ pr_info("***************************************************\n");
+ pr_info("am_drm_lcd: %s: lcd mode [%s] display size: %d x %d\n",
+ __func__, lcd->mode->name,
+ lcd->mode->hdisplay, lcd->mode->vdisplay);
+
+ mode = drm_mode_duplicate(connector->dev, lcd->mode);
+ pr_info("am_drm_lcd: %s: drm mode [%s] display size: %d x %d\n",
+ __func__, mode->name, mode->hdisplay, mode->vdisplay);
+ pr_info("am_drm_lcd: %s: lcd config size: %d x %d\n",
+ __func__, lcd->lcd_drv->lcd_config->lcd_basic.h_active,
+ lcd->lcd_drv->lcd_config->lcd_basic.v_active);
+
+ drm_mode_probed_add(connector, mode);
+ count = 1;
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+ pr_info("***************************************************\n");
+
+ return count;
+}
+
+enum drm_mode_status am_lcd_connector_mode_valid(
+ struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct am_drm_lcd_s *lcd;
+
+ lcd = con_to_lcd(connector);
+ if (!lcd)
+ return MODE_ERROR;
+ if (!lcd->lcd_drv)
+ return MODE_ERROR;
+
+ pr_info("am_drm_lcd: %s: mode [%s] display size: %d x %d\n",
+ __func__, mode->name, mode->hdisplay, mode->vdisplay);
+ pr_info("am_drm_lcd: %s: lcd config size: %d x %d\n",
+ __func__, lcd->lcd_drv->lcd_config->lcd_basic.h_active,
+ lcd->lcd_drv->lcd_config->lcd_basic.v_active);
+
+ if (mode->hdisplay != lcd->lcd_drv->lcd_config->lcd_basic.h_active)
+ return MODE_BAD_WIDTH;
+ if (mode->vdisplay != lcd->lcd_drv->lcd_config->lcd_basic.v_active)
+ return MODE_BAD_WIDTH;
+
+ pr_info("am_drm_lcd: %s %d: check mode OK\n", __func__, __LINE__);
+
+ return MODE_OK;
+}
+
+
+static const struct drm_connector_helper_funcs am_lcd_connector_helper_funcs = {
+ .get_modes = am_lcd_connector_get_modes,
+ .mode_valid = am_lcd_connector_mode_valid,
+ //.best_encoder
+ //.atomic_best_encoder
+};
+
+static enum drm_connector_status am_lcd_connector_detect(
+ struct drm_connector *connector, bool force)
+{
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+ return connector_status_connected;
+}
+
+#if 0
+static const struct drm_connector_funcs am_lcd_connector_funcs = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .detect = am_lcd_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+#else
+
+static int am_lcd_connector_dpms(struct drm_connector *connector, int mode)
+{
+ int ret = 0;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ ret = drm_atomic_helper_connector_dpms(connector, mode);
+ return ret;
+}
+
+static int am_lcd_connector_fill_modes(struct drm_connector *connector,
+ uint32_t maxX, uint32_t maxY)
+{
+ int count = 0;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+ count = drm_helper_probe_single_connector_modes(connector, maxX, maxY);
+ pr_info("am_drm_lcd: %s %d: count=%d\n", __func__, __LINE__, count);
+ return count;
+}
+
+static void am_lcd_connector_destroy(struct drm_connector *connector)
+{
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ drm_connector_cleanup(connector);
+}
+
+static void am_lcd_connector_reset(struct drm_connector *connector)
+{
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ drm_atomic_helper_connector_reset(connector);
+}
+
+static struct drm_connector_state *am_lcd_connector_duplicate_state(
+ struct drm_connector *connector)
+{
+ struct drm_connector_state *state;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ state = drm_atomic_helper_connector_duplicate_state(connector);
+ return state;
+}
+
+static void am_lcd_connector_destroy_state(struct drm_connector *connector,
+ struct drm_connector_state *state)
+{
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ drm_atomic_helper_connector_destroy_state(connector, state);
+}
+
+static const struct drm_connector_funcs am_lcd_connector_funcs = {
+ .dpms = am_lcd_connector_dpms,
+ .detect = am_lcd_connector_detect,
+ .fill_modes = am_lcd_connector_fill_modes,
+ .destroy = am_lcd_connector_destroy,
+ .reset = am_lcd_connector_reset,
+ .atomic_duplicate_state = am_lcd_connector_duplicate_state,
+ .atomic_destroy_state = am_lcd_connector_destroy_state,
+};
+#endif
+
+static void am_lcd_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+}
+
+static void am_lcd_encoder_enable(struct drm_encoder *encoder)
+{
+ enum vmode_e vmode = get_current_vmode();
+ struct am_drm_lcd_s *lcd = encoder_to_lcd(encoder);
+
+ if (!lcd)
+ return;
+ if (!lcd->lcd_drv)
+ return;
+
+ if (vmode == VMODE_LCD)
+ DRM_INFO("am_lcd_encoder_enable\n");
+ else
+ DRM_INFO("am_lcd_encoder_enable fail! vmode:%d\n", vmode);
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+ vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE_PRE, &vmode);
+ mutex_lock(&lcd->lcd_drv->power_mutex);
+ aml_lcd_notifier_call_chain(LCD_EVENT_PREPARE, NULL);
+ aml_lcd_notifier_call_chain(LCD_EVENT_ENABLE, NULL);
+ mutex_unlock(&lcd->lcd_drv->power_mutex);
+ vout_notifier_call_chain(VOUT_EVENT_MODE_CHANGE, &vmode);
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+}
+
+static void am_lcd_encoder_disable(struct drm_encoder *encoder)
+{
+ struct am_drm_lcd_s *lcd = encoder_to_lcd(encoder);
+
+ if (!lcd)
+ return;
+ if (!lcd->lcd_drv)
+ return;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+ mutex_lock(&lcd->lcd_drv->power_mutex);
+ aml_lcd_notifier_call_chain(LCD_EVENT_DISABLE, NULL);
+ aml_lcd_notifier_call_chain(LCD_EVENT_UNPREPARE, NULL);
+ mutex_unlock(&lcd->lcd_drv->power_mutex);
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+}
+
+static void am_lcd_encoder_commit(struct drm_encoder *encoder)
+{
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+}
+
+static int am_lcd_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs am_lcd_encoder_helper_funcs = {
+ .commit = am_lcd_encoder_commit,
+ .mode_set = am_lcd_encoder_mode_set,
+ .enable = am_lcd_encoder_enable,
+ .disable = am_lcd_encoder_disable,
+ .atomic_check = am_lcd_encoder_atomic_check,
+};
+
+static const struct drm_encoder_funcs am_lcd_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static int am_lcd_disable(struct drm_panel *panel)
+{
+ struct am_drm_lcd_s *lcd = panel_to_lcd(panel);
+
+ if (!lcd)
+ return -ENODEV;
+ if (!lcd->lcd_drv)
+ return -ENODEV;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ mutex_lock(&lcd->lcd_drv->power_mutex);
+ aml_lcd_notifier_call_chain(LCD_EVENT_DISABLE, NULL);
+ mutex_unlock(&lcd->lcd_drv->power_mutex);
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ return 0;
+}
+
+static int am_lcd_unprepare(struct drm_panel *panel)
+{
+ struct am_drm_lcd_s *lcd = panel_to_lcd(panel);
+
+ if (!lcd)
+ return -ENODEV;
+ if (!lcd->lcd_drv)
+ return -ENODEV;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ mutex_lock(&lcd->lcd_drv->power_mutex);
+ aml_lcd_notifier_call_chain(LCD_EVENT_UNPREPARE, NULL);
+ mutex_unlock(&lcd->lcd_drv->power_mutex);
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ return 0;
+}
+
+static int am_lcd_prepare(struct drm_panel *panel)
+{
+ struct am_drm_lcd_s *lcd = panel_to_lcd(panel);
+
+ if (!lcd)
+ return -ENODEV;
+ if (!lcd->lcd_drv)
+ return -ENODEV;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ mutex_lock(&lcd->lcd_drv->power_mutex);
+ aml_lcd_notifier_call_chain(LCD_EVENT_PREPARE, NULL);
+ mutex_unlock(&lcd->lcd_drv->power_mutex);
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ return 0;
+}
+
+static int am_lcd_enable(struct drm_panel *panel)
+{
+ struct am_drm_lcd_s *lcd = panel_to_lcd(panel);
+
+ if (!lcd)
+ return -ENODEV;
+ if (!lcd->lcd_drv)
+ return -ENODEV;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ mutex_lock(&lcd->lcd_drv->power_mutex);
+ aml_lcd_notifier_call_chain(LCD_EVENT_ENABLE, NULL);
+ mutex_unlock(&lcd->lcd_drv->power_mutex);
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ return 0;
+}
+
+static int am_lcd_get_modes(struct drm_panel *panel)
+{
+ struct am_drm_lcd_s *lcd = panel_to_lcd(panel);
+ struct drm_connector *connector = panel->connector;
+ struct drm_device *drm = panel->drm;
+ struct drm_display_mode *mode;
+ struct lcd_config_s *pconf;
+
+ if (!lcd->mode)
+ return 0;
+
+ mode = drm_mode_duplicate(drm, lcd->mode);
+ if (!mode) {
+ pr_err("error: am_drm_lcd: failed to add mode %ux%u@%u\n",
+ mode->hdisplay, mode->vdisplay, mode->vrefresh);
+ }
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_set_name(mode);
+
+ drm_mode_probed_add(connector, mode);
+
+ pconf = lcd->lcd_drv->lcd_config;
+ connector->display_info.bpc = pconf->lcd_basic.lcd_bits * 3;
+ connector->display_info.width_mm = pconf->lcd_basic.screen_width;
+ connector->display_info.height_mm = pconf->lcd_basic.screen_height;
+
+ connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ return 1;
+}
+
+static int am_lcd_get_timings(struct drm_panel *panel,
+ unsigned int num_timings,
+ struct display_timing *timings)
+{
+ struct am_drm_lcd_s *lcd = panel_to_lcd(panel);
+
+ if (!lcd)
+ return 0;
+ if (!lcd->timing)
+ return 0;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ num_timings = 1;
+
+ if (timings)
+ memcpy(&timings[0], lcd->timing, sizeof(struct display_timing));
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ return 1;
+}
+
+static const struct drm_panel_funcs am_drm_lcd_funcs = {
+ .disable = am_lcd_disable,
+ .unprepare = am_lcd_unprepare,
+ .prepare = am_lcd_prepare,
+ .enable = am_lcd_enable,
+ .get_modes = am_lcd_get_modes,
+ .get_timings = am_lcd_get_timings,
+};
+
+static void am_drm_lcd_display_mode_timing_init(struct am_drm_lcd_s *lcd)
+{
+ struct lcd_config_s *pconf;
+ unsigned short tmp;
+
+ if (!lcd->lcd_drv) {
+ pr_info("invalid lcd driver\n");
+ return;
+ }
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ pconf = lcd->lcd_drv->lcd_config;
+
+ lcd->mode = &am_lcd_mode;
+ lcd->timing = &am_lcd_timing;
+
+ lcd->mode->clock = pconf->lcd_timing.lcd_clk / 1000;
+ lcd->mode->hdisplay = pconf->lcd_basic.h_active;
+ tmp = pconf->lcd_basic.h_period - pconf->lcd_basic.h_active -
+ pconf->lcd_timing.hsync_bp;
+ lcd->mode->hsync_start = pconf->lcd_basic.h_active + tmp -
+ pconf->lcd_timing.hsync_width;
+ lcd->mode->hsync_end = pconf->lcd_basic.h_active + tmp;
+ lcd->mode->htotal = pconf->lcd_basic.h_period;
+ lcd->mode->vdisplay = pconf->lcd_basic.v_active;
+ tmp = pconf->lcd_basic.v_period - pconf->lcd_basic.v_active -
+ pconf->lcd_timing.vsync_bp;
+ lcd->mode->vsync_start = pconf->lcd_basic.v_active + tmp -
+ pconf->lcd_timing.vsync_width;
+ lcd->mode->vsync_end = pconf->lcd_basic.v_active + tmp;
+ lcd->mode->vtotal = pconf->lcd_basic.v_period;
+ lcd->mode->width_mm = pconf->lcd_basic.screen_width;
+ lcd->mode->height_mm = pconf->lcd_basic.screen_height;
+ lcd->mode->vrefresh = pconf->lcd_timing.sync_duration_num /
+ pconf->lcd_timing.sync_duration_den;
+
+ lcd->timing->pixelclock.min = pconf->lcd_timing.lcd_clk;
+ lcd->timing->pixelclock.typ = pconf->lcd_timing.lcd_clk;
+ lcd->timing->pixelclock.max = pconf->lcd_timing.lcd_clk;
+ lcd->timing->hactive.min = pconf->lcd_basic.h_active;
+ lcd->timing->hactive.typ = pconf->lcd_basic.h_active;
+ lcd->timing->hactive.max = pconf->lcd_basic.h_active;
+ tmp = pconf->lcd_basic.h_period - pconf->lcd_basic.h_active -
+ pconf->lcd_timing.hsync_bp - pconf->lcd_timing.hsync_width;
+ lcd->timing->hfront_porch.min = tmp;
+ lcd->timing->hfront_porch.typ = tmp;
+ lcd->timing->hfront_porch.max = tmp;
+ lcd->timing->hback_porch.min = pconf->lcd_timing.hsync_bp;
+ lcd->timing->hback_porch.typ = pconf->lcd_timing.hsync_bp;
+ lcd->timing->hback_porch.max = pconf->lcd_timing.hsync_bp;
+ lcd->timing->hsync_len.min = pconf->lcd_timing.hsync_width;
+ lcd->timing->hsync_len.typ = pconf->lcd_timing.hsync_width;
+ lcd->timing->hsync_len.max = pconf->lcd_timing.hsync_width;
+ lcd->timing->vactive.min = pconf->lcd_basic.v_active;
+ lcd->timing->vactive.typ = pconf->lcd_basic.v_active;
+ lcd->timing->vactive.max = pconf->lcd_basic.v_active;
+ tmp = pconf->lcd_basic.v_period - pconf->lcd_basic.v_active -
+ pconf->lcd_timing.vsync_bp - pconf->lcd_timing.vsync_width;
+ lcd->timing->vfront_porch.min = tmp;
+ lcd->timing->vfront_porch.typ = tmp;
+ lcd->timing->vfront_porch.max = tmp;
+ lcd->timing->vback_porch.min = pconf->lcd_timing.vsync_bp;
+ lcd->timing->vback_porch.typ = pconf->lcd_timing.vsync_bp;
+ lcd->timing->vback_porch.max = pconf->lcd_timing.vsync_bp;
+ lcd->timing->vsync_len.min = pconf->lcd_timing.vsync_width;
+ lcd->timing->vsync_len.typ = pconf->lcd_timing.vsync_width;
+ lcd->timing->vsync_len.max = pconf->lcd_timing.vsync_width;
+
+ pr_info("am_drm_lcd: %s: lcd config:\n"
+ "lcd_clk %d\n"
+ "h_active %d\n"
+ "v_active %d\n"
+ "screen_width %d\n"
+ "screen_height %d\n"
+ "sync_duration_den %d\n"
+ "sync_duration_num %d\n",
+ __func__,
+ lcd->lcd_drv->lcd_config->lcd_timing.lcd_clk,
+ lcd->lcd_drv->lcd_config->lcd_basic.h_active,
+ lcd->lcd_drv->lcd_config->lcd_basic.v_active,
+ lcd->lcd_drv->lcd_config->lcd_basic.screen_width,
+ lcd->lcd_drv->lcd_config->lcd_basic.screen_height,
+ lcd->lcd_drv->lcd_config->lcd_timing.sync_duration_den,
+ lcd->lcd_drv->lcd_config->lcd_timing.sync_duration_num);
+ pr_info("am_drm_lcd: %s: display mode:\n"
+ "clock %d\n"
+ "hdisplay %d\n"
+ "vdisplay %d\n"
+ "width_mm %d\n"
+ "height_mm %d\n"
+ "vrefresh %d\n",
+ __func__,
+ lcd->mode->clock,
+ lcd->mode->hdisplay,
+ lcd->mode->vdisplay,
+ lcd->mode->width_mm,
+ lcd->mode->height_mm,
+ lcd->mode->vrefresh);
+ pr_info("am_drm_lcd: %s: timing:\n"
+ "pixelclock %d\n"
+ "hactive %d\n"
+ "vactive %d\n",
+ __func__,
+ lcd->timing->pixelclock.typ,
+ lcd->timing->hactive.typ,
+ lcd->timing->vactive.typ);
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+}
+
+int am_drm_lcd_register(struct drm_device *drm)
+{
+ struct drm_connector *connector;
+ struct drm_encoder *encoder;
+ int encoder_type, connector_type;
+ int ret = 0;
+
+ am_drm_lcd = kzalloc(sizeof(*am_drm_lcd), GFP_KERNEL);
+ if (!am_drm_lcd)
+ return -ENOMEM;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ am_drm_lcd->lcd_drv = aml_lcd_get_driver();
+ if (!am_drm_lcd->lcd_drv) {
+ pr_err("invalid lcd driver, exit\n");
+ return -ENODEV;
+ }
+
+ am_drm_lcd_display_mode_timing_init(am_drm_lcd);
+
+ drm_panel_init(&am_drm_lcd->panel);
+ am_drm_lcd->panel.dev = NULL;
+ am_drm_lcd->panel.funcs = &am_drm_lcd_funcs;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ ret = drm_panel_add(&am_drm_lcd->panel);
+ if (ret < 0)
+ return ret;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ am_drm_lcd->drm = drm;
+
+ encoder = &am_drm_lcd->encoder;
+ connector = &am_drm_lcd->connector;
+ encoder_type = DRM_MODE_ENCODER_LVDS;
+ connector_type = DRM_MODE_CONNECTOR_LVDS;
+
+ /* Encoder */
+ drm_encoder_helper_add(encoder, &am_lcd_encoder_helper_funcs);
+ ret = drm_encoder_init(drm, encoder, &am_lcd_encoder_funcs,
+ encoder_type, "am_lcd_encoder");
+ if (ret) {
+ pr_err("error: am_drm_lcd: Failed to init lcd encoder\n");
+ return ret;
+ }
+ pr_info("am_drm_lcd: %s %d: encoder possible_crtcs=%d\n",
+ __func__, __LINE__, encoder->possible_crtcs);
+
+ /* Connector */
+ drm_connector_helper_add(connector, &am_lcd_connector_helper_funcs);
+ ret = drm_connector_init(drm, connector, &am_lcd_connector_funcs,
+ connector_type);
+ if (ret) {
+ pr_err("error: am_drm_lcd: Failed to init lcd connector\n");
+ return ret;
+ }
+
+ /* force possible_crtcs */
+ encoder->possible_crtcs = BIT(0);
+
+ drm_mode_connector_attach_encoder(connector, encoder);
+
+ pr_info("am_drm_lcd: register ok\n");
+
+ return ret;
+}
+
+int am_drm_lcd_unregister(struct drm_device *drm)
+{
+ int ret = 0;
+
+ if (!am_drm_lcd)
+ return -ENODEV;
+
+ if (!am_drm_lcd->lcd_drv)
+ return -ENODEV;
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ drm_panel_detach(&am_drm_lcd->panel);
+ drm_panel_remove(&am_drm_lcd->panel);
+
+ pr_info("am_drm_lcd: %s %d\n", __func__, __LINE__);
+
+ return ret;
+}