Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  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
 19struct sun4i_lvds {
 20	struct drm_connector	connector;
 21	struct drm_encoder	encoder;
 22
 23	struct sun4i_tcon	*tcon;
 24};
 25
 26static inline struct sun4i_lvds *
 27drm_connector_to_sun4i_lvds(struct drm_connector *connector)
 28{
 29	return container_of(connector, struct sun4i_lvds,
 30			    connector);
 31}
 32
 33static inline struct sun4i_lvds *
 34drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder)
 35{
 36	return container_of(encoder, struct sun4i_lvds,
 37			    encoder);
 38}
 39
 40static 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
 49static struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = {
 50	.get_modes	= sun4i_lvds_get_modes,
 51};
 52
 53static void
 54sun4i_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
 63static 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
 71static 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
 84static 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
 97static 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
102static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = {
103	.destroy	= drm_encoder_cleanup,
104};
105
106int 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
172err_cleanup_connector:
173	drm_encoder_cleanup(&lvds->encoder);
174err_out:
175	return ret;
176}
177EXPORT_SYMBOL(sun4i_lvds_init);