Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 31f1535f authored by Noralf Trønnes's avatar Noralf Trønnes Committed by Gerrit - the friendly Code Review server
Browse files

drm/client: Hack: Add bootsplash example



An example to showcase the client API.

TODO:
- A bootsplash client needs a way to tell drm_fb_helper to stay away,
  otherwise it will chime in on setup and hotplug.
  Most DRM drivers register fbdev before calling drm_dev_register() (the
  generic emulation is an exception). This have to be reversed for
  bootsplash to fend off fbdev.
- Probably need some way to determine which is the primary display/device
  on multi DRM device systems.
- Maybe do handover from early/simple DRM driver

Signed-off-by: default avatarNoralf Trønnes <noralf@tronnes.org>
Patch-mainline: dri-devel Archive on lore.kernel.org @ 23/05/19, 15:44:13
Link: https://patchwork.kernel.org/project/dri-devel/patch/20190523134413.4210-9-noralf@tronnes.org/


[stalek@codeaurora.org: write a paragraph that describes the config symbol fully].

Change-Id: Iba4f0d19dc40885e5cd4dcaac02e3e5ebed8b8a9
Signed-off-by: default avatarNarender Ankam <nankam@codeaurora.org>
Signed-off-by: default avatarShubham Talekar <stalek@codeaurora.org>
parent a00ab1bb
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -35,6 +35,13 @@ config DRM_DP_AUX_CHARDEV
	  read and write values to arbitrary DPCD registers on the DP aux
	  channel.

config DRM_CLIENT_BOOTSPLASH
	bool "DRM Bootsplash"
	help
	  Choose this option to enable DRM bootsplash. This option needs to be
	  selected only if UEFI bootsplash is disabled. Choosing this option
	  will render splash logo in display panel during boot up.

config DRM_KMS_HELPER
	tristate
	depends on DRM
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ drm-$(CONFIG_PCI) += ati_pcigart.o
drm-$(CONFIG_DRM_PANEL) += drm_panel.o
drm-$(CONFIG_OF) += drm_of.o
drm-$(CONFIG_AGP) += drm_agpsupport.o
drm-$(CONFIG_DRM_CLIENT_BOOTSPLASH) += drm_bootsplash.o

drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_probe_helper.o \
		drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
+348 −0
Original line number Diff line number Diff line
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/notifier.h>
#include <linux/keyboard.h>
#include <linux/completion.h>

#include <drm/drmP.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_modes.h>
#include <drm/drm_client.h>

#include "drm_internal.h"
#include "drm_splash.h"

static bool drm_bootsplash_enabled = true;
module_param_named(bootsplash_enabled, drm_bootsplash_enabled, bool, 0664);

static void drm_bootsplash_client_unregister(struct drm_client_dev *client);
static int drm_bootsplash_client_hotplug(struct drm_client_dev *client);

struct drm_bootsplash {
	struct drm_client_dev client;
	struct mutex lock;
	struct drm_client_display *display;
	struct drm_client_buffer *buffer[2];
	struct work_struct worker;
	struct completion xref;
	bool started;
	bool stop;
};

static void drm_bootsplash_buffer_delete(struct drm_bootsplash *splash)
{
	unsigned int i;

	for (i = 0; i < 2; i++) {
		if (!IS_ERR_OR_NULL(splash->buffer[i]))
			drm_client_framebuffer_delete(splash->buffer[i]);
		splash->buffer[i] = NULL;
	}
}

static int drm_bootsplash_buffer_create(
			struct drm_bootsplash *splash, u32 width, u32 height)
{
	unsigned int i;

	for (i = 0; i < 2; i++) {
		splash->buffer[i] =
			drm_client_framebuffer_create(&splash->client,
					width, height, SPLASH_IMAGE_FORMAT);
		if (IS_ERR(splash->buffer[i])) {
			drm_bootsplash_buffer_delete(splash);
			return PTR_ERR(splash->buffer[i]);
		}

		splash->buffer[i]->vaddr =
			drm_client_buffer_vmap(splash->buffer[i]);
		if (!(splash->buffer[i]->vaddr))
			DRM_ERROR("drm_client_buffer_vmap fail\n");

	}

	return 0;
}

static int drm_bootsplash_display_probe(struct drm_bootsplash *splash)
{
	struct drm_client_dev *client = &splash->client;
	unsigned int width = 0, height = 0;
	unsigned int num_non_tiled = 0, i;
	unsigned int modeset_mask = 0;
	struct drm_mode_set *modeset;
	bool tiled = false;
	int ret;

	ret = drm_client_modeset_probe(client, 0, 0);
	if (ret)
		return ret;

	mutex_lock(&client->modeset_mutex);

	drm_client_for_each_modeset(modeset, client) {
		if (!modeset->mode)
			continue;

		if (modeset->connectors[0]->has_tile)
			tiled = true;
		else
			num_non_tiled++;
	}

	if (!tiled && !num_non_tiled) {
		drm_bootsplash_buffer_delete(splash);
		ret = -ENOENT;
		goto out;
	}

	/* Assume only one tiled monitor is possible */
	if (tiled) {
		int hdisplay = 0, vdisplay = 0;

		i = 0;
		drm_client_for_each_modeset(modeset, client) {
			i++;
			if (!modeset->connectors[0]->has_tile)
				continue;

			if (!modeset->y)
				hdisplay += modeset->mode->hdisplay;
			if (!modeset->x)
				vdisplay += modeset->mode->vdisplay;
			modeset_mask |= BIT(i - 1);
		}

		width = hdisplay;
		height = vdisplay;

		goto trim;
	}

	/* The rest have one display per modeset, pick the largest */
	i = 0;
	drm_client_for_each_modeset(modeset, client) {
		i++;
		if (!modeset->mode || modeset->connectors[0]->has_tile)
			continue;

		if (modeset->mode->hdisplay *
				modeset->mode->vdisplay > width * height) {
			width = modeset->mode->hdisplay;
			height = modeset->mode->vdisplay;
			modeset_mask = BIT(i - 1);
		}
	}

trim:
	i = 0;
	drm_client_for_each_modeset(modeset, client) {
		unsigned int j;

		if (modeset_mask & BIT(i++))
			continue;
		drm_mode_destroy(client->dev, modeset->mode);
		modeset->mode = NULL;

		for (j = 0; j < modeset->num_connectors; j++) {
			drm_connector_unreference(modeset->connectors[j]);
			modeset->connectors[j] = NULL;
		}
		modeset->num_connectors = 0;
	}

	if (!splash->buffer[0] ||
	    splash->buffer[0]->fb->width != width ||
	    splash->buffer[0]->fb->height != height) {
		drm_bootsplash_buffer_delete(splash);
		ret = drm_bootsplash_buffer_create(splash, width, height);
	}

out:
	mutex_unlock(&client->modeset_mutex);

	return ret;
}

static int drm_bootsplash_display_commit_buffer(
			struct drm_bootsplash *splash, unsigned int num)
{
	struct drm_client_dev *client = &splash->client;
	struct drm_mode_set *modeset;

	mutex_lock(&client->modeset_mutex);
	drm_client_for_each_modeset(modeset, client) {
		if (modeset->mode)
			modeset->fb = splash->buffer[num]->fb;
	}
	mutex_unlock(&client->modeset_mutex);

	return drm_client_modeset_commit(client);
}

/* Draw a box for copying the image */
static void drm_bootsplash_draw_box(struct drm_client_buffer *buffer)
{
	unsigned int width = buffer->fb->width;
	unsigned int height = buffer->fb->height;
	unsigned int x, y, z;
	u32 *pix;

	pix = buffer->vaddr;
	pix += ((height / 2) - 50) * width;
	pix += (width / 2) - 50;

	z = 0;
	for (y = 0; y < SPLASH_IMAGE_HEIGHT; y++) {
		for (x = 0; x < SPLASH_IMAGE_WIDTH; x++)
			*pix++ = splash_bgr888_image[z++];
		pix += width - SPLASH_IMAGE_WIDTH;
	}
}

static int drm_bootsplash_draw(struct drm_bootsplash *splash,
							unsigned int buffer_num)
{
	if (!splash->buffer[buffer_num])
		return -ENOENT;

	drm_bootsplash_draw_box(splash->buffer[buffer_num]);

	return drm_bootsplash_display_commit_buffer(splash, buffer_num);
}

