| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/export.h> | |
| #include <linux/slab.h> | |
| #include <drm/drm_atomic.h> | |
| #include <drm/drm_atomic_helper.h> | |
| #include <drm/drm_atomic_state_helper.h> | |
| #include <drm/drm_damage_helper.h> | |
| #include <drm/drm_drv.h> | |
| #include <drm/drm_edid.h> | |
| #include <drm/drm_fourcc.h> | |
| #include <drm/drm_framebuffer.h> | |
| #include <drm/drm_gem_framebuffer_helper.h> | |
| #include <drm/drm_panic.h> | |
| #include <drm/drm_print.h> | |
| #include <drm/drm_probe_helper.h> | |
| #include "drm_sysfb_helper.h" | |
| struct drm_display_mode drm_sysfb_mode(unsigned int width, | |
| unsigned int height, | |
| unsigned int width_mm, | |
| unsigned int height_mm) | |
| { | |
| /* | |
| * Assume a monitor resolution of 96 dpi to | |
| * get a somewhat reasonable screen size. | |
| */ | |
| if (!width_mm) | |
| width_mm = DRM_MODE_RES_MM(width, 96ul); | |
| if (!height_mm) | |
| height_mm = DRM_MODE_RES_MM(height, 96ul); | |
| { | |
| const struct drm_display_mode mode = { | |
| DRM_MODE_INIT(60, width, height, width_mm, height_mm) | |
| }; | |
| return mode; | |
| } | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_mode); | |
| /* | |
| * Plane | |
| */ | |
| static u32 to_nonalpha_fourcc(u32 fourcc) | |
| { | |
| /* only handle formats with depth != 0 and alpha channel */ | |
| switch (fourcc) { | |
| case DRM_FORMAT_ARGB1555: | |
| return DRM_FORMAT_XRGB1555; | |
| case DRM_FORMAT_ABGR1555: | |
| return DRM_FORMAT_XBGR1555; | |
| case DRM_FORMAT_RGBA5551: | |
| return DRM_FORMAT_RGBX5551; | |
| case DRM_FORMAT_BGRA5551: | |
| return DRM_FORMAT_BGRX5551; | |
| case DRM_FORMAT_ARGB8888: | |
| return DRM_FORMAT_XRGB8888; | |
| case DRM_FORMAT_ABGR8888: | |
| return DRM_FORMAT_XBGR8888; | |
| case DRM_FORMAT_RGBA8888: | |
| return DRM_FORMAT_RGBX8888; | |
| case DRM_FORMAT_BGRA8888: | |
| return DRM_FORMAT_BGRX8888; | |
| case DRM_FORMAT_ARGB2101010: | |
| return DRM_FORMAT_XRGB2101010; | |
| case DRM_FORMAT_ABGR2101010: | |
| return DRM_FORMAT_XBGR2101010; | |
| case DRM_FORMAT_RGBA1010102: | |
| return DRM_FORMAT_RGBX1010102; | |
| case DRM_FORMAT_BGRA1010102: | |
| return DRM_FORMAT_BGRX1010102; | |
| } | |
| return fourcc; | |
| } | |
| static bool is_listed_fourcc(const u32 *fourccs, size_t nfourccs, u32 fourcc) | |
| { | |
| const u32 *fourccs_end = fourccs + nfourccs; | |
| while (fourccs < fourccs_end) { | |
| if (*fourccs == fourcc) | |
| return true; | |
| ++fourccs; | |
| } | |
| return false; | |
| } | |
| /** | |
| * drm_sysfb_build_fourcc_list - Filters a list of supported color formats against | |
| * the device's native formats | |
| * @dev: DRM device | |
| * @native_fourccs: 4CC codes of natively supported color formats | |
| * @native_nfourccs: The number of entries in @native_fourccs | |
| * @fourccs_out: Returns 4CC codes of supported color formats | |
| * @nfourccs_out: The number of available entries in @fourccs_out | |
| * | |
| * This function create a list of supported color format from natively | |
| * supported formats and additional emulated formats. | |
| * At a minimum, most userspace programs expect at least support for | |
| * XRGB8888 on the primary plane. Sysfb devices that have to emulate | |
| * the format should use drm_sysfb_build_fourcc_list() to create a list | |
| * of supported color formats. The returned list can be handed over to | |
| * drm_universal_plane_init() et al. Native formats will go before | |
| * emulated formats. Native formats with alpha channel will be replaced | |
| * by equal formats without alpha channel, as primary planes usually | |
| * don't support alpha. Other heuristics might be applied to optimize | |
| * the sorting order. Formats near the beginning of the list are usually | |
| * preferred over formats near the end of the list. | |
| * | |
| * Returns: | |
| * The number of color-formats 4CC codes returned in @fourccs_out. | |
| */ | |
| size_t drm_sysfb_build_fourcc_list(struct drm_device *dev, | |
| const u32 *native_fourccs, size_t native_nfourccs, | |
| u32 *fourccs_out, size_t nfourccs_out) | |
| { | |
| /* | |
| * XRGB8888 is the default fallback format for most of userspace | |
| * and it's currently the only format that should be emulated for | |
| * the primary plane. Only if there's ever another default fallback, | |
| * it should be added here. | |
| */ | |
| static const u32 extra_fourccs[] = { | |
| DRM_FORMAT_XRGB8888, | |
| }; | |
| static const size_t extra_nfourccs = ARRAY_SIZE(extra_fourccs); | |
| u32 *fourccs = fourccs_out; | |
| const u32 *fourccs_end = fourccs_out + nfourccs_out; | |
| size_t i; | |
| /* | |
| * The device's native formats go first. | |
| */ | |
| for (i = 0; i < native_nfourccs; ++i) { | |
| /* | |
| * Several DTs, boot loaders and firmware report native | |
| * alpha formats that are non-alpha formats instead. So | |
| * replace alpha formats by non-alpha formats. | |
| */ | |
| u32 fourcc = to_nonalpha_fourcc(native_fourccs[i]); | |
| if (is_listed_fourcc(fourccs_out, fourccs - fourccs_out, fourcc)) { | |
| continue; /* skip duplicate entries */ | |
| } else if (fourccs == fourccs_end) { | |
| drm_warn(dev, "Ignoring native format %p4cc\n", &fourcc); | |
| continue; /* end of available output buffer */ | |
| } | |
| drm_dbg_kms(dev, "adding native format %p4cc\n", &fourcc); | |
| *fourccs = fourcc; | |
| ++fourccs; | |
| } | |
| /* | |
| * The extra formats, emulated by the driver, go second. | |
| */ | |
| for (i = 0; (i < extra_nfourccs) && (fourccs < fourccs_end); ++i) { | |
| u32 fourcc = extra_fourccs[i]; | |
| if (is_listed_fourcc(fourccs_out, fourccs - fourccs_out, fourcc)) { | |
| continue; /* skip duplicate and native entries */ | |
| } else if (fourccs == fourccs_end) { | |
| drm_warn(dev, "Ignoring emulated format %p4cc\n", &fourcc); | |
| continue; /* end of available output buffer */ | |
| } | |
| drm_dbg_kms(dev, "adding emulated format %p4cc\n", &fourcc); | |
| *fourccs = fourcc; | |
| ++fourccs; | |
| } | |
| return fourccs - fourccs_out; | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_build_fourcc_list); | |
| static void drm_sysfb_plane_state_destroy(struct drm_sysfb_plane_state *sysfb_plane_state) | |
| { | |
| __drm_gem_destroy_shadow_plane_state(&sysfb_plane_state->base); | |
| kfree(sysfb_plane_state); | |
| } | |
| static void drm_sysfb_memcpy(struct iosys_map *dst, const unsigned int *dst_pitch, | |
| const struct iosys_map *src, const struct drm_framebuffer *fb, | |
| const struct drm_rect *clip, struct drm_format_conv_state *state) | |
| { | |
| drm_fb_memcpy(dst, dst_pitch, src, fb, clip); | |
| } | |
| static drm_sysfb_blit_func drm_sysfb_get_blit_func(u32 dst_format, u32 src_format) | |
| { | |
| if (src_format == dst_format) { | |
| return drm_sysfb_memcpy; | |
| } else if (src_format == DRM_FORMAT_XRGB8888) { | |
| switch (dst_format) { | |
| case DRM_FORMAT_RGB565: | |
| return drm_fb_xrgb8888_to_rgb565; | |
| case DRM_FORMAT_RGB565 | DRM_FORMAT_BIG_ENDIAN: | |
| return drm_fb_xrgb8888_to_rgb565be; | |
| case DRM_FORMAT_XRGB1555: | |
| return drm_fb_xrgb8888_to_xrgb1555; | |
| case DRM_FORMAT_ARGB1555: | |
| return drm_fb_xrgb8888_to_argb1555; | |
| case DRM_FORMAT_RGBA5551: | |
| return drm_fb_xrgb8888_to_rgba5551; | |
| case DRM_FORMAT_RGB888: | |
| return drm_fb_xrgb8888_to_rgb888; | |
| case DRM_FORMAT_BGR888: | |
| return drm_fb_xrgb8888_to_bgr888; | |
| case DRM_FORMAT_ARGB8888: | |
| return drm_fb_xrgb8888_to_argb8888; | |
| case DRM_FORMAT_XBGR8888: | |
| return drm_fb_xrgb8888_to_xbgr8888; | |
| case DRM_FORMAT_ABGR8888: | |
| return drm_fb_xrgb8888_to_abgr8888; | |
| case DRM_FORMAT_XRGB2101010: | |
| return drm_fb_xrgb8888_to_xrgb2101010; | |
| case DRM_FORMAT_ARGB2101010: | |
| return drm_fb_xrgb8888_to_argb2101010; | |
| case DRM_FORMAT_BGRX8888: | |
| return drm_fb_xrgb8888_to_bgrx8888; | |
| case DRM_FORMAT_RGB332: | |
| return drm_fb_xrgb8888_to_rgb332; | |
| } | |
| } | |
| return NULL; | |
| } | |
| int drm_sysfb_plane_helper_begin_fb_access(struct drm_plane *plane, | |
| struct drm_plane_state *plane_state) | |
| { | |
| struct drm_device *dev = plane->dev; | |
| struct drm_sysfb_plane_state *sysfb_plane_state = to_drm_sysfb_plane_state(plane_state); | |
| struct drm_framebuffer *fb = plane_state->fb; | |
| struct drm_crtc_state *crtc_state; | |
| struct drm_sysfb_crtc_state *sysfb_crtc_state; | |
| drm_sysfb_blit_func blit_to_crtc; | |
| int ret; | |
| ret = drm_gem_begin_shadow_fb_access(plane, plane_state); | |
| if (ret) | |
| return ret; | |
| if (!fb) | |
| return 0; | |
| ret = -EINVAL; | |
| crtc_state = drm_atomic_get_new_crtc_state(plane_state->state, plane_state->crtc); | |
| if (drm_WARN_ON_ONCE(dev, !crtc_state)) | |
| goto err_drm_gem_end_shadow_fb_access; | |
| sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state); | |
| if (drm_WARN_ON_ONCE(dev, !sysfb_crtc_state->format)) | |
| goto err_drm_gem_end_shadow_fb_access; | |
| blit_to_crtc = drm_sysfb_get_blit_func(sysfb_crtc_state->format->format, | |
| fb->format->format); | |
| if (!blit_to_crtc) { | |
| drm_warn_once(dev, "No blit helper from %p4cc to %p4cc found.\n", | |
| &fb->format->format, &sysfb_crtc_state->format->format); | |
| goto err_drm_gem_end_shadow_fb_access; | |
| } | |
| sysfb_plane_state->blit_to_crtc = blit_to_crtc; | |
| return 0; | |
| err_drm_gem_end_shadow_fb_access: | |
| drm_gem_end_shadow_fb_access(plane, plane_state); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_plane_helper_begin_fb_access); | |
| int drm_sysfb_plane_helper_atomic_check(struct drm_plane *plane, | |
| struct drm_atomic_commit *new_state) | |
| { | |
| struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev); | |
| struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(new_state, plane); | |
| struct drm_shadow_plane_state *new_shadow_plane_state = | |
| to_drm_shadow_plane_state(new_plane_state); | |
| struct drm_framebuffer *new_fb = new_plane_state->fb; | |
| struct drm_crtc *new_crtc = new_plane_state->crtc; | |
| struct drm_crtc_state *new_crtc_state = NULL; | |
| struct drm_sysfb_crtc_state *new_sysfb_crtc_state; | |
| int ret; | |
| if (new_crtc) | |
| new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc); | |
| ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state, | |
| DRM_PLANE_NO_SCALING, | |
| DRM_PLANE_NO_SCALING, | |
| false, false); | |
| if (ret) | |
| return ret; | |
| else if (!new_plane_state->visible) | |
| return 0; | |
| new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc); | |
| new_sysfb_crtc_state = to_drm_sysfb_crtc_state(new_crtc_state); | |
| new_sysfb_crtc_state->format = sysfb->fb_format; | |
| if (new_fb->format != new_sysfb_crtc_state->format) { | |
| void *buf; | |
| /* format conversion necessary; reserve buffer */ | |
| buf = drm_format_conv_state_reserve(&new_shadow_plane_state->fmtcnv_state, | |
| sysfb->fb_pitch, GFP_KERNEL); | |
| if (!buf) | |
| return -ENOMEM; | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_check); | |
| void drm_sysfb_plane_helper_atomic_update(struct drm_plane *plane, struct drm_atomic_commit *state) | |
| { | |
| struct drm_device *dev = plane->dev; | |
| struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev); | |
| struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); | |
| struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); | |
| struct drm_sysfb_plane_state *sysfb_plane_state = to_drm_sysfb_plane_state(plane_state); | |
| struct drm_shadow_plane_state *shadow_plane_state = &sysfb_plane_state->base; | |
| struct drm_framebuffer *fb = plane_state->fb; | |
| unsigned int dst_pitch = sysfb->fb_pitch; | |
| struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); | |
| struct drm_sysfb_crtc_state *sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state); | |
| const struct drm_format_info *dst_format = sysfb_crtc_state->format; | |
| drm_sysfb_blit_func blit_to_crtc = sysfb_plane_state->blit_to_crtc; | |
| struct drm_atomic_helper_damage_iter iter; | |
| struct drm_rect damage; | |
| int ret, idx; | |
| ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); | |
| if (ret) | |
| return; | |
| if (!drm_dev_enter(dev, &idx)) | |
| goto out_drm_gem_fb_end_cpu_access; | |
| drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); | |
| drm_atomic_for_each_plane_damage(&iter, &damage) { | |
| struct iosys_map dst = sysfb->fb_addr; | |
| struct drm_rect dst_clip = plane_state->dst; | |
| if (!drm_rect_intersect(&dst_clip, &damage)) | |
| continue; | |
| iosys_map_incr(&dst, drm_fb_clip_offset(dst_pitch, dst_format, &dst_clip)); | |
| blit_to_crtc(&dst, &dst_pitch, shadow_plane_state->data, fb, &damage, | |
| &shadow_plane_state->fmtcnv_state); | |
| } | |
| drm_dev_exit(idx); | |
| out_drm_gem_fb_end_cpu_access: | |
| drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_update); | |
| void drm_sysfb_plane_helper_atomic_disable(struct drm_plane *plane, | |
| struct drm_atomic_commit *state) | |
| { | |
| struct drm_device *dev = plane->dev; | |
| struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev); | |
| struct iosys_map dst = sysfb->fb_addr; | |
| struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); | |
| void __iomem *dst_vmap = dst.vaddr_iomem; /* TODO: Use mapping abstraction */ | |
| unsigned int dst_pitch = sysfb->fb_pitch; | |
| const struct drm_format_info *dst_format = sysfb->fb_format; | |
| struct drm_rect dst_clip; | |
| unsigned long lines, linepixels, i; | |
| int idx; | |
| drm_rect_init(&dst_clip, | |
| plane_state->src_x >> 16, plane_state->src_y >> 16, | |
| plane_state->src_w >> 16, plane_state->src_h >> 16); | |
| lines = drm_rect_height(&dst_clip); | |
| linepixels = drm_rect_width(&dst_clip); | |
| if (!drm_dev_enter(dev, &idx)) | |
| return; | |
| /* Clear buffer to black if disabled */ | |
| dst_vmap += drm_fb_clip_offset(dst_pitch, dst_format, &dst_clip); | |
| for (i = 0; i < lines; ++i) { | |
| memset_io(dst_vmap, 0, linepixels * dst_format->cpp[0]); | |
| dst_vmap += dst_pitch; | |
| } | |
| drm_dev_exit(idx); | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_disable); | |
| int drm_sysfb_plane_helper_get_scanout_buffer(struct drm_plane *plane, | |
| struct drm_scanout_buffer *sb) | |
| { | |
| struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev); | |
| sb->width = sysfb->fb_mode.hdisplay; | |
| sb->height = sysfb->fb_mode.vdisplay; | |
| sb->format = sysfb->fb_format; | |
| sb->pitch[0] = sysfb->fb_pitch; | |
| sb->map[0] = sysfb->fb_addr; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_plane_helper_get_scanout_buffer); | |
| void drm_sysfb_plane_reset(struct drm_plane *plane) | |
| { | |
| struct drm_sysfb_plane_state *sysfb_plane_state; | |
| if (plane->state) | |
| drm_sysfb_plane_state_destroy(to_drm_sysfb_plane_state(plane->state)); | |
| sysfb_plane_state = kzalloc_obj(*sysfb_plane_state); | |
| if (sysfb_plane_state) | |
| __drm_gem_reset_shadow_plane(plane, &sysfb_plane_state->base); | |
| else | |
| __drm_gem_reset_shadow_plane(plane, NULL); | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_plane_reset); | |
| struct drm_plane_state *drm_sysfb_plane_atomic_duplicate_state(struct drm_plane *plane) | |
| { | |
| struct drm_device *dev = plane->dev; | |
| struct drm_plane_state *plane_state = plane->state; | |
| struct drm_sysfb_plane_state *sysfb_plane_state; | |
| struct drm_sysfb_plane_state *new_sysfb_plane_state; | |
| struct drm_shadow_plane_state *new_shadow_plane_state; | |
| if (drm_WARN_ON(dev, !plane_state)) | |
| return NULL; | |
| sysfb_plane_state = to_drm_sysfb_plane_state(plane_state); | |
| new_sysfb_plane_state = kzalloc_obj(*new_sysfb_plane_state); | |
| if (!new_sysfb_plane_state) | |
| return NULL; | |
| new_shadow_plane_state = &new_sysfb_plane_state->base; | |
| __drm_gem_duplicate_shadow_plane_state(plane, new_shadow_plane_state); | |
| new_sysfb_plane_state->blit_to_crtc = sysfb_plane_state->blit_to_crtc; | |
| return &new_shadow_plane_state->base; | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_plane_atomic_duplicate_state); | |
| void drm_sysfb_plane_atomic_destroy_state(struct drm_plane *plane, | |
| struct drm_plane_state *plane_state) | |
| { | |
| drm_sysfb_plane_state_destroy(to_drm_sysfb_plane_state(plane_state)); | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_plane_atomic_destroy_state); | |
| /* | |
| * CRTC | |
| */ | |
| static void drm_sysfb_crtc_state_destroy(struct drm_sysfb_crtc_state *sysfb_crtc_state) | |
| { | |
| __drm_atomic_helper_crtc_destroy_state(&sysfb_crtc_state->base); | |
| kfree(sysfb_crtc_state); | |
| } | |
| enum drm_mode_status drm_sysfb_crtc_helper_mode_valid(struct drm_crtc *crtc, | |
| const struct drm_display_mode *mode) | |
| { | |
| struct drm_sysfb_device *sysfb = to_drm_sysfb_device(crtc->dev); | |
| return drm_crtc_helper_mode_valid_fixed(crtc, mode, &sysfb->fb_mode); | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_crtc_helper_mode_valid); | |
| int drm_sysfb_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *new_state) | |
| { | |
| struct drm_device *dev = crtc->dev; | |
| struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev); | |
| struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(new_state, crtc); | |
| int ret; | |
| if (!new_crtc_state->enable) | |
| return 0; | |
| ret = drm_atomic_helper_check_crtc_primary_plane(new_crtc_state); | |
| if (ret) | |
| return ret; | |
| if (new_crtc_state->color_mgmt_changed) { | |
| const size_t gamma_lut_length = | |
| sysfb->fb_gamma_lut_size * sizeof(struct drm_color_lut); | |
| const struct drm_property_blob *gamma_lut = new_crtc_state->gamma_lut; | |
| if (gamma_lut && (gamma_lut->length != gamma_lut_length)) { | |
| drm_dbg(dev, "Incorrect gamma_lut length %zu\n", gamma_lut->length); | |
| return -EINVAL; | |
| } | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_crtc_helper_atomic_check); | |
| void drm_sysfb_crtc_reset(struct drm_crtc *crtc) | |
| { | |
| struct drm_sysfb_device *sysfb = to_drm_sysfb_device(crtc->dev); | |
| struct drm_sysfb_crtc_state *sysfb_crtc_state; | |
| if (crtc->state) | |
| drm_sysfb_crtc_state_destroy(to_drm_sysfb_crtc_state(crtc->state)); | |
| sysfb_crtc_state = kzalloc_obj(*sysfb_crtc_state); | |
| if (sysfb_crtc_state) { | |
| sysfb_crtc_state->format = sysfb->fb_format; | |
| __drm_atomic_helper_crtc_reset(crtc, &sysfb_crtc_state->base); | |
| } else { | |
| __drm_atomic_helper_crtc_reset(crtc, NULL); | |
| } | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_crtc_reset); | |
| struct drm_crtc_state *drm_sysfb_crtc_atomic_duplicate_state(struct drm_crtc *crtc) | |
| { | |
| struct drm_device *dev = crtc->dev; | |
| struct drm_crtc_state *crtc_state = crtc->state; | |
| struct drm_sysfb_crtc_state *new_sysfb_crtc_state; | |
| struct drm_sysfb_crtc_state *sysfb_crtc_state; | |
| if (drm_WARN_ON(dev, !crtc_state)) | |
| return NULL; | |
| new_sysfb_crtc_state = kzalloc_obj(*new_sysfb_crtc_state); | |
| if (!new_sysfb_crtc_state) | |
| return NULL; | |
| sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state); | |
| __drm_atomic_helper_crtc_duplicate_state(crtc, &new_sysfb_crtc_state->base); | |
| new_sysfb_crtc_state->format = sysfb_crtc_state->format; | |
| return &new_sysfb_crtc_state->base; | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_crtc_atomic_duplicate_state); | |
| void drm_sysfb_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state) | |
| { | |
| drm_sysfb_crtc_state_destroy(to_drm_sysfb_crtc_state(crtc_state)); | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_crtc_atomic_destroy_state); | |
| /* | |
| * Connector | |
| */ | |
| static int drm_sysfb_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) | |
| { | |
| struct drm_sysfb_device *sysfb = data; | |
| const u8 *edid = sysfb->edid; | |
| size_t off = block * EDID_LENGTH; | |
| size_t end = off + len; | |
| if (!edid) | |
| return -EINVAL; | |
| if (end > EDID_LENGTH) | |
| return -EINVAL; | |
| memcpy(buf, &edid[off], len); | |
| /* | |
| * We don't have EDID extensions available and reporting them | |
| * will upset DRM helpers. Thus clear the extension field and | |
| * update the checksum. Adding the extension flag to the checksum | |
| * does this. | |
| */ | |
| buf[127] += buf[126]; | |
| buf[126] = 0; | |
| return 0; | |
| } | |
| int drm_sysfb_connector_helper_get_modes(struct drm_connector *connector) | |
| { | |
| struct drm_sysfb_device *sysfb = to_drm_sysfb_device(connector->dev); | |
| const struct drm_edid *drm_edid; | |
| if (sysfb->edid) { | |
| drm_edid = drm_edid_read_custom(connector, drm_sysfb_get_edid_block, sysfb); | |
| drm_edid_connector_update(connector, drm_edid); | |
| drm_edid_free(drm_edid); | |
| } | |
| /* Return the fixed mode even with EDID */ | |
| return drm_connector_helper_get_modes_fixed(connector, &sysfb->fb_mode); | |
| } | |
| EXPORT_SYMBOL(drm_sysfb_connector_helper_get_modes); |