With the hardware readout infrastructure now in place in the KMS
framework, we can provide it for the tidss driver.

Reviewed-by: Maarten Lankhorst <[email protected]>
Signed-off-by: Maxime Ripard <[email protected]>
---
 drivers/gpu/drm/tidss/tidss_crtc.c    |  91 ++++++++++
 drivers/gpu/drm/tidss/tidss_dispc.c   | 320 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tidss/tidss_dispc.h   |  18 ++
 drivers/gpu/drm/tidss/tidss_encoder.c |  42 +++++
 drivers/gpu/drm/tidss/tidss_kms.c     |   6 +-
 drivers/gpu/drm/tidss/tidss_plane.c   | 178 +++++++++++++++++++
 6 files changed, 654 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c 
b/drivers/gpu/drm/tidss/tidss_crtc.c
index 3da49f77fa08..4409f8c9114b 100644
--- a/drivers/gpu/drm/tidss/tidss_crtc.c
+++ b/drivers/gpu/drm/tidss/tidss_crtc.c
@@ -5,6 +5,10 @@
  */
 
+#include <linux/clk.h>
+
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout_helper.h>
+#include <drm/drm_atomic_uapi.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_gem_dma_helper.h>
@@ -371,4 +375,88 @@ static struct drm_crtc_state 
*tidss_crtc_create_state(struct drm_crtc *crtc)
 }
 
