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:
commit
9a297b36f4
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue