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
This commit is contained in:
Dave Airlie 2016-04-22 09:06:44 +10:00
commit 9a297b36f4
5 changed files with 486 additions and 164 deletions

View File

@ -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;
return atmel_hlcdc_plane_prepare_disc_area(s);
ret = atmel_hlcdc_plane_prepare_disc_area(s);
if (ret)
return ret;
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)

View File

@ -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;

View File

@ -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);

View File

@ -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)
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_enable(panel->panel);
}
static void atmel_hlcdc_panel_encoder_disable(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);
}
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;
}
if (rgb->panel) {
drm_panel_prepare(rgb->panel);
drm_panel_enable(rgb->panel);
}
}
regmap_update_bits(rgb->dc->hlcdc->regmap, ATMEL_HLCDC_CFG(5),
ATMEL_HLCDC_MODE_MASK,
cfg);
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);
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)
{
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
@ -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)
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;
output = devm_kzalloc(dev->dev, sizeof(*output), GFP_KERNEL);
if (!output)
return -EINVAL;
panel->base.dpms = DRM_MODE_DPMS_OFF;
output->dc = dc;
panel->base.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,
&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;
output->encoder.possible_crtcs = 0x1;
drm_mode_connector_attach_encoder(&panel->base.connector,
&panel->base.encoder);
panel->base.encoder.possible_crtcs = 0x1;
np = of_graph_get_remote_port_parent(ep->local_node);
drm_panel_attach(p, &panel->base.connector);
panel->panel = p;
ret = -EPROBE_DEFER;
return 0;
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;
}
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;
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);
np = of_get_child_by_name(port_np, "endpoint");
of_node_put(port_np);
of_node_put(ep_np);
if (ret)
return ret;
}
if (!np)
return -EINVAL;
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);
ret = of_graph_parse_endpoint(np, &ep);
of_node_put(port_np);
of_node_put(ep_np);
if (ret)
return ret;
}
if (ret)
return ret;
/* We currently only support panel output */
return atmel_hlcdc_create_panel_output(dev, &ep);
return 0;
}

View File

@ -37,6 +37,7 @@
* @xstride: value to add to the pixel pointer between each line
* @pstride: value to add to the pixel pointer between each pixel
* @nplanes: number of planes (deduced from pixel_format)
* @prepared: plane update has been prepared
*/
struct atmel_hlcdc_plane_state {
struct drm_plane_state base;
@ -58,12 +59,15 @@ struct atmel_hlcdc_plane_state {
int disc_w;
int disc_h;
int ahb_id;
/* These fields are private and should not be touched */
int bpp[ATMEL_HLCDC_MAX_PLANES];
unsigned int offsets[ATMEL_HLCDC_MAX_PLANES];
int xstride[ATMEL_HLCDC_MAX_PLANES];
int pstride[ATMEL_HLCDC_MAX_PLANES];
int nplanes;
bool prepared;
};
static inline struct atmel_hlcdc_plane_state *
@ -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_DMA_CFG_ID,
ATMEL_HLCDC_LAYER_DMA_BLEN_MASK,
ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16);
ATMEL_HLCDC_LAYER_DMA_BLEN_MASK |
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_ITER2BL |
@ -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
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,
static int atmel_hlcdc_plane_prepare_fb(struct drm_plane *p,
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;
if (!new_state->fb)
return 0;
ret = atmel_hlcdc_layer_update_start(&plane->layer);
if (!ret)
state->prepared = true;
return atmel_hlcdc_layer_update_start(&plane->layer);
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_state *state =
drm_plane_state_to_atmel_hlcdc_plane_state(s);
/*
* The Request has already been applied or cancelled, nothing to do
* here.
*/
if (!state->prepared)
return;
atmel_hlcdc_layer_update_rollback(&plane->layer);
state->prepared = false;
}
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,
static struct drm_plane_helper_funcs atmel_hlcdc_layer_plane_helper_funcs = {
.prepare_fb = atmel_hlcdc_plane_prepare_fb,
.cleanup_fb = atmel_hlcdc_plane_cleanup_fb,
.atomic_check = atmel_hlcdc_plane_atomic_check,
.atomic_update = atmel_hlcdc_plane_atomic_update,
.atomic_disable = atmel_hlcdc_plane_atomic_disable,
@ -883,6 +967,7 @@ atmel_hlcdc_plane_atomic_duplicate_state(struct drm_plane *p)
return NULL;
copy->disc_updated = false;
copy->prepared = false;
if (copy->base.fb)
drm_framebuffer_reference(copy->base.fb);