+static int tidss_crtc_readout_state(struct drm_crtc *crtc,
+                                   struct drm_atomic_sro_state *state,
+                                   struct drm_crtc_state *crtc_state)
+{
+       struct drm_device *ddev = crtc->dev;
+       struct tidss_device *tidss = to_tidss(ddev);
+       struct dispc_device *dispc = tidss->dispc;
+       struct tidss_crtc_state *tstate = to_tidss_crtc_state(crtc_state);
+       struct tidss_crtc *tcrtc =
+               to_tidss_crtc(crtc);
+       struct drm_display_mode mode = {};
+       int ret;
+
+       tidss_runtime_get(tidss);
+
+       if (!dispc_vp_is_enabled(dispc, tcrtc->hw_videoport))
+               goto out;
+
+       tstate->base.active = 1;
+       tstate->base.enable = 1;
+
+       ret = dispc_vp_readout_mode(dispc, tcrtc->hw_videoport, &mode);
+       if (ret)
+               goto err_runtime_put;
+
+       ret = drm_atomic_set_mode_for_crtc(&tstate->base, &mode);
+       if (WARN_ON(ret))
+               goto err_runtime_put;
+
+       drm_mode_copy(&tstate->base.adjusted_mode, &mode);
+
+       tstate->bus_flags = dispc_vp_get_bus_flags(dispc, tcrtc->hw_videoport);
+       ret = dispc_vp_get_bus_format(dispc, tcrtc->hw_videoport,
+                                     &tstate->bus_format);
+       if (ret)
+               goto err_runtime_put;
+
+       /*
+        * The active connectors and planes will be filled by their
+        * respective readout callbacks.
+        */
+
+out:
+       tidss_runtime_put(tidss);
+       return 0;
+
+err_runtime_put:
+       tidss_runtime_put(tidss);
+       return ret;
+}
+
+static void tidss_crtc_install_state(struct drm_crtc *crtc,
+                                    struct drm_crtc_state *crtc_state)
+{
+       struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+       struct drm_device *ddev = crtc->dev;
+       struct tidss_device *tidss = to_tidss(ddev);
+       int ret;
+
+       tidss_runtime_get(tidss);
+
+       ret = dispc_vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
+       if (ret != 0)
+               return;
+
+       /* Turn vertical blanking interrupt reporting on. */
+       drm_crtc_vblank_on(crtc);
+}
+
+static bool tidss_crtc_compare_state(struct drm_crtc *crtc,
+                                    struct drm_printer *p,
+                                    struct drm_crtc_state *expected,
+                                    struct drm_crtc_state *actual)
+{
+       struct tidss_crtc_state *t_expected = to_tidss_crtc_state(expected);
+       struct tidss_crtc_state *t_actual = to_tidss_crtc_state(actual);
+       bool ret = drm_atomic_helper_crtc_compare_state(crtc, p, expected, 
actual);
+
+       STATE_CHECK_U32_X(ret, p, crtc->name, t_expected, t_actual, bus_format);
+       STATE_CHECK_U32_X(ret, p, crtc->name, t_expected, t_actual, bus_flags);
+
+       return ret;
+}
+
 static struct drm_crtc_state *tidss_crtc_duplicate_state(struct drm_crtc *crtc)
 {
@@ -416,4 +504,7 @@ static const struct drm_crtc_funcs tidss_crtc_funcs = {
        .page_flip = drm_atomic_helper_page_flip,
        .atomic_create_state = tidss_crtc_create_state,
+       .atomic_sro_readout_state = tidss_crtc_readout_state,
+       .atomic_sro_install_state = tidss_crtc_install_state,
+       .atomic_sro_compare_state = tidss_crtc_compare_state,
        .atomic_duplicate_state = tidss_crtc_duplicate_state,
        .atomic_destroy_state = tidss_crtc_destroy_state,
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c 
b/drivers/gpu/drm/tidss/tidss_dispc.c
index fd33edd80977..6ef7e62a58c5 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.c
+++ b/drivers/gpu/drm/tidss/tidss_dispc.c
@@ -1209,4 +1209,10 @@ void dispc_vp_enable(struct dispc_device *dispc, u32 
hw_videoport)
 }
 
+bool dispc_vp_is_enabled(struct dispc_device *dispc, u32 hw_videoport)
+{
+       return FIELD_GET(DISPC_VP_CONTROL_ENABLE_MASK,
+                        dispc_vp_read(dispc, hw_videoport, DISPC_VP_CONTROL));
+}
+
 void dispc_vp_disable(struct dispc_device *dispc, u32 hw_videoport)
 {
@@ -1462,4 +1468,226 @@ int dispc_vp_set_clk_rate(struct dispc_device *dispc, 
u32 hw_videoport,
 }
 
+static unsigned long calc_pixel_clock_hz(unsigned int htotal,
+                                        unsigned int vtotal,
+                                        unsigned int refresh,
+                                        unsigned int freq_div)
+{
+       u64 rate = (u64)htotal * vtotal * refresh;
+
+       return div_u64(rate * 1000, freq_div);
+}
+
+static const unsigned int refresh_tries[] = {30, 50, 60};
+static const unsigned int refresh_factors_tries[] = {1000, 1001};
+
+static unsigned int dispc_find_closest_refresh_rate_from_clk(struct drm_device 
*dev,
+                                                            struct clk *clk,
+                                                            unsigned int 
htotal,
+                                                            unsigned int 
vtotal,
+                                                            unsigned long 
*pixel_clock_hz)
+{
+       unsigned long actual_clk_rate = clk_get_rate(clk);
+       unsigned long best_clk_rate = 0;
+       unsigned long best_rate_diff = ULONG_MAX;
+       unsigned int best_refresh = 0;
+       unsigned int i, j;
+
+       drm_dbg(dev, "Actual clock rate is %lu\n", actual_clk_rate);
+
+       for (i = 0; i < ARRAY_SIZE(refresh_tries); i++) {
+               for (j = 0; j < ARRAY_SIZE(refresh_factors_tries); j++) {
+                       unsigned int try_refresh = refresh_tries[i];
+                       unsigned int try_factor = refresh_factors_tries[j];
+                       unsigned long try_clk_rate = calc_pixel_clock_hz(htotal,
+                                                                        vtotal,
+                                                                        
try_refresh,
+                                                                        
try_factor);
+                       unsigned long diff;
+
+                       drm_dbg(dev, "Evaluating refresh %u, factor %u, rate 
%lu\n",
+                               try_refresh, try_factor, try_clk_rate);
+
+                       if (try_clk_rate == actual_clk_rate) {
+                               drm_dbg(dev, "Found exact match. Stopping.\n");
+                               best_refresh = try_refresh;
+                               best_clk_rate = try_clk_rate;
+                               goto out;
+                       }
+
+
+                       diff = abs_diff(actual_clk_rate, try_clk_rate);
+                       if (diff < best_rate_diff) {
+                               drm_dbg(dev, "Found new candidate. Difference 
is %lu\n", diff);
+                               best_refresh = try_refresh;
+                               best_clk_rate = try_clk_rate;
+                               best_rate_diff = diff;
+                       }
+               }
+       }
+
+       /*
+        * The video port clock doesn't match any standard refresh rate,
+        * so the mode we would report would be plain wrong. Report the
+        * configuration as unsupported by returning 0, so that the
+        * caller falls back on the regular initialization path.
+        *
+        * We tolerate the same 5% difference that
+        * dispc_vp_set_clk_rate() accepts when programming the clock.
+        */
+       if (dispc_pclk_diff(best_clk_rate, actual_clk_rate) > 5) {
+               drm_dbg(dev, "Clock rate %lu doesn't match any standard refresh 
rate\n",
+                       actual_clk_rate);
+               return 0;
+       }
+
+out:
+       drm_dbg(dev, "Best candidate is %u Hz, pixel clock rate %lu Hz",
+               best_refresh, best_clk_rate);
+
+       if (pixel_clock_hz)
+               *pixel_clock_hz = best_clk_rate;
+
+       return best_refresh;
+}
+
+int dispc_vp_readout_mode(struct dispc_device *dispc,
+                         u32 hw_videoport,
+                         struct drm_display_mode *mode)
+{
+       struct tidss_device *tidss = dispc->tidss;
+       struct drm_device *dev = &tidss->ddev;
+       unsigned long pixel_clock;
+       unsigned int refresh;
+       u16 hdisplay, hfp, hsw, hbp;
+       u16 vdisplay, vfp, vsw, vbp;
+       u32 val;
+
+       val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_SIZE_SCREEN);
+       hdisplay = FIELD_GET(DISPC_VP_SIZE_SCREEN_HDISPLAY_MASK, val) + 1;
+       vdisplay = FIELD_GET(DISPC_VP_SIZE_SCREEN_VDISPLAY_MASK, val) + 1;
+
+       mode->hdisplay = hdisplay;
+       mode->vdisplay = vdisplay;
+
+       val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_TIMING_H);
+       hsw = FIELD_GET(DISPC_VP_TIMING_H_SYNC_PULSE_MASK, val) + 1;
+       hfp = FIELD_GET(DISPC_VP_TIMING_H_FRONT_PORCH_MASK, val) + 1;
+       hbp = FIELD_GET(DISPC_VP_TIMING_H_BACK_PORCH_MASK, val) + 1;
+
+       mode->hsync_start = hdisplay + hfp;
+       mode->hsync_end = hdisplay + hfp + hsw;
+       mode->htotal = hdisplay + hfp + hsw + hbp;
+
+       val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_TIMING_V);
+       vsw = FIELD_GET(DISPC_VP_TIMING_V_SYNC_PULSE_MASK, val) + 1;
+       vfp = FIELD_GET(DISPC_VP_TIMING_V_FRONT_PORCH_MASK, val);
+       vbp = FIELD_GET(DISPC_VP_TIMING_V_BACK_PORCH_MASK, val);
+
+       mode->vsync_start = vdisplay + vfp;
+       mode->vsync_end = vdisplay + vfp + vsw;
+       mode->vtotal = vdisplay + vfp + vsw + vbp;
+
+       refresh = dispc_find_closest_refresh_rate_from_clk(dev,
+                                                          
dispc->vp_clk[hw_videoport],
+                                                          mode->htotal,
+                                                          mode->vtotal,
+                                                          &pixel_clock);
+       if (!refresh)
+               return -EINVAL;
+
+       mode->clock = pixel_clock / 1000;
+
+       val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_POL_FREQ);
+       if (FIELD_GET(DISPC_VP_POL_FREQ_IVS_MASK, val))
+               mode->flags |= DRM_MODE_FLAG_NVSYNC;
+       else
+               mode->flags |= DRM_MODE_FLAG_PVSYNC;
+
+       if (FIELD_GET(DISPC_VP_POL_FREQ_IHS_MASK, val))
+               mode->flags |= DRM_MODE_FLAG_NHSYNC;
+       else
+               mode->flags |= DRM_MODE_FLAG_PHSYNC;
+
+       mode->type |= DRM_MODE_TYPE_DRIVER;
+       drm_mode_set_name(mode);
+       drm_mode_set_crtcinfo(mode, 0);
+
+       return 0;
+}
+
+int dispc_vp_get_bus_format(struct dispc_device *dispc, u32 hw_videoport,
+                           u32 *bus_fmt)
+{
+       static const u32 datalines_to_width[] = { 12, 16, 18, 24, 30, 36 };
+       struct tidss_device *tidss = dispc->tidss;
+       struct drm_device *dev = &tidss->ddev;
+       u32 datalines, data_width;
+       unsigned int i;
+
+       datalines = VP_REG_GET(dispc, hw_videoport, DISPC_VP_CONTROL,
+                              DISPC_VP_CONTROL_DATALINES_MASK);
+
+       if (datalines < ARRAY_SIZE(datalines_to_width)) {
+               data_width = datalines_to_width[datalines];
+       } else {
+               drm_dbg(dev, "Unknown DATALINES encoding %u on %s\n",
+                       datalines, dispc->feat->vp_name[hw_videoport]);
+               return -EINVAL;
+       }
+
+       if (dispc->feat->vp_bus_type[hw_videoport] == DISPC_VP_OLDI_AM65X) {
+               u32 oldi_cfg = dispc_vp_read(dispc, hw_videoport,
+                                            DISPC_VP_DSS_OLDI_CFG);
+               u32 map = FIELD_GET(DISPC_VP_DSS_OLDI_CFG_MAP_MASK, oldi_cfg);
+
+               for (i = 0; i < ARRAY_SIZE(dispc_bus_formats); i++) {
+                       if (dispc_bus_formats[i].is_oldi_fmt &&
+                           dispc_bus_formats[i].data_width == data_width &&
+                           dispc_bus_formats[i].am65x_oldi_mode_reg_val == 
map) {
+                               *bus_fmt = dispc_bus_formats[i].bus_fmt;
+                               return 0;
+                       }
+               }
+
+               drm_dbg(dev, "Unknown OLDI mapping %u on %s\n", map,
+                       dispc->feat->vp_name[hw_videoport]);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(dispc_bus_formats); i++) {
+               if (!dispc_bus_formats[i].is_oldi_fmt &&
+                   dispc_bus_formats[i].data_width == data_width) {
+                       *bus_fmt = dispc_bus_formats[i].bus_fmt;
+                       return 0;
+               }
+       }
+
+       return -EINVAL;
+}
+
+u32 dispc_vp_get_bus_flags(struct dispc_device *dispc, u32 hw_videoport)
+{
+       u32 flags = 0;
+       u32 val;
+
+       val = dispc_vp_read(dispc, hw_videoport, DISPC_VP_POL_FREQ);
+       if (FIELD_GET(DISPC_VP_POL_FREQ_IPC_MASK, val))
+               flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+       else
+               flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
+
+       if (FIELD_GET(DISPC_VP_POL_FREQ_IEO_MASK, val))
+               flags |= DRM_BUS_FLAG_DE_LOW;
+       else
+               flags |= DRM_BUS_FLAG_DE_HIGH;
+
+       if (FIELD_GET(DISPC_VP_POL_FREQ_RF_MASK, val))
+               flags |= DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE;
+       else
+               flags |= DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE;
+
+       return flags;
+}
+
 /* OVR */
 static void dispc_k2g_ovr_set_plane(struct dispc_device *dispc,
@@ -2107,4 +2335,15 @@ static const struct {
 };
 
+static u32 dispc_plane_find_fourcc_by_dss_code(u8 code)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(dispc_color_formats); ++i)
+               if (dispc_color_formats[i].dss_code == code)
+                       return dispc_color_formats[i].fourcc;
+
+       return 0;
+}
+
 static void dispc_plane_set_pixel_format(struct dispc_device *dispc,
                                         u32 hw_plane, u32 fourcc)
@@ -2272,4 +2511,79 @@ void dispc_plane_setup(struct dispc_device *dispc, u32 
hw_plane,
 }
 
+void dispc_plane_get_picture_size(struct dispc_device *dispc, u32 hw_plane,
+                                 unsigned int *width, unsigned int *height)
+{
+       u32 val;
+       u16 w, h;
+
+       val = dispc_vid_read(dispc, hw_plane, DISPC_VID_PICTURE_SIZE);
+       w = FIELD_GET(DISPC_VID_PICTURE_SIZE_MEMSIZEX_MASK, val);
+       h = FIELD_GET(DISPC_VID_PICTURE_SIZE_MEMSIZEY_MASK, val);
+
+       *width = w + 1;
+       *height = h + 1;
+}
+
+void dispc_plane_get_size(struct dispc_device *dispc, u32 hw_plane,
+                         unsigned int *width, unsigned int *height)
+{
+       bool lite = dispc->feat->vid_info[hw_plane].is_lite;
+
+       if (!lite) {
+               u32 val = dispc_vid_read(dispc, hw_plane, DISPC_VID_SIZE);
+               *width = FIELD_GET(DISPC_VID_SIZE_SIZEX_MASK, val) + 1;
+               *height = FIELD_GET(DISPC_VID_SIZE_SIZEY_MASK, val) + 1;
+       } else {
+               dispc_plane_get_picture_size(dispc, hw_plane, width, height);
+       }
+}
+
+const struct drm_format_info *
+dispc_plane_get_current_format(struct dispc_device *dispc, u32 hw_plane)
+{
+       const struct drm_format_info *info;
+       u32 fourcc;
+       u32 val;
+
+       val = dispc_vid_read(dispc, hw_plane, DISPC_VID_ATTRIBUTES);
+       fourcc = dispc_plane_find_fourcc_by_dss_code(
+               FIELD_GET(DISPC_VID_ATTRIBUTES_FORMAT_MASK, val));
+       if (!fourcc)
+               return ERR_PTR(-EINVAL);
+
+       info = drm_format_info(fourcc);
+       if (!info)
+               return ERR_PTR(-EINVAL);
+
+       return info;
+}
+
+u8 dispc_plane_get_global_alpha(struct dispc_device *dispc, u32 hw_plane)
+{
+       return FIELD_GET(DISPC_VID_GLOBAL_ALPHA_GLOBALALPHA_MASK,
+                        dispc_vid_read(dispc, hw_plane,
+                                       DISPC_VID_GLOBAL_ALPHA));
+}
+
+u32 dispc_plane_get_pixel_inc(struct dispc_device *dispc, u32 hw_plane)
+{
+       return dispc_vid_read(dispc, hw_plane, DISPC_VID_PIXEL_INC);
+}
+
+u32 dispc_plane_get_row_inc(struct dispc_device *dispc, u32 hw_plane)
+{
+       return dispc_vid_read(dispc, hw_plane, DISPC_VID_ROW_INC);
+}
+
+u16 dispc_plane_get_blend_mode(struct dispc_device *dispc, u32 hw_plane)
+{
+       u32 val = dispc_vid_read(dispc, hw_plane, DISPC_VID_ATTRIBUTES);
+
+       if (FIELD_GET(DISPC_VID_ATTRIBUTES_PREMULTIPLYALPHA_MASK, val))
+               return DRM_MODE_BLEND_PREMULTI;
+       else
+               return DRM_MODE_BLEND_COVERAGE;
+}
+
 void dispc_plane_enable(struct dispc_device *dispc, u32 hw_plane, bool enable)
 {
@@ -2278,4 +2592,10 @@ void dispc_plane_enable(struct dispc_device *dispc, u32 
hw_plane, bool enable)
 }
 
+bool dispc_plane_is_enabled(struct dispc_device *dispc, u32 hw_plane)
+{
+       return FIELD_GET(DISPC_VID_ATTRIBUTES_ENABLE_MASK,
+                        dispc_vid_read(dispc, hw_plane, DISPC_VID_ATTRIBUTES));
+}
+
 static u32 dispc_vid_get_fifo_size(struct dispc_device *dispc, u32 hw_plane)
 {
diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h 
b/drivers/gpu/drm/tidss/tidss_dispc.h
index e51a2088b40c..0496dec09310 100644
--- a/drivers/gpu/drm/tidss/tidss_dispc.h
+++ b/drivers/gpu/drm/tidss/tidss_dispc.h
@@ -118,4 +118,5 @@ void dispc_vp_prepare(struct dispc_device *dispc, u32 
hw_videoport,
                      const struct drm_crtc_state *state);
 void dispc_vp_enable(struct dispc_device *dispc, u32 hw_videoport);
+bool dispc_vp_is_enabled(struct dispc_device *dispc, u32 hw_videoport);
 void dispc_vp_disable(struct dispc_device *dispc, u32 hw_videoport);
 void dispc_vp_unprepare(struct dispc_device *dispc, u32 hw_videoport);
@@ -133,4 +134,10 @@ int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 
hw_videoport,
 void dispc_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
                    const struct drm_crtc_state *state, bool newmodeset);
