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

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
...@@ -31,6 +31,23 @@ ...@@ -31,6 +31,23 @@
#include "atmel_hlcdc_dc.h" #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 * Atmel HLCDC CRTC structure
* *
...@@ -59,6 +76,7 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) ...@@ -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 atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
struct regmap *regmap = crtc->dc->hlcdc->regmap; struct regmap *regmap = crtc->dc->hlcdc->regmap;
struct drm_display_mode *adj = &c->state->adjusted_mode; struct drm_display_mode *adj = &c->state->adjusted_mode;
struct atmel_hlcdc_crtc_state *state;
unsigned long mode_rate; unsigned long mode_rate;
struct videomode vm; struct videomode vm;
unsigned long prate; unsigned long prate;
...@@ -112,15 +130,27 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) ...@@ -112,15 +130,27 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c)
if (adj->flags & DRM_MODE_FLAG_NHSYNC) if (adj->flags & DRM_MODE_FLAG_NHSYNC)
cfg |= ATMEL_HLCDC_HSPOL; 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), regmap_update_bits(regmap, ATMEL_HLCDC_CFG(5),
ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL | ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL |
ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE | ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE |
ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY | ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY |
ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO | ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO |
ATMEL_HLCDC_GUARDTIME_MASK, ATMEL_HLCDC_GUARDTIME_MASK | ATMEL_HLCDC_MODE_MASK,
cfg); 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) static void atmel_hlcdc_crtc_disable(struct drm_crtc *c)
{ {
struct drm_device *dev = c->dev; struct drm_device *dev = c->dev;
...@@ -221,15 +251,79 @@ void atmel_hlcdc_crtc_resume(struct drm_crtc *c) ...@@ -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, static int atmel_hlcdc_crtc_atomic_check(struct drm_crtc *c,
struct drm_crtc_state *s) 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) ret = atmel_hlcdc_crtc_select_output_mode(s);
return -EINVAL; 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, 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, ...@@ -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 = { 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 = drm_helper_crtc_mode_set,
.mode_set_nofb = atmel_hlcdc_crtc_mode_set_nofb, .mode_set_nofb = atmel_hlcdc_crtc_mode_set_nofb,
.mode_set_base = drm_helper_crtc_mode_set_base, .mode_set_base = drm_helper_crtc_mode_set_base,
...@@ -292,13 +387,60 @@ void atmel_hlcdc_crtc_irq(struct drm_crtc *c) ...@@ -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)); 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 = { static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = {
.page_flip = drm_atomic_helper_page_flip, .page_flip = drm_atomic_helper_page_flip,
.set_config = drm_atomic_helper_set_config, .set_config = drm_atomic_helper_set_config,
.destroy = atmel_hlcdc_crtc_destroy, .destroy = atmel_hlcdc_crtc_destroy,
.reset = drm_atomic_helper_crtc_reset, .reset = atmel_hlcdc_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_duplicate_state = atmel_hlcdc_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .atomic_destroy_state = atmel_hlcdc_crtc_destroy_state,
}; };
int atmel_hlcdc_crtc_create(struct drm_device *dev) int atmel_hlcdc_crtc_create(struct drm_device *dev)
......
...@@ -50,6 +50,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9n12 = { ...@@ -50,6 +50,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9n12 = {
.min_height = 0, .min_height = 0,
.max_width = 1280, .max_width = 1280,
.max_height = 860, .max_height = 860,
.max_spw = 0x3f,
.max_vpw = 0x3f,
.max_hpw = 0xff,
.conflicting_output_formats = true,
.nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9n12_layers), .nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9n12_layers),
.layers = atmel_hlcdc_at91sam9n12_layers, .layers = atmel_hlcdc_at91sam9n12_layers,
}; };
...@@ -134,6 +138,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9x5 = { ...@@ -134,6 +138,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9x5 = {
.min_height = 0, .min_height = 0,
.max_width = 800, .max_width = 800,
.max_height = 600, .max_height = 600,
.max_spw = 0x3f,
.max_vpw = 0x3f,
.max_hpw = 0xff,
.conflicting_output_formats = true,
.nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9x5_layers), .nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9x5_layers),
.layers = atmel_hlcdc_at91sam9x5_layers, .layers = atmel_hlcdc_at91sam9x5_layers,
}; };
...@@ -237,6 +245,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = { ...@@ -237,6 +245,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = {
.min_height = 0, .min_height = 0,
.max_width = 2048, .max_width = 2048,
.max_height = 2048, .max_height = 2048,
.max_spw = 0x3f,
.max_vpw = 0x3f,
.max_hpw = 0x1ff,
.conflicting_output_formats = true,
.nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers), .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers),
.layers = atmel_hlcdc_sama5d3_layers, .layers = atmel_hlcdc_sama5d3_layers,
}; };
...@@ -320,6 +332,9 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d4 = { ...@@ -320,6 +332,9 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d4 = {
.min_height = 0, .min_height = 0,
.max_width = 2048, .max_width = 2048,
.max_height = 2048, .max_height = 2048,
.max_spw = 0xff,
.max_vpw = 0xff,
.max_hpw = 0x3ff,
.nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d4_layers), .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d4_layers),
.layers = atmel_hlcdc_sama5d4_layers, .layers = atmel_hlcdc_sama5d4_layers,
}; };
...@@ -358,19 +373,19 @@ int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, ...@@ -358,19 +373,19 @@ int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc,
int hback_porch = mode->htotal - mode->hsync_end; int hback_porch = mode->htotal - mode->hsync_end;
int hsync_len = mode->hsync_end - mode->hsync_start; 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; return MODE_HSYNC;
if (vsync_len > 0x40 || vsync_len < 1) if (vsync_len > dc->desc->max_spw + 1 || vsync_len < 1)
return MODE_VSYNC; return MODE_VSYNC;
if (hfront_porch > 0x200 || hfront_porch < 1 || if (hfront_porch > dc->desc->max_hpw + 1 || hfront_porch < 1 ||
hback_porch > 0x200 || hback_porch < 1 || hback_porch > dc->desc->max_hpw + 1 || hback_porch < 1 ||
mode->hdisplay < 1) mode->hdisplay < 1)
return MODE_H_ILLEGAL; return MODE_H_ILLEGAL;
if (vfront_porch > 0x40 || vfront_porch < 1 || if (vfront_porch > dc->desc->max_vpw + 1 || vfront_porch < 1 ||
vback_porch > 0x40 || vback_porch < 0 || vback_porch > dc->desc->max_vpw || vback_porch < 0 ||
mode->vdisplay < 1) mode->vdisplay < 1)
return MODE_V_ILLEGAL; return MODE_V_ILLEGAL;
...@@ -427,11 +442,102 @@ static void atmel_hlcdc_fb_output_poll_changed(struct drm_device *dev) ...@@ -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 = { static const struct drm_mode_config_funcs mode_config_funcs = {
.fb_create = atmel_hlcdc_fb_create, .fb_create = atmel_hlcdc_fb_create,
.output_poll_changed = atmel_hlcdc_fb_output_poll_changed, .output_poll_changed = atmel_hlcdc_fb_output_poll_changed,
.atomic_check = drm_atomic_helper_check, .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) 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) ...@@ -445,7 +551,7 @@ static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev)
ret = atmel_hlcdc_create_outputs(dev); ret = atmel_hlcdc_create_outputs(dev);
if (ret) { 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; return ret;
} }
...@@ -509,6 +615,7 @@ static int atmel_hlcdc_dc_load(struct drm_device *dev) ...@@ -509,6 +615,7 @@ static int atmel_hlcdc_dc_load(struct drm_device *dev)
if (!dc->wq) if (!dc->wq)
return -ENOMEM; return -ENOMEM;
init_waitqueue_head(&dc->commit.wait);
dc->desc = match->data; dc->desc = match->data;
dc->hlcdc = dev_get_drvdata(dev->dev->parent); dc->hlcdc = dev_get_drvdata(dev->dev->parent);
dev->dev_private = dc; dev->dev_private = dc;
......
...@@ -50,6 +50,11 @@ ...@@ -50,6 +50,11 @@
* @min_height: minimum height supported by the Display Controller * @min_height: minimum height supported by the Display Controller
* @max_width: maximum width supported by the Display Controller * @max_width: maximum width supported by the Display Controller
* @max_height: maximum height 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 * @layers: a layer description table describing available layers
* @nlayers: layer description table size * @nlayers: layer description table size
*/ */
...@@ -58,6 +63,10 @@ struct atmel_hlcdc_dc_desc { ...@@ -58,6 +63,10 @@ struct atmel_hlcdc_dc_desc {
int min_height; int min_height;
int max_width; int max_width;
int max_height; int max_height;
int max_spw;
int max_vpw;
int max_hpw;
bool conflicting_output_formats;
const struct atmel_hlcdc_layer_desc *layers; const struct atmel_hlcdc_layer_desc *layers;
int nlayers; int nlayers;
}; };
...@@ -128,6 +137,7 @@ struct atmel_hlcdc_planes { ...@@ -128,6 +137,7 @@ struct atmel_hlcdc_planes {
* @planes: instantiated planes * @planes: instantiated planes
* @layers: active HLCDC layer * @layers: active HLCDC layer
* @wq: display controller workqueue * @wq: display controller workqueue
* @commit: used for async commit handling
*/ */
struct atmel_hlcdc_dc { struct atmel_hlcdc_dc {
const struct atmel_hlcdc_dc_desc *desc; const struct atmel_hlcdc_dc_desc *desc;
...@@ -137,6 +147,10 @@ struct atmel_hlcdc_dc { ...@@ -137,6 +147,10 @@ struct atmel_hlcdc_dc {
struct atmel_hlcdc_planes *planes; struct atmel_hlcdc_planes *planes;
struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS]; struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS];
struct workqueue_struct *wq; struct workqueue_struct *wq;
struct {
wait_queue_head_t wait;
bool pending;
} commit;
}; };
extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats; extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats;
...@@ -149,6 +163,7 @@ struct atmel_hlcdc_planes * ...@@ -149,6 +163,7 @@ struct atmel_hlcdc_planes *
atmel_hlcdc_create_planes(struct drm_device *dev); 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_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); void atmel_hlcdc_crtc_irq(struct drm_crtc *c);
......
...@@ -26,16 +26,6 @@ ...@@ -26,16 +26,6 @@
#include "atmel_hlcdc_dc.h" #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 * Atmel HLCDC RGB connector structure
* *
...@@ -44,13 +34,13 @@ enum atmel_hlcdc_connector_rgb_mode { ...@@ -44,13 +34,13 @@ enum atmel_hlcdc_connector_rgb_mode {
* @connector: DRM connector * @connector: DRM connector
* @encoder: DRM encoder * @encoder: DRM encoder
* @dc: pointer to the atmel_hlcdc_dc structure * @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 atmel_hlcdc_rgb_output {
struct drm_connector connector; struct drm_connector connector;
struct drm_encoder encoder; struct drm_encoder encoder;
struct atmel_hlcdc_dc *dc; struct atmel_hlcdc_dc *dc;
int dpms; struct drm_panel *panel;
}; };
static inline struct atmel_hlcdc_rgb_output * static inline struct atmel_hlcdc_rgb_output *
...@@ -66,91 +56,31 @@ drm_encoder_to_atmel_hlcdc_rgb_output(struct drm_encoder *encoder) ...@@ -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); return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder);
} }
/** static void atmel_hlcdc_rgb_encoder_enable(struct drm_encoder *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 = struct atmel_hlcdc_rgb_output *rgb =
drm_encoder_to_atmel_hlcdc_rgb_output(encoder); 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); if (rgb->panel) {
drm_panel_prepare(rgb->panel);
drm_panel_enable(rgb->panel);
}
} }
static void atmel_hlcdc_panel_encoder_disable(struct drm_encoder *encoder) static void atmel_hlcdc_rgb_encoder_disable(struct drm_encoder *encoder)
{ {
struct atmel_hlcdc_rgb_output *rgb = struct atmel_hlcdc_rgb_output *rgb =
drm_encoder_to_atmel_hlcdc_rgb_output(encoder); 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_disable(rgb->panel);
drm_panel_unprepare(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)
{
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);
} }
static const struct drm_encoder_helper_funcs atmel_hlcdc_panel_encoder_helper_funcs = { static const struct drm_encoder_helper_funcs atmel_hlcdc_panel_encoder_helper_funcs = {
.mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup, .disable = atmel_hlcdc_rgb_encoder_disable,
.mode_set = atmel_hlcdc_rgb_encoder_mode_set, .enable = atmel_hlcdc_rgb_encoder_enable,
.disable = atmel_hlcdc_panel_encoder_disable,
.enable = atmel_hlcdc_panel_encoder_enable,
}; };
static void atmel_hlcdc_rgb_encoder_destroy(struct drm_encoder *encoder) 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) ...@@ -167,9 +97,11 @@ static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
{ {
struct atmel_hlcdc_rgb_output *rgb = struct atmel_hlcdc_rgb_output *rgb =
drm_connector_to_atmel_hlcdc_rgb_output(connector); 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, 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 ...@@ -201,7 +133,13 @@ static const struct drm_connector_helper_funcs atmel_hlcdc_panel_connector_helpe
static enum drm_connector_status static enum drm_connector_status
atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force) atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
{ {
return connector_status_connected; 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 static void
...@@ -209,9 +147,10 @@ atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector) ...@@ -209,9 +147,10 @@ atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
{ {
struct atmel_hlcdc_rgb_output *rgb = struct atmel_hlcdc_rgb_output *rgb =
drm_connector_to_atmel_hlcdc_rgb_output(connector); 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); drm_connector_cleanup(connector);
} }
...@@ -225,88 +164,122 @@ static const struct drm_connector_funcs atmel_hlcdc_panel_connector_funcs = { ...@@ -225,88 +164,122 @@ static const struct drm_connector_funcs atmel_hlcdc_panel_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
}; };
static int atmel_hlcdc_create_panel_output(struct drm_device *dev, static int atmel_hlcdc_check_endpoint(struct drm_device *dev,
struct of_endpoint *ep) const struct of_endpoint *ep)
{ {
struct atmel_hlcdc_dc *dc = dev->dev_private;
struct device_node *np; struct device_node *np;
struct drm_panel *p = NULL; void *obj;
struct atmel_hlcdc_panel *panel;
int ret;
np = of_graph_get_remote_port_parent(ep->local_node); 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); of_node_put(np);
if (!p) return obj ? 0 : -EPROBE_DEFER;
return -EPROBE_DEFER; }
panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL); static int atmel_hlcdc_attach_endpoint(struct drm_device *dev,
if (!panel) const struct of_endpoint *ep)
return -EINVAL; {
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); &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, &atmel_hlcdc_panel_encoder_funcs,
DRM_MODE_ENCODER_LVDS, NULL); DRM_MODE_ENCODER_NONE, NULL);
if (ret) if (ret)
return ret; return ret;
panel->base.connector.dpms = DRM_MODE_DPMS_OFF; output->encoder.possible_crtcs = 0x1;
panel->base.connector.polled = DRM_CONNECTOR_POLL_CONNECT;
drm_connector_helper_add(&panel->base.connector, np = of_graph_get_remote_port_parent(ep->local_node);
&atmel_hlcdc_panel_connector_helper_funcs);
ret = drm_connector_init(dev, &panel->base.connector,
&atmel_hlcdc_panel_connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
if (ret)
goto err_encoder_cleanup;
drm_mode_connector_attach_encoder(&panel->base.connector, ret = -EPROBE_DEFER;
&panel->base.encoder);
panel->base.encoder.possible_crtcs = 0x1; 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, &output->connector,
&atmel_hlcdc_panel_connector_funcs,
DRM_MODE_CONNECTOR_Unknown);
if (ret)
goto err_encoder_cleanup;
drm_mode_connector_attach_encoder(&output->connector,
&output->encoder);
ret = drm_panel_attach(panel, &output->connector);
if (ret) {
drm_connector_cleanup(&output->connector);
goto err_encoder_cleanup;
}
drm_panel_attach(p, &panel->base.connector); output->panel = panel;
panel->panel = p;
return 0; 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: err_encoder_cleanup:
drm_encoder_cleanup(&panel->base.encoder); drm_encoder_cleanup(&output->encoder);
return ret; return ret;
} }
int atmel_hlcdc_create_outputs(struct drm_device *dev) 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; struct of_endpoint ep;
int ret; int ret;
port_np = of_get_child_by_name(dev->dev->of_node, "port"); for_each_endpoint_of_node(dev->dev->of_node, ep_np) {
if (!port_np) ret = of_graph_parse_endpoint(ep_np, &ep);
return -EINVAL; if (!ret)
ret = atmel_hlcdc_check_endpoint(dev, &ep);
np = of_get_child_by_name(port_np, "endpoint");
of_node_put(port_np);
if (!np) of_node_put(ep_np);
return -EINVAL; if (ret)
return ret;
}
ret = of_graph_parse_endpoint(np, &ep); for_each_endpoint_of_node(dev->dev->of_node, ep_np) {
of_node_put(port_np); ret = of_graph_parse_endpoint(ep_np, &ep);
if (!ret)
ret = atmel_hlcdc_attach_endpoint(dev, &ep);
if (ret) of_node_put(ep_np);
return ret; if (ret)
return ret;
}
/* We currently only support panel output */ return 0;
return atmel_hlcdc_create_panel_output(dev, &ep);
} }
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
* @xstride: value to add to the pixel pointer between each line * @xstride: value to add to the pixel pointer between each line
* @pstride: value to add to the pixel pointer between each pixel * @pstride: value to add to the pixel pointer between each pixel
* @nplanes: number of planes (deduced from pixel_format) * @nplanes: number of planes (deduced from pixel_format)
* @prepared: plane update has been prepared
*/ */
struct atmel_hlcdc_plane_state { struct atmel_hlcdc_plane_state {
struct drm_plane_state base; struct drm_plane_state base;
...@@ -58,12 +59,15 @@ struct atmel_hlcdc_plane_state { ...@@ -58,12 +59,15 @@ struct atmel_hlcdc_plane_state {
int disc_w; int disc_w;
int disc_h; int disc_h;
int ahb_id;
/* These fields are private and should not be touched */ /* These fields are private and should not be touched */
int bpp[ATMEL_HLCDC_MAX_PLANES]; int bpp[ATMEL_HLCDC_MAX_PLANES];
unsigned int offsets[ATMEL_HLCDC_MAX_PLANES]; unsigned int offsets[ATMEL_HLCDC_MAX_PLANES];
int xstride[ATMEL_HLCDC_MAX_PLANES]; int xstride[ATMEL_HLCDC_MAX_PLANES];
int pstride[ATMEL_HLCDC_MAX_PLANES]; int pstride[ATMEL_HLCDC_MAX_PLANES];
int nplanes; int nplanes;
bool prepared;
}; };
static inline struct atmel_hlcdc_plane_state * static inline struct atmel_hlcdc_plane_state *
...@@ -359,8 +363,10 @@ atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane, ...@@ -359,8 +363,10 @@ atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
atmel_hlcdc_layer_update_cfg(&plane->layer, atmel_hlcdc_layer_update_cfg(&plane->layer,
ATMEL_HLCDC_LAYER_DMA_CFG_ID, ATMEL_HLCDC_LAYER_DMA_CFG_ID,
ATMEL_HLCDC_LAYER_DMA_BLEN_MASK, ATMEL_HLCDC_LAYER_DMA_BLEN_MASK |
ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16); ATMEL_HLCDC_LAYER_DMA_SIF,
ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16 |
state->ahb_id);
atmel_hlcdc_layer_update_cfg(&plane->layer, layout->general_config, atmel_hlcdc_layer_update_cfg(&plane->layer, layout->general_config,
ATMEL_HLCDC_LAYER_ITER2BL | ATMEL_HLCDC_LAYER_ITER2BL |
...@@ -435,6 +441,41 @@ static void atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane, ...@@ -435,6 +441,41 @@ static void atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
} }
} }
int atmel_hlcdc_plane_prepare_ahb_routing(struct drm_crtc_state *c_state)
{
unsigned int ahb_load[2] = { };
struct drm_plane *plane;
drm_atomic_crtc_state_for_each_plane(plane, c_state) {
struct atmel_hlcdc_plane_state *plane_state;
struct drm_plane_state *plane_s;
unsigned int pixels, load = 0;
int i;
plane_s = drm_atomic_get_plane_state(c_state->state, plane);
if (IS_ERR(plane_s))
return PTR_ERR(plane_s);
plane_state =
drm_plane_state_to_atmel_hlcdc_plane_state(plane_s);
pixels = (plane_state->src_w * plane_state->src_h) -
(plane_state->disc_w * plane_state->disc_h);
for (i = 0; i < plane_state->nplanes; i++)
load += pixels * plane_state->bpp[i];
if (ahb_load[0] <= ahb_load[1])
plane_state->ahb_id = 0;
else
plane_state->ahb_id = 1;
ahb_load[plane_state->ahb_id] += load;
}
return 0;
}
int int
atmel_hlcdc_plane_prepare_disc_area(struct drm_crtc_state *c_state) atmel_hlcdc_plane_prepare_disc_area(struct drm_crtc_state *c_state)
{ {
...@@ -714,12 +755,54 @@ static int atmel_hlcdc_plane_atomic_check(struct drm_plane *p, ...@@ -714,12 +755,54 @@ static int atmel_hlcdc_plane_atomic_check(struct drm_plane *p,
static int atmel_hlcdc_plane_prepare_fb(struct drm_plane *p, static int atmel_hlcdc_plane_prepare_fb(struct drm_plane *p,
const struct drm_plane_state *new_state) const struct drm_plane_state *new_state)
{ {
/*
* FIXME: we should avoid this const -> non-const cast but it's
* currently the only solution we have to modify the ->prepared
* state and rollback the update request.
* Ideally, we should rework the code to attach all the resources
* to atmel_hlcdc_plane_state (including the DMA desc allocation),
* but this require a complete rework of the atmel_hlcdc_layer
* code.
*/
struct drm_plane_state *s = (struct drm_plane_state *)new_state;
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
struct atmel_hlcdc_plane_state *state =
drm_plane_state_to_atmel_hlcdc_plane_state(s);
int ret;
ret = atmel_hlcdc_layer_update_start(&plane->layer);
if (!ret)
state->prepared = true;
return ret;
}
static void atmel_hlcdc_plane_cleanup_fb(struct drm_plane *p,
const struct drm_plane_state *old_state)
{
/*
* FIXME: we should avoid this const -> non-const cast but it's
* currently the only solution we have to modify the ->prepared
* state and rollback the update request.
* Ideally, we should rework the code to attach all the resources
* to atmel_hlcdc_plane_state (including the DMA desc allocation),
* but this require a complete rework of the atmel_hlcdc_layer
* code.
*/
struct drm_plane_state *s = (struct drm_plane_state *)old_state;
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
struct atmel_hlcdc_plane_state *state =
drm_plane_state_to_atmel_hlcdc_plane_state(s);
if (!new_state->fb) /*
return 0; * The Request has already been applied or cancelled, nothing to do
* here.
*/
if (!state->prepared)
return;
return atmel_hlcdc_layer_update_start(&plane->layer); atmel_hlcdc_layer_update_rollback(&plane->layer);
state->prepared = false;
} }
static void atmel_hlcdc_plane_atomic_update(struct drm_plane *p, static void atmel_hlcdc_plane_atomic_update(struct drm_plane *p,
...@@ -844,6 +927,7 @@ static void atmel_hlcdc_plane_init_properties(struct atmel_hlcdc_plane *plane, ...@@ -844,6 +927,7 @@ static void atmel_hlcdc_plane_init_properties(struct atmel_hlcdc_plane *plane,
static struct drm_plane_helper_funcs atmel_hlcdc_layer_plane_helper_funcs = { static struct drm_plane_helper_funcs atmel_hlcdc_layer_plane_helper_funcs = {
.prepare_fb = atmel_hlcdc_plane_prepare_fb, .prepare_fb = atmel_hlcdc_plane_prepare_fb,
.cleanup_fb = atmel_hlcdc_plane_cleanup_fb,
.atomic_check = atmel_hlcdc_plane_atomic_check, .atomic_check = atmel_hlcdc_plane_atomic_check,
.atomic_update = atmel_hlcdc_plane_atomic_update, .atomic_update = atmel_hlcdc_plane_atomic_update,
.atomic_disable = atmel_hlcdc_plane_atomic_disable, .atomic_disable = atmel_hlcdc_plane_atomic_disable,
...@@ -883,6 +967,7 @@ atmel_hlcdc_plane_atomic_duplicate_state(struct drm_plane *p) ...@@ -883,6 +967,7 @@ atmel_hlcdc_plane_atomic_duplicate_state(struct drm_plane *p)
return NULL; return NULL;
copy->disc_updated = false; copy->disc_updated = false;
copy->prepared = false;
if (copy->base.fb) if (copy->base.fb)
drm_framebuffer_reference(copy->base.fb); drm_framebuffer_reference(copy->base.fb);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment