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