+int dispc_vp_readout_mode(struct dispc_device *dispc,
+                         u32 hw_videoport,
+                         struct drm_display_mode *mode);
+int dispc_vp_get_bus_format(struct dispc_device *dispc, u32 hw_videoport,
+                           u32 *bus_fmt);
+u32 dispc_vp_get_bus_flags(struct dispc_device *dispc, u32 hw_videoport);
 
 int dispc_runtime_suspend(struct dispc_device *dispc);
@@ -143,5 +150,16 @@ void dispc_plane_setup(struct dispc_device *dispc, u32 
hw_plane,
                       struct drm_plane_state *state,
                       u32 hw_videoport);
+void dispc_plane_get_picture_size(struct dispc_device *dispc, u32 hw_plane,
+                                 unsigned int *width, unsigned int *height);
+void dispc_plane_get_size(struct dispc_device *dispc, u32 hw_plane,
+                         unsigned int *width, unsigned int *height);
+const struct drm_format_info *
+dispc_plane_get_current_format(struct dispc_device *dispc, u32 hw_plane);
+u8 dispc_plane_get_global_alpha(struct dispc_device *dispc, u32 hw_plane);
+u32 dispc_plane_get_pixel_inc(struct dispc_device *dispc, u32 hw_plane);
+u32 dispc_plane_get_row_inc(struct dispc_device *dispc, u32 hw_plane);
+u16 dispc_plane_get_blend_mode(struct dispc_device *dispc, u32 hw_plane);
 void dispc_plane_enable(struct dispc_device *dispc, u32 hw_plane, bool enable);
