Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a297b36 authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge branch 'drm-atmel-hlcdc-devel' of https://github.com/bbrezillon/linux-at91 into drm-next

This PR contains several improvement and cleanup patches for the
atmel-hlcdc driver to be applied on drm-next (targeting 4.7).

* 'drm-atmel-hlcdc-devel' of https://github.com/bbrezillon/linux-at91:
  drm: atmel-hlcdc: route DMA accesses through AHB interfaces
  drm: atmel-hlcdc: check display mode validity in crtc->mode_fixup()
  drm: atmel-hlcdc: rework the output code to support drm bridges
  drm: atmel-hlcdc: move output mode selection in CRTC implementation
  drm: atmel-hlcdc: support extended timing ranges on sama5d4 and sama5d2
  drm: atmel-hlcdc: remove leftovers from atomic mode setting migration
  drm: atmel-hlcdc: fix connector and encoder types
  drm: atmel-hlcdc: support asynchronous atomic commit operations
  drm: atmel-hlcdc: add a ->cleanup_fb() operation
parents 605b28c8 ebab87ab
Loading
Loading
Loading
Loading
+150 −8
Original line number Diff line number Diff line
@@ -31,6 +31,23 @@

#include "atmel_hlcdc_dc.h"

/**
 * Atmel HLCDC CRTC state structure
 *
 * @base: base CRTC state
 * @output_mode: RGBXXX output mode
 */
struct atmel_hlcdc_crtc_state {
	struct drm_crtc_state base;
	unsigned int output_mode;
};

static inline struct atmel_hlcdc_crtc_state *
drm_crtc_state_to_atmel_hlcdc_crtc_state(struct drm_crtc_state *state)
{
	return container_of(state, struct atmel_hlcdc_crtc_state, base);
}