static void drm_bootsplash_worker(struct work_struct *work)
{
	struct drm_bootsplash *splash =
		container_of(work, struct drm_bootsplash, worker);
	struct drm_client_dev *client = &splash->client;
	struct drm_device *dev = client->dev;
	unsigned int buffer_num = 0;
	bool stop = false;
	int ret = 0, times = 0;

	while (!splash->stop) {
		mutex_lock(&splash->lock);

		stop = splash->stop;

		buffer_num = !buffer_num;

		ret = drm_bootsplash_draw(splash, buffer_num);

		mutex_unlock(&splash->lock);

		if (stop || ret == -ENOENT || ret == -EBUSY)
			break;

		if (times == 10)
			splash->stop = true;
		else
			times++;

		msleep(500);
	}

	if ((times == 10) && splash->stop)
		drm_lastclose(dev);

	drm_bootsplash_buffer_delete(splash);

	DRM_DEBUG("Bootsplash has stopped (start=%u, stop=%u, ret=%d).\n",
			splash->started, splash->stop, ret);

	complete(&splash->xref);
}

static int drm_bootsplash_client_hotplug(struct drm_client_dev *client)
{
	struct drm_bootsplash *splash =
		container_of(client, struct drm_bootsplash, client);
	int ret = 0, retval;

	if (splash->stop)
		goto out_unlock;

	ret = drm_bootsplash_display_probe(splash);
	if (ret < 0) {
		if (splash->started && ret == -ENOENT)
			splash->stop = true;
		goto out_unlock;
	}

	if (!splash->started) {
		splash->started = true;
		reinit_completion(&splash->xref);
		schedule_work(&splash->worker);
		retval = wait_for_completion_interruptible(&splash->xref);
		if (retval < 0)
			DRM_ERROR("wait for bootsplash worker failed\n");
	}

out_unlock:
	return ret;
}

static const struct drm_client_funcs drm_bootsplash_client_funcs = {
	.owner          = THIS_MODULE,
	.unregister     = drm_bootsplash_client_unregister,
	.hotplug        = drm_bootsplash_client_hotplug,
};

static void drm_bootsplash_client_unregister(struct drm_client_dev *client)
{
	struct drm_bootsplash *splash =
		container_of(client, struct drm_bootsplash, client);

	mutex_lock(&splash->lock);
	splash->stop = true;
	mutex_unlock(&splash->lock);

	flush_work(&splash->worker);

	drm_client_release(client);
	kfree(splash);
}

void drm_bootsplash_client_register(struct drm_device *dev)
{
	struct drm_bootsplash *splash;
	int ret;

	if (!drm_bootsplash_enabled)
		return;

	splash = kzalloc(sizeof(*splash), GFP_KERNEL);
	if (!splash)
		return;

	ret = drm_client_init(dev, &splash->client, "bootsplash",
			&drm_bootsplash_client_funcs);
	if (ret) {
		DRM_DEV_ERROR(dev->dev, "Fail to create client, ret=%d\n", ret);
		kfree(splash);
		return;
	}

	/* For this simple example only allow the first */
	drm_bootsplash_enabled = false;

	mutex_init(&splash->lock);

	INIT_WORK(&splash->worker, drm_bootsplash_worker);
	init_completion(&splash->xref);

	ret = drm_bootsplash_client_hotplug(&splash->client);
	if (ret)
		DRM_DEV_ERROR(dev->dev, "client hotplug ret=%d\n", ret);

	drm_client_register(&splash->client);
}

MODULE_DESCRIPTION("bootsplash");
+7 −0
Original line number Diff line number Diff line
@@ -156,6 +156,13 @@ void drm_client_release(struct drm_client_dev *client)
}
EXPORT_SYMBOL(drm_client_release);

void drm_client_dev_register(struct drm_device *dev)
{
#ifdef CONFIG_DRM_CLIENT_BOOTSPLASH
	drm_bootsplash_client_register(dev);
#endif
}

void drm_client_dev_unregister(struct drm_device *dev)
{
	struct drm_client_dev *client, *tmp;
+5 −0
Original line number Diff line number Diff line
@@ -106,6 +106,7 @@ int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
void drm_client_register(struct drm_client_dev *client);
void drm_client_release(struct drm_client_dev *client);

void drm_client_dev_register(struct drm_device *dev);
void drm_client_dev_unregister(struct drm_device *dev);
void drm_client_dev_hotplug(struct drm_device *dev);
void drm_client_dev_restore(struct drm_device *dev);
@@ -158,6 +159,10 @@ int drm_client_modeset_commit_force(struct drm_client_dev *client);
int drm_client_modeset_commit(struct drm_client_dev *client);
int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);

#ifdef CONFIG_DRM_CLIENT_BOOTSPLASH
void drm_bootsplash_client_register(struct drm_device *dev);
#endif

/**
 * drm_client_for_each_modeset() - Iterate over client modesets
 * @modeset: &drm_mode_set loop cursor