Merge branch 'drm-atmel-hlcdc-devel' of https://github.com/bbrezillon/linux-at91...
authorDave Airlie <airlied@redhat.com>
Thu, 21 Apr 2016 23:06:44 +0000 (09:06 +1000)
committerDave Airlie <airlied@redhat.com>
Thu, 21 Apr 2016 23:06:44 +0000 (09:06 +1000)
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

drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c

index 58c4f785cf8427663611723013c9e8c8fa50ebbf..8df0aaf98725aaaeb049d21a5dcb6409a43c9da3 100644 (file)
 
 #include "atmel_hlcdc_dc.h"
 
+/**
+ * Atmel HLCDC CRTC state structure
+ *
+ * @base: base CRTC state
+ * @output_mode: RGBXXX output mode
+ */
+struct atmel_hlcdc_crtc_state {
+       struct drm_crtc_state base;
+       unsigned int output_mode;
+};
+
+static inline struct atmel_hlcdc_crtc_state *
+drm_crtc_state_to_atmel_hlcdc_crtc_state(struct drm_crtc_state *state)
+{
+       return container_of(state, struct atmel_hlcdc_crtc_state, base);
+}
+
 /**
  * Atmel HLCDC CRTC structure
  *
@@ -59,6 +76,7 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c)
        struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
        struct regmap *regmap = crtc->dc->hlcdc->regmap;
        struct drm_display_mode *adj = &c->state->adjusted_mode;
+       struct atmel_hlcdc_crtc_state *state;
        unsigned long mode_rate;
        struct videomode vm;
        unsigned long prate;
@@ -112,15 +130,27 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c)
        if (adj->flags & DRM_MODE_FLAG_NHSYNC)
                cfg |= ATMEL_HLCDC_HSPOL;
 
+       state = drm_crtc_state_to_atmel_hlcdc_crtc_state(c->state);
+       cfg |= state->output_mode << 8;
+
        regmap_update_bits(regmap, ATMEL_HLCDC_CFG(5),
                           ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL |
                           ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE |
                           ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY |
                           ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO |
-                          ATMEL_HLCDC_GUARDTIME_MASK,
+                          ATMEL_HLCDC_GUARDTIME_MASK | ATMEL_HLCDC_MODE_MASK,
                           cfg);
 }
 
+static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *c,
+                                       const struct drm_display_mode *mode,
+                                       struct drm_display_mode *adjusted_mode)
+{
+       struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
+
+       return atmel_hlcdc_dc_mode_valid(crtc->dc, adjusted_mode) == MODE_OK;
+}
+
 static void atmel_hlcdc_crtc_disable(struct drm_crtc *c)
 {
        struct drm_device *dev = c->dev;
@@ -221,15 +251,79 @@ void atmel_hlcdc_crtc_resume(struct drm_crtc *c)
        }
 }
 
+#define ATMEL_HLCDC_RGB444_OUTPUT      BIT(0)
+#define ATMEL_HLCDC_RGB565_OUTPUT      BIT(1)
+#define ATMEL_HLCDC_RGB666_OUTPUT      BIT(2)
+#define ATMEL_HLCDC_RGB888_OUTPUT      BIT(3)
+#define ATMEL_HLCDC_OUTPUT_MODE_MASK   GENMASK(3, 0)
+
+static int atmel_hlcdc_crtc_select_output_mode(struct drm_crtc_state *state)
+{
+       unsigned int output_fmts = ATMEL_HLCDC_OUTPUT_MODE_MASK;
+       struct atmel_hlcdc_crtc_state *hstate;
+       struct drm_connector_state *cstate;
+       struct drm_connector *connector;
+       struct atmel_hlcdc_crtc *crtc;
+       int i;
+
+       crtc = drm_crtc_to_atmel_hlcdc_crtc(state->crtc);
+
+       for_each_connector_in_state(state->state, connector, cstate, i) {
+               struct drm_display_info *info = &connector->display_info;
+               unsigned int supported_fmts = 0;
+               int j;
+
+               if (!cstate->crtc)
+                       continue;
+
+               for (j = 0; j < info->num_bus_formats; j++) {
+                       switch (info->bus_formats[j]) {
+                       case MEDIA_BUS_FMT_RGB444_1X12:
+                               supported_fmts |= ATMEL_HLCDC_RGB444_OUTPUT;
+                               break;
+                       case MEDIA_BUS_FMT_RGB565_1X16:
+                               supported_fmts |= ATMEL_HLCDC_RGB565_OUTPUT;
+                               break;
+                       case MEDIA_BUS_FMT_RGB666_1X18:
+                               supported_fmts |= ATMEL_HLCDC_RGB666_OUTPUT;
+                               break;
+                       case MEDIA_BUS_FMT_RGB888_1X24:
+                               supported_fmts |= ATMEL_HLCDC_RGB888_OUTPUT;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+
+               if (crtc->dc->desc->conflicting_output_formats)
+                       output_fmts &= supported_fmts;
+               else
+                       output_fmts |= supported_fmts;
+       }
+
+       if (!output_fmts)
+               return -EINVAL;
+
+       hstate = drm_crtc_state_to_atmel_hlcdc_crtc_state(state);
+       hstate->output_mode = fls(output_fmts) - 1;
+
+       return 0;
+}
+
 static int atmel_hlcdc_crtc_atomic_check(struct drm_crtc *c,
                                         struct drm_crtc_state *s)
 {
-       struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
+       int ret;
 
-       if (atmel_hlcdc_dc_mode_valid(crtc->dc, &s->adjusted_mode) != MODE_OK)
-               return -EINVAL;
+       ret = atmel_hlcdc_crtc_select_output_mode(s);
+       if (ret)
+               return ret;
+
+       ret = atmel_hlcdc_plane_prepare_disc_area(s);
+       if (ret)
+               return ret;
 
-       return atmel_hlcdc_plane_prepare_disc_area(s);
+       return atmel_hlcdc_plane_prepare_ahb_routing(s);
 }
 
 static void atmel_hlcdc_crtc_atomic_begin(struct drm_crtc *c,
@@ -254,6 +348,7 @@ static void atmel_hlcdc_crtc_atomic_flush(struct drm_crtc *crtc,
 }
 
 static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = {
+       .mode_fixup = atmel_hlcdc_crtc_mode_fixup,
        .mode_set = drm_helper_crtc_mode_set,
        .mode_set_nofb = atmel_hlcdc_crtc_mode_set_nofb,
        .mode_set_base = drm_helper_crtc_mode_set_base,
@@ -292,13 +387,60 @@ void atmel_hlcdc_crtc_irq(struct drm_crtc *c)
        atmel_hlcdc_crtc_finish_page_flip(drm_crtc_to_atmel_hlcdc_crtc(c));
 }
 
+void atmel_hlcdc_crtc_reset(struct drm_crtc *crtc)
+{
+       struct atmel_hlcdc_crtc_state *state;
+
+       if (crtc->state && crtc->state->mode_blob)
+               drm_property_unreference_blob(crtc->state->mode_blob);
+
+       if (crtc->state) {
+               state = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state);
+               kfree(state);
+       }
+
+       state = kzalloc(sizeof(*state), GFP_KERNEL);
+       if (state) {
+               crtc->state = &state->base;
+               crtc->state->crtc = crtc;
+       }
+}
+
+static struct drm_crtc_state *
+atmel_hlcdc_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+       struct atmel_hlcdc_crtc_state *state, *cur;
+
+       if (WARN_ON(!crtc->state))
+               return NULL;
+
+       state = kmalloc(sizeof(*state), GFP_KERNEL);
+       if (state)
+               __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+       cur = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state);
+       state->output_mode = cur->output_mode;
+
+       return &state->base;
+}
+
+static void atmel_hlcdc_crtc_destroy_state(struct drm_crtc *crtc,
+                                          struct drm_crtc_state *s)
+{
+       struct atmel_hlcdc_crtc_state *state;
+
+       state = drm_crtc_state_to_atmel_hlcdc_crtc_state(s);
+       __drm_atomic_helper_crtc_destroy_state(crtc, s);
+       kfree(state);
+}
+
 static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = {
        .page_flip = drm_atomic_helper_page_flip,
        .set_config = drm_atomic_helper_set_config,
        .destroy = atmel_hlcdc_crtc_destroy,
-       .reset = drm_atomic_helper_crtc_reset,
-       .atomic_duplicate_state =  drm_atomic_helper_crtc_duplicate_state,
-       .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+       .reset = atmel_hlcdc_crtc_reset,
+       .atomic_duplicate_state =  atmel_hlcdc_crtc_duplicate_state,
+       .atomic_destroy_state = atmel_hlcdc_crtc_destroy_state,
 };
 
 int atmel_hlcdc_crtc_create(struct drm_device *dev)
index 9907dd1ef4d05dccbcd7b45dc0adeff288cdf088..8ded7645747e503ed6d3a56ae15a4417b39f5ac8 100644 (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;
index fed517f297da1ead290a60c1ab0140467cefed01..7a47f8c094d021d75a3ec86263145eb8326d7f22 100644 (file)
  * @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);
 
index 0f7ec016e7a9695da29045d00d4b01dd636296ed..39802c0539b6ba5e2a3361f1441c20da2c5de2ec 100644 (file)
 
 #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);
+       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 =
                        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_disable(rgb->panel);
+               drm_panel_unprepare(rgb->panel);
        }
-
-       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 = {
-       .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)
-               return -EINVAL;
+static int atmel_hlcdc_attach_endpoint(struct drm_device *dev,
+                                      const struct of_endpoint *ep)
+{
+       struct atmel_hlcdc_dc *dc = dev->dev_private;
+       struct atmel_hlcdc_rgb_output *output;
+       struct device_node *np;
+       struct drm_panel *panel;
+       struct drm_bridge *bridge;
+       int ret;
 
-       panel->base.dpms = DRM_MODE_DPMS_OFF;
+       output = devm_kzalloc(dev->dev, sizeof(*output), GFP_KERNEL);
+       if (!output)
+               return -EINVAL;
 
-       panel->base.dc = dc;
+       output->dc = dc;
 
-       drm_encoder_helper_add(&panel->base.encoder,
+       drm_encoder_helper_add(&output->encoder,
                               &atmel_hlcdc_panel_encoder_helper_funcs);
-       ret = drm_encoder_init(dev, &panel->base.encoder,
+       ret = drm_encoder_init(dev, &output->encoder,
                               &atmel_hlcdc_panel_encoder_funcs,
-                              DRM_MODE_ENCODER_LVDS, NULL);
+                              DRM_MODE_ENCODER_NONE, NULL);
        if (ret)
                return ret;
 
-       panel->base.connector.dpms = DRM_MODE_DPMS_OFF;
-       panel->base.connector.polled = DRM_CONNECTOR_POLL_CONNECT;
-       drm_connector_helper_add(&panel->base.connector,
-                                &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;
+
+       np = of_graph_get_remote_port_parent(ep->local_node);
 
-       drm_mode_connector_attach_encoder(&panel->base.connector,
-                                         &panel->base.encoder);
-       panel->base.encoder.possible_crtcs = 0x1;
+       ret = -EPROBE_DEFER;
+
+       panel = of_drm_find_panel(np);
+       if (panel) {
+               of_node_put(np);
+               output->connector.dpms = DRM_MODE_DPMS_OFF;
+               output->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
+               drm_connector_helper_add(&output->connector,
+                               &atmel_hlcdc_panel_connector_helper_funcs);
+               ret = drm_connector_init(dev, &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);
-       panel->panel = p;
+               output->panel = panel;
 
-       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:
-       drm_encoder_cleanup(&panel->base.encoder);
+       drm_encoder_cleanup(&output->encoder);
 
        return ret;
 }
 
 int atmel_hlcdc_create_outputs(struct drm_device *dev)
 {
-       struct device_node *port_np, *np;
+       struct device_node *ep_np = NULL;
        struct of_endpoint ep;
        int ret;
 
-       port_np = of_get_child_by_name(dev->dev->of_node, "port");
-       if (!port_np)
-               return -EINVAL;
-
-       np = of_get_child_by_name(port_np, "endpoint");
-       of_node_put(port_np);
+       for_each_endpoint_of_node(dev->dev->of_node, ep_np) {
+               ret = of_graph_parse_endpoint(ep_np, &ep);
+               if (!ret)
+                       ret = atmel_hlcdc_check_endpoint(dev, &ep);
 
-       if (!np)
-               return -EINVAL;
+               of_node_put(ep_np);
+               if (ret)
+                       return ret;
+       }
 
-       ret = of_graph_parse_endpoint(np, &ep);
-       of_node_put(port_np);
+       for_each_endpoint_of_node(dev->dev->of_node, ep_np) {
+               ret = of_graph_parse_endpoint(ep_np, &ep);
+               if (!ret)
+                       ret = atmel_hlcdc_attach_endpoint(dev, &ep);
 
-       if (ret)
-               return ret;
+               of_node_put(ep_np);
+               if (ret)
+                       return ret;
+       }
 
-       /* We currently only support panel output */
-       return atmel_hlcdc_create_panel_output(dev, &ep);
+       return 0;
 }
index d65dcaee38323c19def9c958cb31e6a9617c9c82..aef3ca8a81fae37c31c84ba13eb45ff374dab83c 100644 (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;
+
+       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_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,
@@ -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);
This page took 0.052323 seconds and 5 git commands to generate.