/**
 * Atmel HLCDC CRTC structure
 *
@@ -59,6 +76,7 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c)
	struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
	struct regmap *regmap = crtc->dc->hlcdc->regmap;
	struct drm_display_mode *adj = &c->state->adjusted_mode;
	struct atmel_hlcdc_crtc_state *state;
	unsigned long mode_rate;
	struct videomode vm;
	unsigned long prate;
@@ -112,15 +130,27 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c)
	if (adj->flags & DRM_MODE_FLAG_NHSYNC)
		cfg |= ATMEL_HLCDC_HSPOL;

	state = drm_crtc_state_to_atmel_hlcdc_crtc_state(c->state);
	cfg |= state->output_mode << 8;

	regmap_update_bits(regmap, ATMEL_HLCDC_CFG(5),
			   ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL |
			   ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE |
			   ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY |
			   ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO |
			   ATMEL_HLCDC_GUARDTIME_MASK,
			   ATMEL_HLCDC_GUARDTIME_MASK | ATMEL_HLCDC_MODE_MASK,
			   cfg);
}

static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *c,
					const struct drm_display_mode *mode,
					struct drm_display_mode *adjusted_mode)
{
	struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);

	return atmel_hlcdc_dc_mode_valid(crtc->dc, adjusted_mode) == MODE_OK;
}

static void atmel_hlcdc_crtc_disable(struct drm_crtc *c)
{
	struct drm_device *dev = c->dev;
@@ -221,15 +251,79 @@ void atmel_hlcdc_crtc_resume(struct drm_crtc *c)
	}
}

#define ATMEL_HLCDC_RGB444_OUTPUT	BIT(0)
#define ATMEL_HLCDC_RGB565_OUTPUT	BIT(1)
#define ATMEL_HLCDC_RGB666_OUTPUT	BIT(2)
#define ATMEL_HLCDC_RGB888_OUTPUT	BIT(3)
#define ATMEL_HLCDC_OUTPUT_MODE_MASK	GENMASK(3, 0)

static int atmel_hlcdc_crtc_select_output_mode(struct drm_crtc_state *state)
{
	unsigned int output_fmts = ATMEL_HLCDC_OUTPUT_MODE_MASK;
	struct atmel_hlcdc_crtc_state *hstate;
	struct drm_connector_state *cstate;
	struct drm_connector *connector;
	struct atmel_hlcdc_crtc *crtc;
	int i;

	crtc = drm_crtc_to_atmel_hlcdc_crtc(state->crtc);

	for_each_connector_in_state(state->state, connector, cstate, i) {
		struct drm_display_info *info = &connector->display_info;
		unsigned int supported_fmts = 0;
		int j;

		if (!cstate->crtc)
			continue;

		for (j = 0; j < info->num_bus_formats; j++) {
			switch (info->bus_formats[j]) {
			case MEDIA_BUS_FMT_RGB444_1X12:
				supported_fmts |= ATMEL_HLCDC_RGB444_OUTPUT;
				break;
			case MEDIA_BUS_FMT_RGB565_1X16:
				supported_fmts |= ATMEL_HLCDC_RGB565_OUTPUT;
				break;
			case MEDIA_BUS_FMT_RGB666_1X18:
				supported_fmts |= ATMEL_HLCDC_RGB666_OUTPUT;
				break;
			case MEDIA_BUS_FMT_RGB888_1X24:
				supported_fmts |= ATMEL_HLCDC_RGB888_OUTPUT;
				break;
			default:
				break;
			}
		}

		if (crtc->dc->desc->conflicting_output_formats)
			output_fmts &= supported_fmts;
		else
			output_fmts |= supported_fmts;
	}

	if (!output_fmts)
		return -EINVAL;

	hstate = drm_crtc_state_to_atmel_hlcdc_crtc_state(state);
	hstate->output_mode = fls(output_fmts) - 1;

	return 0;
}

static int atmel_hlcdc_crtc_atomic_check(struct drm_crtc *c,
					 struct drm_crtc_state *s)
{
	struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
	int ret;

	if (atmel_hlcdc_dc_mode_valid(crtc->dc, &s->adjusted_mode) != MODE_OK)
		return -EINVAL;
	ret = atmel_hlcdc_crtc_select_output_mode(s);
	if (ret)
		return ret;

	ret = atmel_hlcdc_plane_prepare_disc_area(s);
	if (ret)
		return ret;

	return atmel_hlcdc_plane_prepare_disc_area(s);
	return atmel_hlcdc_plane_prepare_ahb_routing(s);
}

static void atmel_hlcdc_crtc_atomic_begin(struct drm_crtc *c,
@@ -254,6 +348,7 @@ static void atmel_hlcdc_crtc_atomic_flush(struct drm_crtc *crtc,
}

static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = {
	.mode_fixup = atmel_hlcdc_crtc_mode_fixup,
	.mode_set = drm_helper_crtc_mode_set,
	.mode_set_nofb = atmel_hlcdc_crtc_mode_set_nofb,
	.mode_set_base = drm_helper_crtc_mode_set_base,
@@ -292,13 +387,60 @@ void atmel_hlcdc_crtc_irq(struct drm_crtc *c)
	atmel_hlcdc_crtc_finish_page_flip(drm_crtc_to_atmel_hlcdc_crtc(c));
}

void atmel_hlcdc_crtc_reset(struct drm_crtc *crtc)
{
	struct atmel_hlcdc_crtc_state *state;

	if (crtc->state && crtc->state->mode_blob)
		drm_property_unreference_blob(crtc->state->mode_blob);

	if (crtc->state) {
		state = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state);
		kfree(state);
	}

	state = kzalloc(sizeof(*state), GFP_KERNEL);
	if (state) {
		crtc->state = &state->base;
		crtc->state->crtc = crtc;
	}
}

static struct drm_crtc_state *
atmel_hlcdc_crtc_duplicate_state(struct drm_crtc *crtc)
{
	struct atmel_hlcdc_crtc_state *state, *cur;

	if (WARN_ON(!crtc->state))
		return NULL;

	state = kmalloc(sizeof(*state), GFP_KERNEL);
	if (state)
		__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);

	cur = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state);
	state->output_mode = cur->output_mode;

	return &state->base;
}

static void atmel_hlcdc_crtc_destroy_state(struct drm_crtc *crtc,
					   struct drm_crtc_state *s)
{
	struct atmel_hlcdc_crtc_state *state;

	state = drm_crtc_state_to_atmel_hlcdc_crtc_state(s);
	__drm_atomic_helper_crtc_destroy_state(crtc, s);
	kfree(state);
}

static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = {
	.page_flip = drm_atomic_helper_page_flip,
	.set_config = drm_atomic_helper_set_config,
	.destroy = atmel_hlcdc_crtc_destroy,
	.reset = drm_atomic_helper_crtc_reset,
	.atomic_duplicate_state =  drm_atomic_helper_crtc_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
	.reset = atmel_hlcdc_crtc_reset,
	.atomic_duplicate_state =  atmel_hlcdc_crtc_duplicate_state,
	.atomic_destroy_state = atmel_hlcdc_crtc_destroy_state,
};

int atmel_hlcdc_crtc_create(struct drm_device *dev)
+115 −8
Original line number Diff line number Diff line
@@ -50,6 +50,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9n12 = {
	.min_height = 0,
	.max_width = 1280,
	.max_height = 860,
	.max_spw = 0x3f,
	.max_vpw = 0x3f,
	.max_hpw = 0xff,
	.conflicting_output_formats = true,
	.nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9n12_layers),
	.layers = atmel_hlcdc_at91sam9n12_layers,
};
@@ -134,6 +138,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9x5 = {
	.min_height = 0,
	.max_width = 800,
	.max_height = 600,
	.max_spw = 0x3f,
	.max_vpw = 0x3f,
	.max_hpw = 0xff,
	.conflicting_output_formats = true,
	.nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9x5_layers),
	.layers = atmel_hlcdc_at91sam9x5_layers,
};
@@ -237,6 +245,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = {
	.min_height = 0,
	.max_width = 2048,
	.max_height = 2048,
	.max_spw = 0x3f,
	.max_vpw = 0x3f,
	.max_hpw = 0x1ff,
	.conflicting_output_formats = true,
	.nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers),
	.layers = atmel_hlcdc_sama5d3_layers,
};
@@ -320,6 +332,9 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d4 = {
	.min_height = 0,
	.max_width = 2048,
	.max_height = 2048,
	.max_spw = 0xff,
	.max_vpw = 0xff,
	.max_hpw = 0x3ff,
	.nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d4_layers),
	.layers = atmel_hlcdc_sama5d4_layers,
};
@@ -358,19 +373,19 @@ int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc,
	int hback_porch = mode->htotal - mode->hsync_end;
	int hsync_len = mode->hsync_end - mode->hsync_start;

	if (hsync_len > 0x40 || hsync_len < 1)
	if (hsync_len > dc->desc->max_spw + 1 || hsync_len < 1)
		return MODE_HSYNC;

	if (vsync_len > 0x40 || vsync_len < 1)
	if (vsync_len > dc->desc->max_spw + 1 || vsync_len < 1)
		return MODE_VSYNC;

	if (hfront_porch > 0x200 || hfront_porch < 1 ||
	    hback_porch > 0x200 || hback_porch < 1 ||
	if (hfront_porch > dc->desc->max_hpw + 1 || hfront_porch < 1 ||
	    hback_porch > dc->desc->max_hpw + 1 || hback_porch < 1 ||
	    mode->hdisplay < 1)
		return MODE_H_ILLEGAL;

	if (vfront_porch > 0x40 || vfront_porch < 1 ||
	    vback_porch > 0x40 || vback_porch < 0 ||
	if (vfront_porch > dc->desc->max_vpw + 1 || vfront_porch < 1 ||
	    vback_porch > dc->desc->max_vpw || vback_porch < 0 ||
	    mode->vdisplay < 1)
		return MODE_V_ILLEGAL;

@@ -427,11 +442,102 @@ static void atmel_hlcdc_fb_output_poll_changed(struct drm_device *dev)
	}
}

struct atmel_hlcdc_dc_commit {
	struct work_struct work;
	struct drm_device *dev;
	struct drm_atomic_state *state;
};

static void
atmel_hlcdc_dc_atomic_complete(struct atmel_hlcdc_dc_commit *commit)
{
	struct drm_device *dev = commit->dev;
	struct atmel_hlcdc_dc *dc = dev->dev_private;
	struct drm_atomic_state *old_state = commit->state;

	/* Apply the atomic update. */
	drm_atomic_helper_commit_modeset_disables(dev, old_state);
	drm_atomic_helper_commit_planes(dev, old_state, false);
	drm_atomic_helper_commit_modeset_enables(dev, old_state);

	drm_atomic_helper_wait_for_vblanks(dev, old_state);

	drm_atomic_helper_cleanup_planes(dev, old_state);

	drm_atomic_state_free(old_state);

	/* Complete the commit, wake up any waiter. */
	spin_lock(&dc->commit.wait.lock);
	dc->commit.pending = false;
	wake_up_all_locked(&dc->commit.wait);
	spin_unlock(&dc->commit.wait.lock);

	kfree(commit);
}

static void atmel_hlcdc_dc_atomic_work(struct work_struct *work)
{
	struct atmel_hlcdc_dc_commit *commit =
		container_of(work, struct atmel_hlcdc_dc_commit, work);

	atmel_hlcdc_dc_atomic_complete(commit);
}

static int atmel_hlcdc_dc_atomic_commit(struct drm_device *dev,
					struct drm_atomic_state *state,
					bool async)
{
	struct atmel_hlcdc_dc *dc = dev->dev_private;
	struct atmel_hlcdc_dc_commit *commit;
	int ret;

	ret = drm_atomic_helper_prepare_planes(dev, state);
	if (ret)
		return ret;

	/* Allocate the commit object. */
	commit = kzalloc(sizeof(*commit), GFP_KERNEL);
	if (!commit) {
		ret = -ENOMEM;
		goto error;
	}

	INIT_WORK(&commit->work, atmel_hlcdc_dc_atomic_work);
	commit->dev = dev;
	commit->state = state;

	spin_lock(&dc->commit.wait.lock);
	ret = wait_event_interruptible_locked(dc->commit.wait,
					      !dc->commit.pending);
	if (ret == 0)
		dc->commit.pending = true;
	spin_unlock(&dc->commit.wait.lock);

	if (ret) {
		kfree(commit);
		goto error;
	}

	/* Swap the state, this is the point of no return. */
	drm_atomic_helper_swap_state(dev, state);

	if (async)
		queue_work(dc->wq, &commit->work);
	else
		atmel_hlcdc_dc_atomic_complete(commit);

	return 0;

error:
	drm_atomic_helper_cleanup_planes(dev, state);
	return ret;
}

