// SPDX-License-Identifier: GPL-2.0 /* * R-Car Display Unit Writeback Support * * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com> */ #include <drm/drm_atomic_helper.h> #include <drm/drm_device.h> #include <drm/drm_edid.h> #include <drm/drm_fourcc.h> #include <drm/drm_framebuffer.h> #include <drm/drm_probe_helper.h> #include <drm/drm_writeback.h> #include "rcar_du_crtc.h" #include "rcar_du_drv.h" #include "rcar_du_kms.h" #include "rcar_du_writeback.h" /** * struct rcar_du_wb_conn_state - Driver-specific writeback connector state * @state: base DRM connector state * @format: format of the writeback framebuffer */ struct rcar_du_wb_conn_state { struct drm_connector_state state; const struct rcar_du_format_info *format; }; #define to_rcar_wb_conn_state(s) \ container_of(s, struct rcar_du_wb_conn_state, state) /** * struct rcar_du_wb_job - Driver-private data for writeback jobs * @sg_tables: scatter-gather tables for the framebuffer memory */ struct rcar_du_wb_job { struct sg_table sg_tables[3]; }; static int rcar_du_wb_conn_get_modes(struct drm_connector *connector) { struct drm_device *dev = connector->dev; return drm_add_modes_noedid(connector, dev->mode_config.max_width, dev->mode_config.max_height); } static int rcar_du_wb_prepare_job(struct drm_writeback_connector *connector, struct drm_writeback_job *job) { struct rcar_du_crtc *rcrtc = wb_to_rcar_crtc(connector); struct rcar_du_wb_job *rjob; int ret; if (!job->fb) return 0; rjob = kzalloc(sizeof(*rjob), GFP_KERNEL); if (!rjob) return -ENOMEM; /* Map the framebuffer to the VSP. */ ret = rcar_du_vsp_map_fb(rcrtc->vsp, job->fb, rjob->sg_tables); if (ret < 0) { kfree(rjob); return ret; } job->priv = rjob; return 0; } static void rcar_du_wb_cleanup_job(struct drm_writeback_connector *connector, struct drm_writeback_job *job) { struct rcar_du_crtc *rcrtc = wb_to_rcar_crtc(connector); struct rcar_du_wb_job *rjob = job->priv; if (!job->fb) return; rcar_du_vsp_unmap_fb(rcrtc->vsp, job->fb, rjob->sg_tables); kfree(rjob); } static const struct drm_connector_helper_funcs rcar_du_wb_conn_helper_funcs = { .get_modes = rcar_du_wb_conn_get_modes, .prepare_writeback_job = rcar_du_wb_prepare_job, .cleanup_writeback_job = rcar_du_wb_cleanup_job, }; static struct drm_connector_state * rcar_du_wb_conn_duplicate_state(struct drm_connector *connector) { struct rcar_du_wb_conn_state *copy; if (WARN_ON(!connector->state)) return NULL; copy = kzalloc(sizeof(*copy), GFP_KERNEL); if (!copy) return NULL; __drm_atomic_helper_connector_duplicate_state(connector, ©->state); return ©->state; } static void rcar_du_wb_conn_destroy_state(struct drm_connector *connector, struct drm_connector_state *state) { __drm_atomic_helper_connector_destroy_state(state); kfree(to_rcar_wb_conn_state(state)); } static void rcar_du_wb_conn_reset(struct drm_connector *connector) { struct rcar_du_wb_conn_state *state; if (connector->state) { rcar_du_wb_conn_destroy_state(connector, connector->state); connector->state = NULL; } state = kzalloc(sizeof(*state), GFP_KERNEL); if (state == NULL) return; __drm_atomic_helper_connector_reset(connector, &state->state); } static const struct drm_connector_funcs rcar_du_wb_conn_funcs = { .reset = rcar_du_wb_conn_reset, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = drm_connector_cleanup, .atomic_duplicate_state = rcar_du_wb_conn_duplicate_state, .atomic_destroy_state = rcar_du_wb_conn_destroy_state, }; static int rcar_du_wb_enc_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { struct rcar_du_wb_conn_state *wb_state = to_rcar_wb_conn_state(conn_state); const struct drm_display_mode *mode = &crtc_state->mode; struct drm_device *dev = encoder->dev; struct drm_framebuffer *fb; if (!conn_state->writeback_job) return 0; fb = conn_state->writeback_job->fb; /* * Verify that the framebuffer format is supported and that its size * matches the current mode. */ if (fb->width != mode->hdisplay || fb->height != mode->vdisplay) { dev_dbg(dev->dev, "%s: invalid framebuffer size %ux%u\n", __func__, fb->width, fb->height); return -EINVAL; } wb_state->format = rcar_du_format_info(fb->format->format); if (wb_state->format == NULL) { dev_dbg(dev->dev, "%s: unsupported format %p4cc\n", __func__, &fb->format->format); return -EINVAL; } return 0; } static const struct drm_encoder_helper_funcs rcar_du_wb_enc_helper_funcs = { .atomic_check = rcar_du_wb_enc_atomic_check, }; /* * Only RGB formats are currently supported as the VSP outputs RGB to the DU * and can't convert to YUV separately for writeback. */ static const u32 writeback_formats[] = { DRM_FORMAT_RGB332, DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB4444, DRM_FORMAT_ARGB1555, DRM_FORMAT_XRGB1555, DRM_FORMAT_RGB565, DRM_FORMAT_BGR888, DRM_FORMAT_RGB888, DRM_FORMAT_BGRA8888, DRM_FORMAT_BGRX8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, }; int rcar_du_writeback_init(struct rcar_du_device *rcdu, struct rcar_du_crtc *rcrtc) { struct drm_writeback_connector *wb_conn = &rcrtc->writeback; drm_connector_helper_add(&wb_conn->base, &rcar_du_wb_conn_helper_funcs); return drm_writeback_connector_init(&rcdu->ddev, wb_conn, &rcar_du_wb_conn_funcs, &rcar_du_wb_enc_helper_funcs, writeback_formats, ARRAY_SIZE(writeback_formats), 1 << drm_crtc_index(&rcrtc->crtc)); } void rcar_du_writeback_setup(struct rcar_du_crtc *rcrtc, struct vsp1_du_writeback_config *cfg) { struct rcar_du_wb_conn_state *wb_state; struct drm_connector_state *state; struct rcar_du_wb_job *rjob; struct drm_framebuffer *fb; unsigned int i; state = rcrtc->writeback.base.state; if (!state || !state->writeback_job) return; fb = state->writeback_job->fb; rjob = state->writeback_job->priv; wb_state = to_rcar_wb_conn_state(state); cfg->pixelformat = wb_state->format->v4l2; cfg->pitch = fb->pitches[0]; for (i = 0; i < wb_state->format->planes; ++i) cfg->mem[i] = sg_dma_address(rjob->sg_tables[i].sgl) + fb->offsets[i]; drm_writeback_queue_job(&rcrtc->writeback, state); } void rcar_du_writeback_complete(struct rcar_du_crtc *rcrtc) { drm_writeback_signal_completion(&rcrtc->writeback, 0); }