blob: cc8c5483aa223e18482549184bac123b9e48ad8f [file] [log] [blame]
/*
* drivers/amlogic/drm/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/component.h>
#include <linux/amlogic/media/vout/lcd/lcd_vout.h>
#include <linux/amlogic/media/vout/lcd/lcd_notify.h>
#include "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("enable\n");
else
DRM_INFO("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_ENABLE, NULL);
lcd->lcd_drv->lcd_config->retry_enable_cnt = 0;
while (lcd->lcd_drv->lcd_config->retry_enable_flag) {
if (lcd->lcd_drv->lcd_config->retry_enable_cnt++ >=
LCD_ENABLE_RETRY_MAX)
break;
pr_info("am_drm_lcd: retry enable...%d\n",
lcd->lcd_drv->lcd_config->retry_enable_cnt);
aml_lcd_notifier_call_chain(LCD_EVENT_IF_POWER_OFF, NULL);
msleep(1000);
aml_lcd_notifier_call_chain(LCD_EVENT_IF_POWER_ON, NULL);
}
lcd->lcd_drv->lcd_config->retry_enable_cnt = 0;
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);
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;
mutex_lock(&lcd->lcd_drv->power_mutex);
aml_lcd_notifier_call_chain(LCD_EVENT_DISABLE, NULL);
mutex_unlock(&lcd->lcd_drv->power_mutex);
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;
mutex_lock(&lcd->lcd_drv->power_mutex);
aml_lcd_notifier_call_chain(LCD_EVENT_UNPREPARE, NULL);
mutex_unlock(&lcd->lcd_drv->power_mutex);
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;
mutex_lock(&lcd->lcd_drv->power_mutex);
aml_lcd_notifier_call_chain(LCD_EVENT_PREPARE, NULL);
mutex_unlock(&lcd->lcd_drv->power_mutex);
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;
/*ToDo:the below is no use,may delete it!*/
mode = drm_mode_duplicate(drm, lcd->mode);
if (!mode)
return 0;
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__);
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;
char *vout_mode;
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;
vout_mode = get_vout_mode_internal();
lcd->mode = &am_lcd_mode;
lcd->timing = &am_lcd_timing;
if (vout_mode) {
strncpy(lcd->mode->name, vout_mode, DRM_DISPLAY_MODE_LEN);
lcd->mode->name[DRM_DISPLAY_MODE_LEN - 1] = 0;
/*ToDo:change it according to lcd drivers config*/
if (!strcmp(vout_mode, "panel"))
lcd->connector.connector_type = DRM_MODE_CONNECTOR_DSI;
else
lcd->connector.connector_type = DRM_MODE_CONNECTOR_LVDS;
}
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;
DRM_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);
DRM_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);
DRM_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);
DRM_INFO("am_drm_lcd: %s %d\n", __func__, __LINE__);
}
int am_drm_lcd_notify_callback(struct notifier_block *block, unsigned long cmd,
void *para)
{
am_drm_lcd->lcd_drv = aml_lcd_get_driver();
if (!am_drm_lcd->lcd_drv) {
DRM_ERROR("invalid lcd driver, exit\n");
return -ENODEV;
}
switch (cmd) {
case VOUT_EVENT_MODE_CHANGE:
am_drm_lcd_display_mode_timing_init(am_drm_lcd);
DRM_INFO("%s:event MODE_CHANGE\n", __func__);
break;
default:
break;
}
return 0;
}
static struct notifier_block am_drm_lcd_notifier_nb = {
.notifier_call = am_drm_lcd_notify_callback,
};
static const struct of_device_id am_meson_lcd_dt_ids[] = {
{ .compatible = "amlogic,drm-lcd", },
{},
};
static int am_meson_lcd_bind(struct device *dev, struct device *master,
void *data)
{
struct drm_device *drm = data;
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;
}
/*
* register vout client for timing init,
* avoid init with null info when lcd probe with unifykey case.
*/
vout_register_client(&am_drm_lcd_notifier_nb);
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);
/* Fill connector orientation property */
drm_connector_set_panel_orientation(
connector, DRM_MODE_PANEL_ORIENTATION_LEFT_UP);
pr_info("am_drm_lcd: register ok\n");
return ret;
}
static void am_meson_lcd_unbind(struct device *dev, struct device *master,
void *data)
{
if (!am_drm_lcd)
return;
if (!am_drm_lcd->lcd_drv)
return;
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__);
}
static const struct component_ops am_meson_lcd_ops = {
.bind = am_meson_lcd_bind,
.unbind = am_meson_lcd_unbind,
};
static int am_meson_lcd_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &am_meson_lcd_ops);
}
static int am_meson_lcd_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &am_meson_lcd_ops);
return 0;
}
static struct platform_driver am_meson_lcd_pltfm_driver = {
.probe = am_meson_lcd_probe,
.remove = am_meson_lcd_remove,
.driver = {
.name = "meson-lcd",
.of_match_table = am_meson_lcd_dt_ids,
},
};
module_platform_driver(am_meson_lcd_pltfm_driver);
MODULE_AUTHOR("MultiMedia Amlogic <multimedia-sh@amlogic.com>");
MODULE_DESCRIPTION("Amlogic Meson Drm LCD driver");
MODULE_LICENSE("GPL");