static const struct drm_mode_config_funcs mode_config_funcs = {
	.fb_create = atmel_hlcdc_fb_create,
	.output_poll_changed = atmel_hlcdc_fb_output_poll_changed,
	.atomic_check = drm_atomic_helper_check,
	.atomic_commit = drm_atomic_helper_commit,
	.atomic_commit = atmel_hlcdc_dc_atomic_commit,
};

static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev)
@@ -445,7 +551,7 @@ static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev)

	ret = atmel_hlcdc_create_outputs(dev);
	if (ret) {
		dev_err(dev->dev, "failed to create panel: %d\n", ret);
		dev_err(dev->dev, "failed to create HLCDC outputs: %d\n", ret);
		return ret;
	}

@@ -509,6 +615,7 @@ static int atmel_hlcdc_dc_load(struct drm_device *dev)
	if (!dc->wq)
		return -ENOMEM;

	init_waitqueue_head(&dc->commit.wait);
	dc->desc = match->data;
	dc->hlcdc = dev_get_drvdata(dev->dev->parent);
	dev->dev_private = dc;
+15 −0
Original line number Diff line number Diff line
@@ -50,6 +50,11 @@
 * @min_height: minimum height supported by the Display Controller
 * @max_width: maximum width supported by the Display Controller
 * @max_height: maximum height supported by the Display Controller
 * @max_spw: maximum vertical/horizontal pulse width
 * @max_vpw: maximum vertical back/front porch width
 * @max_hpw: maximum horizontal back/front porch width
 * @conflicting_output_formats: true if RGBXXX output formats conflict with
 *				each other.
 * @layers: a layer description table describing available layers
 * @nlayers: layer description table size
 */
@@ -58,6 +63,10 @@ struct atmel_hlcdc_dc_desc {
	int min_height;
	int max_width;
	int max_height;
	int max_spw;
	int max_vpw;
	int max_hpw;
	bool conflicting_output_formats;
	const struct atmel_hlcdc_layer_desc *layers;
	int nlayers;
};
@@ -128,6 +137,7 @@ struct atmel_hlcdc_planes {
 * @planes: instantiated planes
 * @layers: active HLCDC layer
 * @wq: display controller workqueue
 * @commit: used for async commit handling
 */
struct atmel_hlcdc_dc {
	const struct atmel_hlcdc_dc_desc *desc;
@@ -137,6 +147,10 @@ struct atmel_hlcdc_dc {
	struct atmel_hlcdc_planes *planes;
	struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS];
	struct workqueue_struct *wq;
	struct {
		wait_queue_head_t wait;
		bool pending;
	} commit;
};

extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats;
@@ -149,6 +163,7 @@ struct atmel_hlcdc_planes *
atmel_hlcdc_create_planes(struct drm_device *dev);

int atmel_hlcdc_plane_prepare_disc_area(struct drm_crtc_state *c_state);
int atmel_hlcdc_plane_prepare_ahb_routing(struct drm_crtc_state *c_state);

void atmel_hlcdc_crtc_irq(struct drm_crtc *c);

+111 −138
Original line number Diff line number Diff line
@@ -26,16 +26,6 @@

#include "atmel_hlcdc_dc.h"

/**
 * Atmel HLCDC RGB output mode
 */
enum atmel_hlcdc_connector_rgb_mode {
	ATMEL_HLCDC_CONNECTOR_RGB444,
	ATMEL_HLCDC_CONNECTOR_RGB565,
	ATMEL_HLCDC_CONNECTOR_RGB666,
	ATMEL_HLCDC_CONNECTOR_RGB888,
};

/**
 * Atmel HLCDC RGB connector structure
 *
@@ -44,13 +34,13 @@ enum atmel_hlcdc_connector_rgb_mode {
 * @connector: DRM connector
 * @encoder: DRM encoder
 * @dc: pointer to the atmel_hlcdc_dc structure
 * @dpms: current DPMS mode
 * @panel: panel connected on the RGB output
 */
struct atmel_hlcdc_rgb_output {
	struct drm_connector connector;
	struct drm_encoder encoder;
	struct atmel_hlcdc_dc *dc;
	int dpms;
	struct drm_panel *panel;
};

static inline struct atmel_hlcdc_rgb_output *
@@ -66,91 +56,31 @@ drm_encoder_to_atmel_hlcdc_rgb_output(struct drm_encoder *encoder)
	return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder);
}

/**
 * Atmel HLCDC Panel device structure
 *
 * This structure is specialization of the slave device structure to
 * interface with drm panels.
 *
 * @base: base slave device fields
 * @panel: drm panel attached to this slave device
 */
struct atmel_hlcdc_panel {
	struct atmel_hlcdc_rgb_output base;
	struct drm_panel *panel;
};

static inline struct atmel_hlcdc_panel *
atmel_hlcdc_rgb_output_to_panel(struct atmel_hlcdc_rgb_output *output)
{
	return container_of(output, struct atmel_hlcdc_panel, base);
}

static void atmel_hlcdc_panel_encoder_enable(struct drm_encoder *encoder)
{
	struct atmel_hlcdc_rgb_output *rgb =
			drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
	struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);

	drm_panel_enable(panel->panel);
}

static void atmel_hlcdc_panel_encoder_disable(struct drm_encoder *encoder)
static void atmel_hlcdc_rgb_encoder_enable(struct drm_encoder *encoder)
{
	struct atmel_hlcdc_rgb_output *rgb =
			drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
	struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);

	drm_panel_disable(panel->panel);
	if (rgb->panel) {
		drm_panel_prepare(rgb->panel);
		drm_panel_enable(rgb->panel);
	}

static bool
atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder,
				     const struct drm_display_mode *mode,
				     struct drm_display_mode *adjusted)
{
	return true;
}

static void
atmel_hlcdc_rgb_encoder_mode_set(struct drm_encoder *encoder,
				 struct drm_display_mode *mode,
				 struct drm_display_mode *adjusted)
