![Russell King](/assets/img/avatar_default.png)
This patch adds support for the pair of LCD controllers on the Marvell Armada 510 SoCs. This driver supports: - multiple contiguous scanout buffers for video and graphics - shm backed cacheable buffer objects for X pixmaps for Vivante GPU acceleration - dual lcd0 and lcd1 crt operation - video overlay on each LCD crt via DRM planes - page flipping of the main scanout buffers - DRM prime for buffer export/import This driver is trivial to extend to other Armada SoCs. Included in this commit is the core driver with no output support; output support is platform and encoder driver dependent. Tested-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> Reviewed-by: Rob Clark <robdclark@gmail.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
159 lines
4 KiB
C
159 lines
4 KiB
C
/*
|
|
* Copyright (C) 2012 Russell King
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_encoder_slave.h>
|
|
#include "armada_output.h"
|
|
#include "armada_drm.h"
|
|
|
|
struct armada_connector {
|
|
struct drm_connector conn;
|
|
const struct armada_output_type *type;
|
|
};
|
|
|
|
#define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn)
|
|
|
|
struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn)
|
|
{
|
|
struct drm_encoder *enc = conn->encoder;
|
|
|
|
return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]);
|
|
}
|
|
|
|
static enum drm_connector_status armada_drm_connector_detect(
|
|
struct drm_connector *conn, bool force)
|
|
{
|
|
struct armada_connector *dconn = drm_to_armada_conn(conn);
|
|
enum drm_connector_status status = connector_status_disconnected;
|
|
|
|
if (dconn->type->detect) {
|
|
status = dconn->type->detect(conn, force);
|
|
} else {
|
|
struct drm_encoder *enc = armada_drm_connector_encoder(conn);
|
|
|
|
if (enc)
|
|
status = encoder_helper_funcs(enc)->detect(enc, conn);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static void armada_drm_connector_destroy(struct drm_connector *conn)
|
|
{
|
|
struct armada_connector *dconn = drm_to_armada_conn(conn);
|
|
|
|
drm_sysfs_connector_remove(conn);
|
|
drm_connector_cleanup(conn);
|
|
kfree(dconn);
|
|
}
|
|
|
|
static int armada_drm_connector_set_property(struct drm_connector *conn,
|
|
struct drm_property *property, uint64_t value)
|
|
{
|
|
struct armada_connector *dconn = drm_to_armada_conn(conn);
|
|
|
|
if (!dconn->type->set_property)
|
|
return -EINVAL;
|
|
|
|
return dconn->type->set_property(conn, property, value);
|
|
}
|
|
|
|
static const struct drm_connector_funcs armada_drm_conn_funcs = {
|
|
.dpms = drm_helper_connector_dpms,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.detect = armada_drm_connector_detect,
|
|
.destroy = armada_drm_connector_destroy,
|
|
.set_property = armada_drm_connector_set_property,
|
|
};
|
|
|
|
void armada_drm_encoder_prepare(struct drm_encoder *encoder)
|
|
{
|
|
encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_OFF);
|
|
}
|
|
|
|
void armada_drm_encoder_commit(struct drm_encoder *encoder)
|
|
{
|
|
encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_ON);
|
|
}
|
|
|
|
bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
|
|
const struct drm_display_mode *mode, struct drm_display_mode *adjusted)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* Shouldn't this be a generic helper function? */
|
|
int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
|
|
int valid = MODE_BAD;
|
|
|
|
if (encoder) {
|
|
struct drm_encoder_slave *slave = to_encoder_slave(encoder);
|
|
|
|
valid = slave->slave_funcs->mode_valid(encoder, mode);
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
|
|
struct drm_property *property, uint64_t value)
|
|
{
|
|
struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
|
|
int rc = -EINVAL;
|
|
|
|
if (encoder) {
|
|
struct drm_encoder_slave *slave = to_encoder_slave(encoder);
|
|
|
|
rc = slave->slave_funcs->set_property(encoder, conn, property,
|
|
value);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int armada_output_create(struct drm_device *dev,
|
|
const struct armada_output_type *type, const void *data)
|
|
{
|
|
struct armada_connector *dconn;
|
|
int ret;
|
|
|
|
dconn = kzalloc(sizeof(*dconn), GFP_KERNEL);
|
|
if (!dconn)
|
|
return -ENOMEM;
|
|
|
|
dconn->type = type;
|
|
|
|
ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs,
|
|
type->connector_type);
|
|
if (ret) {
|
|
DRM_ERROR("unable to init connector\n");
|
|
goto err_destroy_dconn;
|
|
}
|
|
|
|
ret = type->create(&dconn->conn, data);
|
|
if (ret)
|
|
goto err_conn;
|
|
|
|
ret = drm_sysfs_connector_add(&dconn->conn);
|
|
if (ret)
|
|
goto err_sysfs;
|
|
|
|
return 0;
|
|
|
|
err_sysfs:
|
|
if (dconn->conn.encoder)
|
|
dconn->conn.encoder->funcs->destroy(dconn->conn.encoder);
|
|
err_conn:
|
|
drm_connector_cleanup(&dconn->conn);
|
|
err_destroy_dconn:
|
|
kfree(dconn);
|
|
return ret;
|
|
}
|