/*
 * Copyright (C) 2012 Russell King
 *  Rewritten from the dovefb driver, and Armada510 manuals.
 *
 * 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_drm.h"
#include "armada_output.h"
#include "armada_slave.h"

static int armada_drm_slave_get_modes(struct drm_connector *conn)
{
	struct drm_encoder *enc = armada_drm_connector_encoder(conn);
	int count = 0;

	if (enc) {
		struct drm_encoder_slave *slave = to_encoder_slave(enc);

		count = slave->slave_funcs->get_modes(enc, conn);
	}

	return count;
}

static void armada_drm_slave_destroy(struct drm_encoder *enc)
{
	struct drm_encoder_slave *slave = to_encoder_slave(enc);
	struct i2c_client *client = drm_i2c_encoder_get_client(enc);

	if (slave->slave_funcs)
		slave->slave_funcs->destroy(enc);
	if (client)
		i2c_put_adapter(client->adapter);

	drm_encoder_cleanup(&slave->base);
	kfree(slave);
}

static const struct drm_encoder_funcs armada_drm_slave_encoder_funcs = {
	.destroy	= armada_drm_slave_destroy,
};

static const struct drm_connector_helper_funcs armada_drm_slave_helper_funcs = {
	.get_modes	= armada_drm_slave_get_modes,
	.mode_valid	= armada_drm_slave_encoder_mode_valid,
	.best_encoder	= armada_drm_connector_encoder,
};

static const struct drm_encoder_helper_funcs drm_slave_encoder_helpers = {
	.dpms = drm_i2c_encoder_dpms,
	.save = drm_i2c_encoder_save,
	.restore = drm_i2c_encoder_restore,
	.mode_fixup = drm_i2c_encoder_mode_fixup,
	.prepare = drm_i2c_encoder_prepare,
	.commit = drm_i2c_encoder_commit,
	.mode_set = drm_i2c_encoder_mode_set,
	.detect = drm_i2c_encoder_detect,
};

static int
armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
{
	const struct armada_drm_slave_config *config = data;
	struct drm_encoder_slave *slave;
	struct i2c_adapter *adap;
	int ret;

	conn->interlace_allowed = config->interlace_allowed;
	conn->doublescan_allowed = config->doublescan_allowed;
	conn->polled = config->polled;

	drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);

	slave = kzalloc(sizeof(*slave), GFP_KERNEL);
	if (!slave)
		return -ENOMEM;

	slave->base.possible_crtcs = config->crtcs;

	adap = i2c_get_adapter(config->i2c_adapter_id);
	if (!adap) {
		kfree(slave);
		return -EPROBE_DEFER;
	}

	ret = drm_encoder_init(conn->dev, &slave->base,
			       &armada_drm_slave_encoder_funcs,
			       DRM_MODE_ENCODER_TMDS);
	if (ret) {
		DRM_ERROR("unable to init encoder\n");
		i2c_put_adapter(adap);
		kfree(slave);
		return ret;
	}

	ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
	i2c_put_adapter(adap);
	if (ret) {
		DRM_ERROR("unable to init encoder slave\n");
		armada_drm_slave_destroy(&slave->base);
		return ret;
	}

	drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);

	ret = slave->slave_funcs->create_resources(&slave->base, conn);
	if (ret) {
		armada_drm_slave_destroy(&slave->base);
		return ret;
	}

	ret = drm_mode_connector_attach_encoder(conn, &slave->base);
	if (ret) {
		armada_drm_slave_destroy(&slave->base);
		return ret;
	}

	conn->encoder = &slave->base;

	return ret;
}

static const struct armada_output_type armada_drm_conn_slave = {
	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
	.create		= armada_drm_conn_slave_create,
	.set_property	= armada_drm_slave_encoder_set_property,
};

int armada_drm_connector_slave_create(struct drm_device *dev,
	const struct armada_drm_slave_config *config)
{
	return armada_output_create(dev, &armada_drm_conn_slave, config);
}