18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2017 Free Electrons 48c2ecf20Sopenharmony_ci * Maxime Ripard <maxime.ripard@free-electrons.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/clk.h> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h> 108c2ecf20Sopenharmony_ci#include <drm/drm_bridge.h> 118c2ecf20Sopenharmony_ci#include <drm/drm_of.h> 128c2ecf20Sopenharmony_ci#include <drm/drm_panel.h> 138c2ecf20Sopenharmony_ci#include <drm/drm_print.h> 148c2ecf20Sopenharmony_ci#include <drm/drm_probe_helper.h> 158c2ecf20Sopenharmony_ci#include <drm/drm_simple_kms_helper.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include "sun4i_crtc.h" 188c2ecf20Sopenharmony_ci#include "sun4i_tcon.h" 198c2ecf20Sopenharmony_ci#include "sun4i_lvds.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistruct sun4i_lvds { 228c2ecf20Sopenharmony_ci struct drm_connector connector; 238c2ecf20Sopenharmony_ci struct drm_encoder encoder; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci struct drm_panel *panel; 268c2ecf20Sopenharmony_ci}; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistatic inline struct sun4i_lvds * 298c2ecf20Sopenharmony_cidrm_connector_to_sun4i_lvds(struct drm_connector *connector) 308c2ecf20Sopenharmony_ci{ 318c2ecf20Sopenharmony_ci return container_of(connector, struct sun4i_lvds, 328c2ecf20Sopenharmony_ci connector); 338c2ecf20Sopenharmony_ci} 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic inline struct sun4i_lvds * 368c2ecf20Sopenharmony_cidrm_encoder_to_sun4i_lvds(struct drm_encoder *encoder) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci return container_of(encoder, struct sun4i_lvds, 398c2ecf20Sopenharmony_ci encoder); 408c2ecf20Sopenharmony_ci} 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic int sun4i_lvds_get_modes(struct drm_connector *connector) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci struct sun4i_lvds *lvds = 458c2ecf20Sopenharmony_ci drm_connector_to_sun4i_lvds(connector); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci return drm_panel_get_modes(lvds->panel, connector); 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic const struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = { 518c2ecf20Sopenharmony_ci .get_modes = sun4i_lvds_get_modes, 528c2ecf20Sopenharmony_ci}; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic void 558c2ecf20Sopenharmony_cisun4i_lvds_connector_destroy(struct drm_connector *connector) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci drm_connector_cleanup(connector); 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic const struct drm_connector_funcs sun4i_lvds_con_funcs = { 618c2ecf20Sopenharmony_ci .fill_modes = drm_helper_probe_single_connector_modes, 628c2ecf20Sopenharmony_ci .destroy = sun4i_lvds_connector_destroy, 638c2ecf20Sopenharmony_ci .reset = drm_atomic_helper_connector_reset, 648c2ecf20Sopenharmony_ci .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 658c2ecf20Sopenharmony_ci .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 668c2ecf20Sopenharmony_ci}; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic void sun4i_lvds_encoder_enable(struct drm_encoder *encoder) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci DRM_DEBUG_DRIVER("Enabling LVDS output\n"); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci if (lvds->panel) { 758c2ecf20Sopenharmony_ci drm_panel_prepare(lvds->panel); 768c2ecf20Sopenharmony_ci drm_panel_enable(lvds->panel); 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic void sun4i_lvds_encoder_disable(struct drm_encoder *encoder) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci DRM_DEBUG_DRIVER("Disabling LVDS output\n"); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if (lvds->panel) { 878c2ecf20Sopenharmony_ci drm_panel_disable(lvds->panel); 888c2ecf20Sopenharmony_ci drm_panel_unprepare(lvds->panel); 898c2ecf20Sopenharmony_ci } 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = { 938c2ecf20Sopenharmony_ci .disable = sun4i_lvds_encoder_disable, 948c2ecf20Sopenharmony_ci .enable = sun4i_lvds_encoder_enable, 958c2ecf20Sopenharmony_ci}; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ciint sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci struct drm_encoder *encoder; 1008c2ecf20Sopenharmony_ci struct drm_bridge *bridge; 1018c2ecf20Sopenharmony_ci struct sun4i_lvds *lvds; 1028c2ecf20Sopenharmony_ci int ret; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL); 1058c2ecf20Sopenharmony_ci if (!lvds) 1068c2ecf20Sopenharmony_ci return -ENOMEM; 1078c2ecf20Sopenharmony_ci encoder = &lvds->encoder; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, 1108c2ecf20Sopenharmony_ci &lvds->panel, &bridge); 1118c2ecf20Sopenharmony_ci if (ret) { 1128c2ecf20Sopenharmony_ci dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n"); 1138c2ecf20Sopenharmony_ci return 0; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci drm_encoder_helper_add(&lvds->encoder, 1178c2ecf20Sopenharmony_ci &sun4i_lvds_enc_helper_funcs); 1188c2ecf20Sopenharmony_ci ret = drm_simple_encoder_init(drm, &lvds->encoder, 1198c2ecf20Sopenharmony_ci DRM_MODE_ENCODER_LVDS); 1208c2ecf20Sopenharmony_ci if (ret) { 1218c2ecf20Sopenharmony_ci dev_err(drm->dev, "Couldn't initialise the lvds encoder\n"); 1228c2ecf20Sopenharmony_ci goto err_out; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* The LVDS encoder can only work with the TCON channel 0 */ 1268c2ecf20Sopenharmony_ci lvds->encoder.possible_crtcs = drm_crtc_mask(&tcon->crtc->crtc); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (lvds->panel) { 1298c2ecf20Sopenharmony_ci drm_connector_helper_add(&lvds->connector, 1308c2ecf20Sopenharmony_ci &sun4i_lvds_con_helper_funcs); 1318c2ecf20Sopenharmony_ci ret = drm_connector_init(drm, &lvds->connector, 1328c2ecf20Sopenharmony_ci &sun4i_lvds_con_funcs, 1338c2ecf20Sopenharmony_ci DRM_MODE_CONNECTOR_LVDS); 1348c2ecf20Sopenharmony_ci if (ret) { 1358c2ecf20Sopenharmony_ci dev_err(drm->dev, "Couldn't initialise the lvds connector\n"); 1368c2ecf20Sopenharmony_ci goto err_cleanup_connector; 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci drm_connector_attach_encoder(&lvds->connector, 1408c2ecf20Sopenharmony_ci &lvds->encoder); 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (bridge) { 1448c2ecf20Sopenharmony_ci ret = drm_bridge_attach(encoder, bridge, NULL, 0); 1458c2ecf20Sopenharmony_ci if (ret) { 1468c2ecf20Sopenharmony_ci dev_err(drm->dev, "Couldn't attach our bridge\n"); 1478c2ecf20Sopenharmony_ci goto err_cleanup_connector; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return 0; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cierr_cleanup_connector: 1548c2ecf20Sopenharmony_ci drm_encoder_cleanup(&lvds->encoder); 1558c2ecf20Sopenharmony_cierr_out: 1568c2ecf20Sopenharmony_ci return ret; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ciEXPORT_SYMBOL(sun4i_lvds_init); 159