static void atmel_hlcdc_rgb_encoder_disable(struct drm_encoder *encoder)
{
	struct atmel_hlcdc_rgb_output *rgb =
			drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
	struct drm_display_info *info = &rgb->connector.display_info;
	unsigned int cfg;

	cfg = 0;

	if (info->num_bus_formats) {
		switch (info->bus_formats[0]) {
		case MEDIA_BUS_FMT_RGB565_1X16:
			cfg |= ATMEL_HLCDC_CONNECTOR_RGB565 << 8;
			break;
		case MEDIA_BUS_FMT_RGB666_1X18:
			cfg |= ATMEL_HLCDC_CONNECTOR_RGB666 << 8;
			break;
		case MEDIA_BUS_FMT_RGB888_1X24:
			cfg |= ATMEL_HLCDC_CONNECTOR_RGB888 << 8;
			break;
		case MEDIA_BUS_FMT_RGB444_1X12:
		default:
			break;
		}
	}

	regmap_update_bits(rgb->dc->hlcdc->regmap, ATMEL_HLCDC_CFG(5),
			   ATMEL_HLCDC_MODE_MASK,
			   cfg);
	if (rgb->panel) {
		drm_panel_disable(rgb->panel);
		drm_panel_unprepare(rgb->panel);
	}
}

static const struct drm_encoder_helper_funcs atmel_hlcdc_panel_encoder_helper_funcs = {
	.mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup,
	.mode_set = atmel_hlcdc_rgb_encoder_mode_set,
	.disable = atmel_hlcdc_panel_encoder_disable,
	.enable = atmel_hlcdc_panel_encoder_enable,
	.disable = atmel_hlcdc_rgb_encoder_disable,
	.enable = atmel_hlcdc_rgb_encoder_enable,
};

static void atmel_hlcdc_rgb_encoder_destroy(struct drm_encoder *encoder)
@@ -167,9 +97,11 @@ static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
{
	struct atmel_hlcdc_rgb_output *rgb =
			drm_connector_to_atmel_hlcdc_rgb_output(connector);
	struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);

	return panel->panel->funcs->get_modes(panel->panel);
	if (rgb->panel)
		return rgb->panel->funcs->get_modes(rgb->panel);

	return 0;
}

static int atmel_hlcdc_rgb_mode_valid(struct drm_connector *connector,
@@ -201,7 +133,13 @@ static const struct drm_connector_helper_funcs atmel_hlcdc_panel_connector_helpe
static enum drm_connector_status
atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
{
	struct atmel_hlcdc_rgb_output *rgb =
			drm_connector_to_atmel_hlcdc_rgb_output(connector);

	if (rgb->panel)
		return connector_status_connected;

	return connector_status_disconnected;
}

static void
@@ -209,9 +147,10 @@ atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
{
	struct atmel_hlcdc_rgb_output *rgb =
			drm_connector_to_atmel_hlcdc_rgb_output(connector);
	struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);

	drm_panel_detach(panel->panel);
	if (rgb->panel)
		drm_panel_detach(rgb->panel);

	drm_connector_cleanup(connector);
}

@@ -225,88 +164,122 @@ static const struct drm_connector_funcs atmel_hlcdc_panel_connector_funcs = {
	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};

static int atmel_hlcdc_create_panel_output(struct drm_device *dev,
					   struct of_endpoint *ep)
static int atmel_hlcdc_check_endpoint(struct drm_device *dev,
				      const struct of_endpoint *ep)
{
	struct atmel_hlcdc_dc *dc = dev->dev_private;
	struct device_node *np;
	struct drm_panel *p = NULL;
	struct atmel_hlcdc_panel *panel;
	int ret;
	void *obj;

	np = of_graph_get_remote_port_parent(ep->local_node);
	if (!np)
		return -EINVAL;

	p = of_drm_find_panel(np);
	obj = of_drm_find_panel(np);
	if (!obj)
		obj = of_drm_find_bridge(np);

	of_node_put(np);

	if (!p)
		return -EPROBE_DEFER;
	return obj ? 0 : -EPROBE_DEFER;
}

	panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL);
	if (!panel)
		return -EINVAL;
