162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2021 Microsoft
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/hyperv.h>
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <drm/drm_damage_helper.h>
962306a36Sopenharmony_ci#include <drm/drm_drv.h>
1062306a36Sopenharmony_ci#include <drm/drm_edid.h>
1162306a36Sopenharmony_ci#include <drm/drm_format_helper.h>
1262306a36Sopenharmony_ci#include <drm/drm_fourcc.h>
1362306a36Sopenharmony_ci#include <drm/drm_framebuffer.h>
1462306a36Sopenharmony_ci#include <drm/drm_gem_atomic_helper.h>
1562306a36Sopenharmony_ci#include <drm/drm_gem_framebuffer_helper.h>
1662306a36Sopenharmony_ci#include <drm/drm_gem_shmem_helper.h>
1762306a36Sopenharmony_ci#include <drm/drm_probe_helper.h>
1862306a36Sopenharmony_ci#include <drm/drm_simple_kms_helper.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "hyperv_drm.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic int hyperv_blit_to_vram_rect(struct drm_framebuffer *fb,
2362306a36Sopenharmony_ci				    const struct iosys_map *vmap,
2462306a36Sopenharmony_ci				    struct drm_rect *rect)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	struct hyperv_drm_device *hv = to_hv(fb->dev);
2762306a36Sopenharmony_ci	struct iosys_map dst = IOSYS_MAP_INIT_VADDR_IOMEM(hv->vram);
2862306a36Sopenharmony_ci	int idx;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	if (!drm_dev_enter(&hv->dev, &idx))
3162306a36Sopenharmony_ci		return -ENODEV;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	iosys_map_incr(&dst, drm_fb_clip_offset(fb->pitches[0], fb->format, rect));
3462306a36Sopenharmony_ci	drm_fb_memcpy(&dst, fb->pitches, vmap, fb, rect);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	drm_dev_exit(idx);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	return 0;
3962306a36Sopenharmony_ci}
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic int hyperv_blit_to_vram_fullscreen(struct drm_framebuffer *fb,
4262306a36Sopenharmony_ci					  const struct iosys_map *map)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct drm_rect fullscreen = {
4562306a36Sopenharmony_ci		.x1 = 0,
4662306a36Sopenharmony_ci		.x2 = fb->width,
4762306a36Sopenharmony_ci		.y1 = 0,
4862306a36Sopenharmony_ci		.y2 = fb->height,
4962306a36Sopenharmony_ci	};
5062306a36Sopenharmony_ci	return hyperv_blit_to_vram_rect(fb, map, &fullscreen);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int hyperv_connector_get_modes(struct drm_connector *connector)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct hyperv_drm_device *hv = to_hv(connector->dev);
5662306a36Sopenharmony_ci	int count;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	count = drm_add_modes_noedid(connector,
5962306a36Sopenharmony_ci				     connector->dev->mode_config.max_width,
6062306a36Sopenharmony_ci				     connector->dev->mode_config.max_height);
6162306a36Sopenharmony_ci	drm_set_preferred_mode(connector, hv->preferred_width,
6262306a36Sopenharmony_ci			       hv->preferred_height);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return count;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic const struct drm_connector_helper_funcs hyperv_connector_helper_funcs = {
6862306a36Sopenharmony_ci	.get_modes = hyperv_connector_get_modes,
6962306a36Sopenharmony_ci};
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic const struct drm_connector_funcs hyperv_connector_funcs = {
7262306a36Sopenharmony_ci	.fill_modes = drm_helper_probe_single_connector_modes,
7362306a36Sopenharmony_ci	.destroy = drm_connector_cleanup,
7462306a36Sopenharmony_ci	.reset = drm_atomic_helper_connector_reset,
7562306a36Sopenharmony_ci	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
7662306a36Sopenharmony_ci	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
7762306a36Sopenharmony_ci};
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic inline int hyperv_conn_init(struct hyperv_drm_device *hv)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	drm_connector_helper_add(&hv->connector, &hyperv_connector_helper_funcs);
8262306a36Sopenharmony_ci	return drm_connector_init(&hv->dev, &hv->connector,
8362306a36Sopenharmony_ci				  &hyperv_connector_funcs,
8462306a36Sopenharmony_ci				  DRM_MODE_CONNECTOR_VIRTUAL);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic int hyperv_check_size(struct hyperv_drm_device *hv, int w, int h,
8862306a36Sopenharmony_ci			     struct drm_framebuffer *fb)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	u32 pitch = w * (hv->screen_depth / 8);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (fb)
9362306a36Sopenharmony_ci		pitch = fb->pitches[0];
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (pitch * h > hv->fb_size)
9662306a36Sopenharmony_ci		return -EINVAL;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	return 0;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic void hyperv_pipe_enable(struct drm_simple_display_pipe *pipe,
10262306a36Sopenharmony_ci			       struct drm_crtc_state *crtc_state,
10362306a36Sopenharmony_ci			       struct drm_plane_state *plane_state)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	struct hyperv_drm_device *hv = to_hv(pipe->crtc.dev);
10662306a36Sopenharmony_ci	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	hyperv_hide_hw_ptr(hv->hdev);
10962306a36Sopenharmony_ci	hyperv_update_situation(hv->hdev, 1,  hv->screen_depth,
11062306a36Sopenharmony_ci				crtc_state->mode.hdisplay,
11162306a36Sopenharmony_ci				crtc_state->mode.vdisplay,
11262306a36Sopenharmony_ci				plane_state->fb->pitches[0]);
11362306a36Sopenharmony_ci	hyperv_blit_to_vram_fullscreen(plane_state->fb, &shadow_plane_state->data[0]);
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic int hyperv_pipe_check(struct drm_simple_display_pipe *pipe,
11762306a36Sopenharmony_ci			     struct drm_plane_state *plane_state,
11862306a36Sopenharmony_ci			     struct drm_crtc_state *crtc_state)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct hyperv_drm_device *hv = to_hv(pipe->crtc.dev);
12162306a36Sopenharmony_ci	struct drm_framebuffer *fb = plane_state->fb;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (fb->format->format != DRM_FORMAT_XRGB8888)
12462306a36Sopenharmony_ci		return -EINVAL;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (fb->pitches[0] * fb->height > hv->fb_size) {
12762306a36Sopenharmony_ci		drm_err(&hv->dev, "fb size requested by %s for %dX%d (pitch %d) greater than %ld\n",
12862306a36Sopenharmony_ci			current->comm, fb->width, fb->height, fb->pitches[0], hv->fb_size);
12962306a36Sopenharmony_ci		return -EINVAL;
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	return 0;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic void hyperv_pipe_update(struct drm_simple_display_pipe *pipe,
13662306a36Sopenharmony_ci			       struct drm_plane_state *old_state)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct hyperv_drm_device *hv = to_hv(pipe->crtc.dev);
13962306a36Sopenharmony_ci	struct drm_plane_state *state = pipe->plane.state;
14062306a36Sopenharmony_ci	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state);
14162306a36Sopenharmony_ci	struct drm_rect rect;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	if (drm_atomic_helper_damage_merged(old_state, state, &rect)) {
14462306a36Sopenharmony_ci		hyperv_blit_to_vram_rect(state->fb, &shadow_plane_state->data[0], &rect);
14562306a36Sopenharmony_ci		hyperv_update_dirt(hv->hdev, &rect);
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic const struct drm_simple_display_pipe_funcs hyperv_pipe_funcs = {
15062306a36Sopenharmony_ci	.enable	= hyperv_pipe_enable,
15162306a36Sopenharmony_ci	.check = hyperv_pipe_check,
15262306a36Sopenharmony_ci	.update	= hyperv_pipe_update,
15362306a36Sopenharmony_ci	DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS,
15462306a36Sopenharmony_ci};
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic const uint32_t hyperv_formats[] = {
15762306a36Sopenharmony_ci	DRM_FORMAT_XRGB8888,
15862306a36Sopenharmony_ci};
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic const uint64_t hyperv_modifiers[] = {
16162306a36Sopenharmony_ci	DRM_FORMAT_MOD_LINEAR,
16262306a36Sopenharmony_ci	DRM_FORMAT_MOD_INVALID
16362306a36Sopenharmony_ci};
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic inline int hyperv_pipe_init(struct hyperv_drm_device *hv)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	int ret;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	ret = drm_simple_display_pipe_init(&hv->dev,
17062306a36Sopenharmony_ci					   &hv->pipe,
17162306a36Sopenharmony_ci					   &hyperv_pipe_funcs,
17262306a36Sopenharmony_ci					   hyperv_formats,
17362306a36Sopenharmony_ci					   ARRAY_SIZE(hyperv_formats),
17462306a36Sopenharmony_ci					   hyperv_modifiers,
17562306a36Sopenharmony_ci					   &hv->connector);
17662306a36Sopenharmony_ci	if (ret)
17762306a36Sopenharmony_ci		return ret;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	drm_plane_enable_fb_damage_clips(&hv->pipe.plane);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	return 0;
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic enum drm_mode_status
18562306a36Sopenharmony_cihyperv_mode_valid(struct drm_device *dev,
18662306a36Sopenharmony_ci		  const struct drm_display_mode *mode)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	struct hyperv_drm_device *hv = to_hv(dev);
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (hyperv_check_size(hv, mode->hdisplay, mode->vdisplay, NULL))
19162306a36Sopenharmony_ci		return MODE_BAD;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	return MODE_OK;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic const struct drm_mode_config_funcs hyperv_mode_config_funcs = {
19762306a36Sopenharmony_ci	.fb_create = drm_gem_fb_create_with_dirty,
19862306a36Sopenharmony_ci	.mode_valid = hyperv_mode_valid,
19962306a36Sopenharmony_ci	.atomic_check = drm_atomic_helper_check,
20062306a36Sopenharmony_ci	.atomic_commit = drm_atomic_helper_commit,
20162306a36Sopenharmony_ci};
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ciint hyperv_mode_config_init(struct hyperv_drm_device *hv)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	struct drm_device *dev = &hv->dev;
20662306a36Sopenharmony_ci	int ret;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	ret = drmm_mode_config_init(dev);
20962306a36Sopenharmony_ci	if (ret) {
21062306a36Sopenharmony_ci		drm_err(dev, "Failed to initialized mode setting.\n");
21162306a36Sopenharmony_ci		return ret;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	dev->mode_config.min_width = 0;
21562306a36Sopenharmony_ci	dev->mode_config.min_height = 0;
21662306a36Sopenharmony_ci	dev->mode_config.max_width = hv->screen_width_max;
21762306a36Sopenharmony_ci	dev->mode_config.max_height = hv->screen_height_max;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	dev->mode_config.preferred_depth = hv->screen_depth;
22062306a36Sopenharmony_ci	dev->mode_config.prefer_shadow = 0;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	dev->mode_config.funcs = &hyperv_mode_config_funcs;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	ret = hyperv_conn_init(hv);
22562306a36Sopenharmony_ci	if (ret) {
22662306a36Sopenharmony_ci		drm_err(dev, "Failed to initialized connector.\n");
22762306a36Sopenharmony_ci		return ret;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	ret = hyperv_pipe_init(hv);
23162306a36Sopenharmony_ci	if (ret) {
23262306a36Sopenharmony_ci		drm_err(dev, "Failed to initialized pipe.\n");
23362306a36Sopenharmony_ci		return ret;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	drm_mode_config_reset(dev);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	return 0;
23962306a36Sopenharmony_ci}
240