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/drm_atomic_helper.h> 10 #include <drm/drm_bridge.h> 11 #include <drm/drm_encoder.h> 12 #include <drm/drm_of.h> 13 #include <drm/drm_panel.h> 14 #include <drm/drm_print.h> 15 #include <drm/drm_probe_helper.h> 16 17 #include "sun4i_crtc.h" 18 #include "sun4i_tcon.h" 19 #include "sun4i_lvds.h" 20 21 static void sun4i_panel_put_action(void *data) 22 { 23 drm_panel_put(data); 24 } 25 26 struct sun4i_lvds { 27 struct drm_connector connector; 28 struct drm_encoder encoder; 29 30 struct drm_panel *panel; 31 }; 32 33 static inline struct sun4i_lvds * 34 drm_connector_to_sun4i_lvds(struct drm_connector *connector) 35 { 36 return container_of(connector, struct sun4i_lvds, 37 connector); 38 } 39 40 static inline struct sun4i_lvds * 41 drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder) 42 { 43 return container_of(encoder, struct sun4i_lvds, 44 encoder); 45 } 46 47 static int sun4i_lvds_get_modes(struct drm_connector *connector) 48 { 49 struct sun4i_lvds *lvds = 50 drm_connector_to_sun4i_lvds(connector); 51 52 return drm_panel_get_modes(lvds->panel, connector); 53 } 54 55 static const struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = { 56 .get_modes = sun4i_lvds_get_modes, 57 }; 58 59 static void 60 sun4i_lvds_connector_destroy(struct drm_connector *connector) 61 { 62 drm_connector_cleanup(connector); 63 } 64 65 static const struct drm_connector_funcs sun4i_lvds_con_funcs = { 66 .fill_modes = drm_helper_probe_single_connector_modes, 67 .destroy = sun4i_lvds_connector_destroy, 68 .reset = drm_atomic_helper_connector_reset, 69 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 70 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 71 }; 72 73 static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder) 74 { 75 struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); 76 77 DRM_DEBUG_DRIVER("Enabling LVDS output\n"); 78 79 if (lvds->panel) { 80 drm_panel_prepare(lvds->panel); 81 drm_panel_enable(lvds->panel); 82 } 83 } 84 85 static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder) 86 { 87 struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); 88 89 DRM_DEBUG_DRIVER("Disabling LVDS output\n"); 90 91 if (lvds->panel) { 92 drm_panel_disable(lvds->panel); 93 drm_panel_unprepare(lvds->panel); 94 } 95 } 96 97 static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = { 98 .destroy = drm_encoder_cleanup, 99 }; 100 101 static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = { 102 .disable = sun4i_lvds_encoder_disable, 103 .enable = sun4i_lvds_encoder_enable, 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 encoder = &lvds->encoder; 117 118 ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, 119 &lvds->panel, &bridge); 120 if (ret) { 121 dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n"); 122 return 0; 123 } 124 125 if (lvds->panel) { 126 ret = devm_add_action_or_reset(tcon->dev, 127 sun4i_panel_put_action, 128 lvds->panel); 129 if (ret) 130 return ret; 131 } 132 133 drm_encoder_helper_add(&lvds->encoder, 134 &sun4i_lvds_enc_helper_funcs); 135 ret = drm_encoder_init(drm, &lvds->encoder, &sun4i_lvds_enc_funcs, 136 DRM_MODE_ENCODER_LVDS, NULL); 137 if (ret) { 138 dev_err(drm->dev, "Couldn't initialise the lvds encoder\n"); 139 goto err_out; 140 } 141 142 /* The LVDS encoder can only work with the TCON channel 0 */ 143 lvds->encoder.possible_crtcs = drm_crtc_mask(&tcon->crtc->crtc); 144 145 if (lvds->panel) { 146 drm_connector_helper_add(&lvds->connector, 147 &sun4i_lvds_con_helper_funcs); 148 ret = drm_connector_init(drm, &lvds->connector, 149 &sun4i_lvds_con_funcs, 150 DRM_MODE_CONNECTOR_LVDS); 151 if (ret) { 152 dev_err(drm->dev, "Couldn't initialise the lvds connector\n"); 153 goto err_cleanup_connector; 154 } 155 156 drm_connector_attach_encoder(&lvds->connector, 157 &lvds->encoder); 158 } 159 160 if (bridge) { 161 ret = drm_bridge_attach(encoder, bridge, NULL, 0); 162 if (ret) 163 goto err_cleanup_connector; 164 } 165 166 return 0; 167 168 err_cleanup_connector: 169 drm_encoder_cleanup(&lvds->encoder); 170 err_out: 171 return ret; 172 } 173 EXPORT_SYMBOL(sun4i_lvds_init); 174