static int atmel_hlcdc_attach_endpoint(struct drm_device *dev,
				       const struct of_endpoint *ep)
{
	struct atmel_hlcdc_dc *dc = dev->dev_private;
	struct atmel_hlcdc_rgb_output *output;
	struct device_node *np;
	struct drm_panel *panel;
	struct drm_bridge *bridge;
	int ret;

	panel->base.dpms = DRM_MODE_DPMS_OFF;
	output = devm_kzalloc(dev->dev, sizeof(*output), GFP_KERNEL);
	if (!output)
		return -EINVAL;

	panel->base.dc = dc;
	output->dc = dc;

	drm_encoder_helper_add(&panel->base.encoder,
	drm_encoder_helper_add(&output->encoder,
			       &atmel_hlcdc_panel_encoder_helper_funcs);
	ret = drm_encoder_init(dev, &panel->base.encoder,
	ret = drm_encoder_init(dev, &output->encoder,
			       &atmel_hlcdc_panel_encoder_funcs,
			       DRM_MODE_ENCODER_LVDS, NULL);
			       DRM_MODE_ENCODER_NONE, NULL);
	if (ret)
		return ret;

	panel->base.connector.dpms = DRM_MODE_DPMS_OFF;
	panel->base.connector.polled = DRM_CONNECTOR_POLL_CONNECT;
	drm_connector_helper_add(&panel->base.connector,
	output->encoder.possible_crtcs = 0x1;

	np = of_graph_get_remote_port_parent(ep->local_node);

	ret = -EPROBE_DEFER;

	panel = of_drm_find_panel(np);
	if (panel) {
		of_node_put(np);
		output->connector.dpms = DRM_MODE_DPMS_OFF;
		output->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
		drm_connector_helper_add(&output->connector,
				&atmel_hlcdc_panel_connector_helper_funcs);
	ret = drm_connector_init(dev, &panel->base.connector,
		ret = drm_connector_init(dev, &output->connector,
					 &atmel_hlcdc_panel_connector_funcs,
				 DRM_MODE_CONNECTOR_LVDS);
					 DRM_MODE_CONNECTOR_Unknown);
		if (ret)
			goto err_encoder_cleanup;

	drm_mode_connector_attach_encoder(&panel->base.connector,
					  &panel->base.encoder);
	panel->base.encoder.possible_crtcs = 0x1;
		drm_mode_connector_attach_encoder(&output->connector,
						  &output->encoder);

	drm_panel_attach(p, &panel->base.connector);
	panel->panel = p;
		ret = drm_panel_attach(panel, &output->connector);
		if (ret) {
			drm_connector_cleanup(&output->connector);
			goto err_encoder_cleanup;
		}

		output->panel = panel;

		return 0;
	}

	bridge = of_drm_find_bridge(np);
	of_node_put(np);

	if (bridge) {
		output->encoder.bridge = bridge;
		bridge->encoder = &output->encoder;
		ret = drm_bridge_attach(dev, bridge);
		if (!ret)
			return 0;
	}

err_encoder_cleanup:
	drm_encoder_cleanup(&panel->base.encoder);
	drm_encoder_cleanup(&output->encoder);

	return ret;
}

int atmel_hlcdc_create_outputs(struct drm_device *dev)
{
	struct device_node *port_np, *np;
	struct device_node *ep_np = NULL;
	struct of_endpoint ep;
	int ret;

	port_np = of_get_child_by_name(dev->dev->of_node, "port");
	if (!port_np)
		return -EINVAL;

	np = of_get_child_by_name(port_np, "endpoint");
	of_node_put(port_np);
	for_each_endpoint_of_node(dev->dev->of_node, ep_np) {
		ret = of_graph_parse_endpoint(ep_np, &ep);
		if (!ret)
			ret = atmel_hlcdc_check_endpoint(dev, &ep);

	if (!np)
		return -EINVAL;
		of_node_put(ep_np);
		if (ret)
			return ret;
	}

	ret = of_graph_parse_endpoint(np, &ep);
	of_node_put(port_np);
	for_each_endpoint_of_node(dev->dev->of_node, ep_np) {
		ret = of_graph_parse_endpoint(ep_np, &ep);
		if (!ret)
			ret = atmel_hlcdc_attach_endpoint(dev, &ep);

		of_node_put(ep_np);
		if (ret)
			return ret;
	}

	/* We currently only support panel output */
	return atmel_hlcdc_create_panel_output(dev, &ep);
	return 0;
}
+90 −5

File changed.

Preview size limit exceeded, changes collapsed.