+bool dispc_plane_is_enabled(struct dispc_device *dispc, u32 hw_plane);
 const u32 *dispc_plane_formats(struct dispc_device *dispc, unsigned int *len);
 
diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c 
b/drivers/gpu/drm/tidss/tidss_encoder.c
index 10dbcc6cdf6a..5034e601ac86 100644
--- a/drivers/gpu/drm/tidss/tidss_encoder.c
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -6,6 +6,8 @@
 
 #include <linux/export.h>
+#include <linux/media-bus-format.h>
 
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
@@ -74,4 +76,42 @@ static int tidss_bridge_atomic_check(struct drm_bridge 
*bridge,
 }
 
+static int tidss_readout_state(struct drm_bridge *bridge,
+                              struct drm_atomic_sro_state *state,
+                              struct drm_bridge_state *bridge_state,
+                              struct drm_crtc_state *crtc_state,
+                              struct drm_connector_state *conn_state)
+{
+       struct tidss_crtc_state *tcrtc_state = to_tidss_crtc_state(crtc_state);
+
+       /*
+        * Reconstruct the bridge bus configuration from the CRTC readout state.
+        *
+        * During normal atomic check,
+        * drm_atomic_bridge_chain_select_bus_fmts() and
+        * drm_atomic_bridge_propagate_bus_flags() walk the bridge chain to
+        * negotiate formats and propagate flags. During SRO readout, those
+        * functions do not run, so each bridge must reconstruct its own
+        * state.
+        *
+        * This bridge is the first in the chain, so the CRTC state (read out
+        * before bridges) is used as the source. The output bus format is
+        * taken from the CRTC's bus_format, which during normal atomic check
+        * is set by tidss_bridge_atomic_check() to the next bridge's
+        * negotiated input format. The input bus format is
+        * MEDIA_BUS_FMT_FIXED since this bridge does not implement
+        * atomic_get_input_bus_fmts. Bus flags are taken from the CRTC
+        * state, which reads them from hardware registers during its own
+        * readout.
+        */
+
+       bridge_state->input_bus_cfg.format = MEDIA_BUS_FMT_FIXED;
+       bridge_state->output_bus_cfg.format = tcrtc_state->bus_format;
+
+       bridge_state->output_bus_cfg.flags = tcrtc_state->bus_flags;
+       bridge_state->input_bus_cfg.flags = tcrtc_state->bus_flags;
+
+       return 0;
+}
+
 static const struct drm_bridge_funcs tidss_bridge_funcs = {
        .attach                         = tidss_bridge_attach,
@@ -80,4 +120,6 @@ static const struct drm_bridge_funcs tidss_bridge_funcs = {
        .atomic_duplicate_state         = 
drm_atomic_helper_bridge_duplicate_state,
        .atomic_destroy_state           = 
drm_atomic_helper_bridge_destroy_state,
+       .atomic_sro_readout_state       = tidss_readout_state,
+       .atomic_sro_compare_state       = 
drm_atomic_helper_bridge_compare_state,
 };
 
diff --git a/drivers/gpu/drm/tidss/tidss_kms.c 
b/drivers/gpu/drm/tidss/tidss_kms.c
index 3b97c08c4f71..b0b69b853111 100644
--- a/drivers/gpu/drm/tidss/tidss_kms.c
+++ b/drivers/gpu/drm/tidss/tidss_kms.c
@@ -7,4 +7,6 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_gem_framebuffer_helper.h>
@@ -65,5 +67,6 @@ static void tidss_atomic_commit_tail(struct drm_atomic_commit 
*old_state)
 
 static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = {
-       .atomic_commit_tail = tidss_atomic_commit_tail,
+       .atomic_commit_tail     = tidss_atomic_commit_tail,
+       .atomic_sro_build_state = drm_atomic_helper_sro_build_state,
 };
 
@@ -134,4 +137,5 @@ static const struct drm_mode_config_funcs mode_config_funcs 
= {
        .atomic_check = tidss_atomic_check,
        .atomic_commit = drm_atomic_helper_commit,
+       .atomic_sro_readout_state = drm_atomic_helper_sro_readout_state,
        .hw_reset = tidss_hw_reset,
 };
diff --git a/drivers/gpu/drm/tidss/tidss_plane.c 
b/drivers/gpu/drm/tidss/tidss_plane.c
index b0d5d6959b88..ebdb63f8e8f1 100644
--- a/drivers/gpu/drm/tidss/tidss_plane.c
+++ b/drivers/gpu/drm/tidss/tidss_plane.c
@@ -7,4 +7,6 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_readout.h>
+#include <drm/drm_atomic_state_readout_helper.h>
 #include <drm/drm_blend.h>
 #include <drm/drm_crtc.h>
@@ -13,4 +15,5 @@
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_panic_helper.h>
 
@@ -177,4 +180,177 @@ static const struct drm_plane_helper_funcs 
tidss_primary_plane_helper_funcs = {
 };
 
+static const struct drm_framebuffer_funcs tidss_plane_readout_fb_funcs = {
+       .destroy        = drm_gem_fb_destroy,
+};
+
+static struct drm_framebuffer *tidss_plane_readout_fb(struct drm_plane *plane)
+{
+       struct drm_device *ddev = plane->dev;
+       struct tidss_device *tidss = to_tidss(ddev);
+       struct dispc_device *dispc = tidss->dispc;
+       struct tidss_plane *tplane = to_tidss_plane(plane);
+       const struct drm_format_info *info;
+       struct drm_framebuffer *fb;
+       int ret;
+
+       fb = kzalloc_obj(*fb);
+       if (!fb)
+               return ERR_PTR(-ENOMEM);
+
+       fb->dev = plane->dev;
+
+       info = dispc_plane_get_current_format(dispc, tplane->hw_plane_id);
+       if (IS_ERR(info)) {
+               ret = PTR_ERR(info);
+               goto err_free_fb;
+       }
+
+       // TODO: Figure out YUV and multiplanar formats, along with
+       // the color encoding and range
+       if (info->is_yuv) {
+               ret = -EINVAL;
+               goto err_free_fb;
+       }
+
+       fb->format = info;
+
+       dispc_plane_get_picture_size(dispc, tplane->hw_plane_id, &fb->width,
+                                    &fb->height);
+
+       /*
+        * A pixel increment of 1 means that the DMA engine fetches every
+        * pixel of the source buffer. Any other value means that
+        * horizontal decimation is enabled, and the buffer geometry
+        * cannot be reconstructed from the registers.
+        */
+       if (dispc_plane_get_pixel_inc(dispc, tplane->hw_plane_id) != 1) {
+               ret = -EINVAL;
+               goto err_free_fb;
+       }
+
+       /*
+        * The row increment register holds the extra bytes between the
+        * end of a row of the picture and the beginning of the next one,
+        * which includes any padding the firmware might have programmed.
+        */
+       fb->pitches[0] = fb->width * (drm_format_info_bpp(info, 0) / 8) +
+                        dispc_plane_get_row_inc(dispc, tplane->hw_plane_id) - 
1;
+
+       /*
+        * TODO: The DMA base address has the offsets baked in, and there
+        * is no backing buffer to recover them from.
+        */
+       fb->offsets[0] = 0;
+
+       ret = drm_framebuffer_init(plane->dev, fb, 
&tidss_plane_readout_fb_funcs);
+       if (ret) {
+               kfree(fb);
+               return ERR_PTR(ret);
+       }
+
+       return fb;
+
+err_free_fb:
+       kfree(fb);
+       return ERR_PTR(ret);
+}
+
+static struct drm_crtc *tidss_plane_readout_crtc(struct drm_plane *plane)
+{
+       struct drm_device *dev = plane->dev;
+
+       /* TODO: Add support for multiple CRTCs */
+       if (dev->num_crtcs != 1)
+               return ERR_PTR(-EINVAL);
+
+       return list_first_entry(&dev->mode_config.crtc_list, struct drm_crtc, 
head);
+}
+
+static int tidss_plane_atomic_readout_state(struct drm_plane *plane,
+                                           struct drm_atomic_sro_state *state,
+                                           struct drm_plane_state *plane_state)
+{
+       struct drm_device *ddev = plane->dev;
+       struct tidss_device *tidss = to_tidss(ddev);
+       struct dispc_device *dispc = tidss->dispc;
+       struct tidss_plane *tplane = to_tidss_plane(plane);
+       struct drm_crtc_state *crtc_state;
+       struct drm_framebuffer *fb;
+       struct drm_crtc *crtc;
+       unsigned int in_w, in_h;
+       int ret;
+
+       tidss_runtime_get(tidss);
+
+       if (!dispc_plane_is_enabled(dispc, tplane->hw_plane_id))
+               goto out;
+
+       fb = tidss_plane_readout_fb(plane);
+       if (IS_ERR(fb)) {
+               ret = PTR_ERR(fb);
+               goto err_runtime_pm;
+       }
+
+       crtc = tidss_plane_readout_crtc(plane);
+       if (IS_ERR(crtc)) {
+               drm_framebuffer_put(fb);
+               ret = PTR_ERR(crtc);
+               goto err_runtime_pm;
+       }
+
+       plane_state->fb = fb;
+       plane_state->crtc = crtc;
+       plane_state->visible = true;
+
+       dispc_plane_get_picture_size(dispc, tplane->hw_plane_id, &in_w, &in_h);
+       plane_state->src_w = in_w << 16;
+       plane_state->src_h = in_h << 16;
+
+       dispc_plane_get_size(dispc, tplane->hw_plane_id, &plane_state->crtc_w,
+                            &plane_state->crtc_h);
+
+       // TODO: Handle crtc_x/crtc_x/src_x/src_y
+       // crtc_x/crtc_y are handled by DISPC_OVR_ATTRIBUTES / 
OVR1_DSS_ATTRIBUTES
+
+       // TODO: Handle zpos, see DISPC_OVR_ATTRIBUTES / OVR1_DSS_ATTRIBUTES
+
+       plane_state->src.x1 = 0;
+       plane_state->src.x2 = plane_state->src_w;
+       plane_state->src.y1 = 0;
+       plane_state->src.y2 = plane_state->src_h;
+       plane_state->dst.x1 = 0;
+       plane_state->dst.x2 = plane_state->crtc_w;
+       plane_state->dst.y1 = 0;
+       plane_state->dst.y2 = plane_state->crtc_h;
+
+       /*
+        * The global alpha register is 8 bits wide and is programmed
+        * from the high byte of the 16 bits alpha property.
+        * Replicating the byte is the only inverse that preserves the
+        * fraction of full opacity (0x00 -> 0x0000, 0xff -> 0xffff)
+        * and round-trips through a program-then-readout cycle.
+        */
+       plane_state->alpha =
+               dispc_plane_get_global_alpha(dispc, tplane->hw_plane_id) * 
0x0101;
+       plane_state->pixel_blend_mode =
+               dispc_plane_get_blend_mode(dispc, tplane->hw_plane_id);
+
+       crtc_state = drm_atomic_sro_get_crtc_state(state, crtc);
+       if (!crtc_state) {
+               ret = -ENODEV;
+               goto err_runtime_pm;
+       }
+
+       crtc_state->plane_mask |= drm_plane_mask(plane);
+
+out:
+       tidss_runtime_put(tidss);
+       return 0;
+
+err_runtime_pm:
+       tidss_runtime_put(tidss);
+       return ret;
+}
+
 static const struct drm_plane_funcs tidss_plane_funcs = {
        .update_plane = drm_atomic_helper_update_plane,
@@ -182,4 +358,6 @@ static const struct drm_plane_funcs tidss_plane_funcs = {
        .destroy = drm_plane_destroy,
        .atomic_create_state = drm_atomic_helper_plane_create_state,
+       .atomic_sro_readout_state = tidss_plane_atomic_readout_state,
+       .atomic_sro_compare_state = drm_atomic_helper_plane_compare_state,
        .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
        .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,

-- 
2.55.0

Reply via email to