1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2017 Free Electrons 4 * Maxime Ripard <maxime.ripard@free-electrons.com> 5 */ 6 7 #include <linux/clk.h> 8 9 #include <drm/drmP.h> 10 #include <drm/drm_atomic_helper.h> 11 #include <drm/drm_crtc_helper.h> 12 #include <drm/drm_of.h> 13 #include <drm/drm_panel.h> 14 15 #include "sun4i_crtc.h" 16 #include "sun4i_tcon.h" 17 #include "sun4i_lvds.h" 18 19 struct sun4i_lvds { 20 struct drm_connector connector; 21 struct drm_encoder encoder; 22 23 struct sun4i_tcon *tcon; 24 }; 25 26 static inline struct sun4i_lvds * 27 drm_connector_to_sun4i_lvds(struct drm_connector *connector) 28 { 29 return container_of(connector, struct sun4i_lvds, 30 connector); 31 } 32 33 static inline struct sun4i_lvds * 34 drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder) 35 { 36 return container_of(encoder, struct sun4i_lvds, 37 encoder); 38 } 39 40 static int sun4i_lvds_get_modes(struct drm_connector *connector) 41 { 42 struct sun4i_lvds *lvds = 43 drm_connector_to_sun4i_lvds(connector); 44 struct sun4i_tcon *tcon = lvds->tcon; 45 46 return drm_panel_get_modes(tcon->panel); 47 } 48 49 static struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = { 50 .get_modes = sun4i_lvds_get_modes, 51 }; 52 53 static void 54 sun4i_lvds_connector_destroy(struct drm_connector *connector) 55 { 56 struct sun4i_lvds *lvds = drm_connector_to_sun4i_lvds(connector); 57 struct sun4i_tcon *tcon = lvds->tcon; 58 59 drm_panel_detach(tcon->panel); 60 drm_connector_cleanup(connector); 61 } 62 63 static const struct drm_connector_funcs sun4i_lvds_con_funcs = { 64 .fill_modes = drm_helper_probe_single_connector_modes, 65 .destroy = sun4i_lvds_connector_destroy, 66 .reset = drm_atomic_helper_connector_reset, 67 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 68 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 69 }; 70 71 static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder) 72 { 73 struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); 74 struct sun4i_tcon *tcon = lvds->tcon; 75 76 DRM_DEBUG_DRIVER("Enabling LVDS output\n"); 77 78 if (!IS_ERR(tcon->panel)) { 79 drm_panel_prepare(tcon->panel); 80 drm_panel_enable(tcon->panel); 81 } 82 } 83 84 static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder) 85 { 86 struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); 87 struct sun4i_tcon *tcon = lvds->tcon; 88 89 DRM_DEBUG_DRIVER("Disabling LVDS output\n"); 90 91 if (!IS_ERR(tcon->panel)) { 92 drm_panel_disable(tcon->panel); 93 drm_panel_unprepare(tcon->panel); 94 } 95 } 96 97 static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = { 98 .disable = sun4i_lvds_encoder_disable, 99 .enable = sun4i_lvds_encoder_enable, 100 }; 101 102 static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = { 103 .destroy = drm_encoder_cleanup, 104 }; 105 106 int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon) 107 { 108 struct drm_encoder *encoder; 109 struct drm_bridge *bridge; 110 struct sun4i_lvds *lvds; 111 int ret; 112 113 lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL); 114 if (!lvds) 115 return -ENOMEM; 116 lvds->tcon = tcon; 117 encoder = &lvds->encoder; 118 119 ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, 120 &tcon->panel, &bridge); 121 if (ret) { 122 dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n"); 123 return 0; 124 } 125 126 drm_encoder_helper_add(&lvds->encoder, 127 &sun4i_lvds_enc_helper_funcs); 128 ret = drm_encoder_init(drm, 129 &lvds->encoder, 130 &sun4i_lvds_enc_funcs, 131 DRM_MODE_ENCODER_LVDS, 132 NULL); 133 if (ret) { 134 dev_err(drm->dev, "Couldn't initialise the lvds encoder\n"); 135 goto err_out; 136 } 137 138 /* The LVDS encoder can only work with the TCON channel 0 */ 139 lvds->encoder.possible_crtcs = BIT(drm_crtc_index(&tcon->crtc->crtc)); 140 141 if (tcon->panel) { 142 drm_connector_helper_add(&lvds->connector, 143 &sun4i_lvds_con_helper_funcs); 144 ret = drm_connector_init(drm, &lvds->connector, 145 &sun4i_lvds_con_funcs, 146 DRM_MODE_CONNECTOR_LVDS); 147 if (ret) { 148 dev_err(drm->dev, "Couldn't initialise the lvds connector\n"); 149 goto err_cleanup_connector; 150 } 151 152 drm_mode_connector_attach_encoder(&lvds->connector, 153 &lvds->encoder); 154 155 ret = drm_panel_attach(tcon->panel, &lvds->connector); 156 if (ret) { 157 dev_err(drm->dev, "Couldn't attach our panel\n"); 158 goto err_cleanup_connector; 159 } 160 } 161 162 if (bridge) { 163 ret = drm_bridge_attach(encoder, bridge, NULL); 164 if (ret) { 165 dev_err(drm->dev, "Couldn't attach our bridge\n"); 166 goto err_cleanup_connector; 167 } 168 } 169 170 return 0; 171 172 err_cleanup_connector: 173 drm_encoder_cleanup(&lvds->encoder); 174 err_out: 175 return ret; 176 } 177 EXPORT_SYMBOL(sun4i_lvds_init); 178