@@ -2,6 +2,7 @@ ccflags-y = -Iinclude/drm
fbdevdrm-y := fbdevdrm_bo.o \
fbdevdrm_device.o \
fbdevdrm_drv.o \
+ fbdevdrm_modeset.o \
fbdevdrm_ttm.o
obj-$(CONFIG_DRM_FBDEVDRM) += fbdevdrm.o
@@ -37,10 +37,16 @@ int fbdevdrm_device_init(struct fbdevdrm_device *fdev, struct drm_driver *drv,
if (ret)
goto err_drm_dev_fini;
+ ret = fbdevdrm_modeset_init(&fdev->modeset, &fdev->dev, fb_info);
+ if (ret)
+ goto err_fbdevdrm_ttm_cleanup;
+
INIT_LIST_HEAD(&fdev->device_list);
return 0;
+err_fbdevdrm_ttm_cleanup:
+ fbdevdrm_ttm_cleanup(&fdev->ttm);
err_drm_dev_fini:
drm_dev_fini(&fdev->dev);
return ret;
@@ -55,6 +61,7 @@ void fbdevdrm_device_cleanup(struct fbdevdrm_device *fdev)
"in device list\n");
}
+ fbdevdrm_modeset_cleanup(&fdev->modeset);
fbdevdrm_ttm_cleanup(&fdev->ttm);
drm_dev_fini(dev);
@@ -16,6 +16,7 @@
#include <drm/drm_device.h>
#include <linux/kernel.h>
#include <linux/list.h>
+#include "fbdevdrm_modeset.h"
#include "fbdevdrm_ttm.h"
struct drm_driver;
@@ -26,6 +27,7 @@ struct fbdevdrm_device {
struct fb_info *fb_info;
struct fbdevdrm_ttm ttm;
+ struct fbdevdrm_modeset modeset;
struct list_head device_list; /* entry in global device list */
};
@@ -148,7 +148,9 @@ static struct drm_driver fbdevdrm_drv = {
.name = DRIVER_NAME,
.desc = DRIVER_DESCRIPTION,
.date = DRIVER_DATE,
- .driver_features = DRIVER_GEM,
+ .driver_features = DRIVER_ATOMIC |
+ DRIVER_GEM |
+ DRIVER_MODESET,
.fops = &driver_fops
};
new file mode 100644
@@ -0,0 +1,430 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * One purpose of this driver is to allow for easy conversion of framebuffer
+ * drivers to DRM. As a special exception to the GNU GPL, you are allowed to
+ * relicense this file under the terms of a license of your choice if you're
+ * porting a framebuffer driver. In order to do so, update the SPDX license
+ * identifier to the new license and remove this exception.
+ *
+ * If you add code to this file, please ensure that it's compatible with the
+ * stated exception.
+ */
+
+#include "fbdevdrm_modeset.h"
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_device.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_probe_helper.h>
+#include <linux/fb.h>
+
+/*
+ * CRTC
+ */
+
+static enum drm_mode_status crtc_helper_mode_valid(
+ struct drm_crtc *crtc, const struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static bool crtc_helper_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
+{ }
+
+static int crtc_helper_mode_set_base_atomic(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ int x, int y,
+ enum mode_set_atomic mode)
+{
+ return 0;
+}
+
+static int crtc_helper_atomic_check(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ return 0;
+}
+
+static void crtc_helper_atomic_begin(
+ struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state)
+{ }
+
+static void crtc_helper_atomic_flush(
+ struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state)
+{ }
+
+static void crtc_helper_atomic_enable(
+ struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state)
+{ }
+
+static void crtc_helper_atomic_disable(
+ struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state)
+{ }
+
+static const struct drm_crtc_helper_funcs fbdevdrm_crtc_helper_funcs = {
+ .dpms = NULL, /* legacy */
+ .prepare = NULL, /* legacy */
+ .commit = NULL, /* legacy */
+ .mode_valid = crtc_helper_mode_valid,
+ .mode_fixup = crtc_helper_mode_fixup,
+ .mode_set = NULL, /* legacy */
+ .mode_set_nofb = crtc_helper_mode_set_nofb,
+ .mode_set_base = NULL, /* legacy */
+ .mode_set_base_atomic = crtc_helper_mode_set_base_atomic,
+ .disable = NULL, /* legacy */
+ .atomic_check = crtc_helper_atomic_check,
+ .atomic_begin = crtc_helper_atomic_begin,
+ .atomic_flush = crtc_helper_atomic_flush,
+ .atomic_enable = crtc_helper_atomic_enable,
+ .atomic_disable = crtc_helper_atomic_disable,
+};
+
+static void crtc_destroy(struct drm_crtc *crtc)
+{ }
+
+static int crtc_atomic_set_property(struct drm_crtc *crtc,
+ struct drm_crtc_state *state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ return -EINVAL;
+}
+
+static int crtc_atomic_get_property(struct drm_crtc *crtc,
+ const struct drm_crtc_state *state,
+ struct drm_property *property,
+ uint64_t *val)
+{
+ return -EINVAL;
+}
+
+static int crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ return -ENODEV;
+}
+
+static void crtc_disable_vblank(struct drm_crtc *crtc)
+{ }
+
+static const struct drm_crtc_funcs fbdevdrm_crtc_funcs = {
+ .reset = drm_atomic_helper_crtc_reset,
+ .cursor_set = NULL, /* not supported by fbdev */
+ .cursor_set2 = NULL,
+ .cursor_move = NULL,
+ .gamma_set = NULL, /* not supported by fbdev */
+ .destroy = crtc_destroy,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = NULL,
+ .page_flip_target = NULL,
+ .set_property = NULL, /* unused in atomic drivers */
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .atomic_set_property = crtc_atomic_set_property,
+ .atomic_get_property = crtc_atomic_get_property,
+ .late_register = NULL,
+ .early_unregister = NULL,
+ .set_crc_source = NULL,
+ .atomic_print_state = NULL,
+ .get_vblank_counter = NULL, /* not supported by fbdev */
+ .enable_vblank = crtc_enable_vblank,
+ .disable_vblank = crtc_disable_vblank
+};
+
+/*
+ * Encoder
+ */
+
+static enum drm_mode_status encoder_helper_mode_valid(
+ struct drm_encoder *crtc, const struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static bool encoder_helper_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void encoder_helper_atomic_mode_set(
+ struct drm_encoder *encoder, struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{ }
+
+static void encoder_helper_disable(struct drm_encoder *encoder)
+{ }
+
+static void encoder_helper_enable(struct drm_encoder *encoder)
+{ }
+
+static int encoder_helper_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs fbdevdrm_encoder_helper_funcs = {
+ .dpms = NULL, /* legacy */
+ .mode_valid = encoder_helper_mode_valid,
+ .mode_fixup = encoder_helper_mode_fixup,
+ .prepare = NULL, /* legacy */
+ .commit = NULL, /* legacy */
+ .mode_set = NULL, /* legacy */
+ .atomic_mode_set = encoder_helper_atomic_mode_set,
+ .get_crtc = NULL, /* legacy */
+ .detect = NULL, /* legacy */
+ .disable = encoder_helper_disable,
+ .enable = encoder_helper_enable,
+ .atomic_check = encoder_helper_atomic_check
+};
+
+static void encoder_destroy(struct drm_encoder *encoder)
+{ }
+
+static const struct drm_encoder_funcs fbdevdrm_encoder_funcs = {
+ .reset = NULL,
+ .destroy = encoder_destroy,
+ .late_register = NULL,
+ .early_unregister = NULL,
+};
+
+/*
+ * Connector
+ */
+
+static int connector_helper_get_modes(struct drm_connector *connector)
+{
+ return 0;
+}
+
+static int connector_helper_detect_ctx(struct drm_connector *connector,
+ struct drm_modeset_acquire_ctx *ctx,
+ bool force)
+{
+ return connector_status_connected;
+}
+
+static enum drm_mode_status connector_helper_mode_valid(
+ struct drm_connector *connector, struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static int connector_helper_atomic_check(struct drm_connector *connector,
+ struct drm_connector_state *state)
+{
+ return 0;
+}
+
+static void connector_helper_atomic_commit(struct drm_connector *connector,
+ struct drm_connector_state *state)
+{ }
+
+static const struct drm_connector_helper_funcs
+ fbdevdrm_connector_helper_funcs = {
+ .get_modes = connector_helper_get_modes,
+ .detect_ctx = connector_helper_detect_ctx,
+ .mode_valid = connector_helper_mode_valid,
+ .best_encoder = NULL, /* use default */
+ .atomic_best_encoder = NULL, /* use best_encoder instead */
+ .atomic_check = connector_helper_atomic_check,
+ .atomic_commit = connector_helper_atomic_commit
+};
+
+static enum drm_connector_status connector_detect(
+ struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static void connector_force(struct drm_connector *connector)
+{ }
+
+static void connector_destroy(struct drm_connector *connector)
+{ }
+
+static int connector_atomic_set_property(struct drm_connector *connector,
+ struct drm_connector_state *state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ return -EINVAL;
+}
+
+static int connector_atomic_get_property(
+ struct drm_connector *connector,
+ const struct drm_connector_state *state, struct drm_property *property,
+ uint64_t *val)
+{
+ return -EINVAL;
+}
+
+static const struct drm_connector_funcs fbdevdrm_connector_funcs = {
+ .dpms = NULL, /* not used by atomic drivers */
+ .reset = drm_atomic_helper_connector_reset,
+ .detect = connector_detect,
+ .force = connector_force,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .set_property = NULL,
+ .late_register = NULL,
+ .early_unregister = NULL,
+ .destroy = connector_destroy,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .atomic_set_property = connector_atomic_set_property,
+ .atomic_get_property = connector_atomic_get_property,
+ .atomic_print_state = NULL
+};
+
+/*
+ * Mode config
+ */
+
+static enum drm_mode_status mode_config_mode_valid(
+ struct drm_device *dev, const struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static const struct drm_mode_config_funcs fbdevdrm_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .get_format_info = NULL,
+ /* DRM porting notes: the output_poll_changed callback is used by
+ * fb helpers to implement fbdev emulation. As fbdevdrm is built
+ * upon fbdev, this is basically the opposite. If you're porting
+ * an fbdev driver to DRM and enable fbdev emulation, this callback
+ * will become useful.
+ */
+ .output_poll_changed = drm_fb_helper_output_poll_changed,
+ .mode_valid = mode_config_mode_valid,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+ .atomic_state_alloc = NULL,
+ .atomic_state_clear = NULL,
+ .atomic_state_free = NULL
+};
+
+/*
+ * Public interface
+ */
+
+static int update_mode_config_from_fb_info(struct drm_mode_config* mode_config, struct fb_info *fb_info)
+{
+ /* DRM backporting notes: This function only exists to work around
+ * the fact that we don't know the hardware limitations. Here we
+ * test for the maximum supported resolution. If you're converting
+ * an fbdev driver to DRM, remove this function and simply fill in
+ * the actual hardware limits in the mode_config structure.
+ */
+
+ struct list_head *pos;
+ u32 xres = 0;
+ u32 yres = 0;
+ int num_modes = 0;
+
+ list_for_each(pos, &fb_info->modelist) {
+ const struct fb_modelist *modelist =
+ container_of(pos, struct fb_modelist, list);
+ if (modelist->mode.xres > xres)
+ xres = modelist->mode.xres;
+ if (modelist->mode.yres > yres)
+ yres = modelist->mode.yres;
+
+ ++num_modes;
+ }
+
+ if (!xres || !yres)
+ return -ENODEV;
+
+ mode_config->max_width = (int)xres;
+ mode_config->max_height = (int)yres;
+ mode_config->fb_base = fb_info->fix.smem_start;
+
+ /* TODO: get preferred depth from screeninfo */
+ mode_config->preferred_depth = 32;
+
+ return 0;
+}
+
+int fbdevdrm_modeset_init(struct fbdevdrm_modeset *modeset,
+ struct drm_device *dev, struct fb_info *fb_info)
+{
+ int ret;
+
+ modeset->dev = dev;
+ modeset->fb_info = fb_info;
+
+ drm_mode_config_init(dev);
+ ret = update_mode_config_from_fb_info(&dev->mode_config, fb_info);
+ if (ret)
+ goto err_drm_mode_config_cleanup;
+ dev->mode_config.funcs = &fbdevdrm_mode_config_funcs;
+
+ /* One by one, we enable all stages of the display pipeline and
+ * connect them with each other.
+ */
+
+ ret = drm_crtc_init_with_planes(dev, &modeset->crtc, NULL, NULL,
+ &fbdevdrm_crtc_funcs, NULL);
+ if (ret)
+ goto err_drm_mode_config_cleanup;
+ drm_crtc_helper_add(&modeset->crtc, &fbdevdrm_crtc_helper_funcs);
+
+ /* Use DRM_MODE_ENCODER_TYPE_DAC. It's true for many of the fbdev
+ * devices and doesn't imply hotplugging. */
+ ret = drm_encoder_init(dev, &modeset->encoder,
+ &fbdevdrm_encoder_funcs,
+ DRM_MODE_ENCODER_DAC, NULL);
+ if (ret)
+ goto err_drm_mode_config_cleanup;
+ drm_encoder_helper_add(&modeset->encoder,
+ &fbdevdrm_encoder_helper_funcs);
+
+ modeset->encoder.possible_crtcs = (1ul << modeset->crtc.index);
+
+ ret = drm_connector_init(dev, &modeset->connector,
+ &fbdevdrm_connector_funcs,
+ DRM_MODE_CONNECTOR_VGA);
+ if (ret)
+ goto err_drm_mode_config_cleanup;
+ drm_connector_helper_add(&modeset->connector,
+ &fbdevdrm_connector_helper_funcs);
+
+ ret = drm_connector_register(&modeset->connector);
+ if (ret < 0)
+ goto err_drm_mode_config_cleanup;
+
+ ret = drm_connector_attach_encoder(&modeset->connector,
+ &modeset->encoder);
+ if (ret)
+ goto err_drm_mode_config_cleanup;
+
+ /* Final step: resetting the device's mode config creates
+ * state for all objects in the mode-setting pipeline.
+ */
+ drm_mode_config_reset(dev);
+
+ return 0;
+
+err_drm_mode_config_cleanup:
+ /* Also removes all CRTCs, encoders and connectors that we added. */
+ drm_mode_config_cleanup(dev);
+ return ret;
+}
+
+void fbdevdrm_modeset_cleanup(struct fbdevdrm_modeset *modeset)
+{
+ drm_mode_config_cleanup(modeset->dev);
+}
new file mode 100644
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * One purpose of this driver is to allow for easy conversion of framebuffer
+ * drivers to DRM. As a special exception to the GNU GPL, you are allowed to
+ * relicense this file under the terms of a license of your choice if you're
+ * porting a framebuffer driver. In order to do so, update the SPDX license
+ * identifier to the new license and remove this exception.
+ *
+ * If you add code to this file, please ensure that it's compatible with the
+ * stated exception.
+ */
+
+#ifndef FBDEVDRM_MODESET_H
+#define FBDEVDRM_MODESET_H
+
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+
+struct drm_device;
+struct fb_info;
+
+struct fbdevdrm_modeset {
+ struct drm_crtc crtc;
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+
+ struct drm_device *dev;
+ struct fb_info *fb_info;
+};
+
+int fbdevdrm_modeset_init(struct fbdevdrm_modeset *modeset,
+ struct drm_device *dev, struct fb_info *fb_info);
+void fbdevdrm_modeset_cleanup(struct fbdevdrm_modeset *modeset);
+
+#endif
Modesetting for fbdevdrm supports a single display pipeline with CRTC, primary plane, encoder and connector. The fbdev device would have been an ideal candidate for using |struct drm_simple_display_pipe|. To better illustrate the conversion from fbdev to DRM drivers, fbdevdrm used the regular DRM data structures instead. Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de> --- drivers/gpu/drm/fbdevdrm/Makefile | 1 + drivers/gpu/drm/fbdevdrm/fbdevdrm_device.c | 7 + drivers/gpu/drm/fbdevdrm/fbdevdrm_device.h | 2 + drivers/gpu/drm/fbdevdrm/fbdevdrm_drv.c | 4 +- drivers/gpu/drm/fbdevdrm/fbdevdrm_modeset.c | 430 ++++++++++++++++++++ drivers/gpu/drm/fbdevdrm/fbdevdrm_modeset.h | 36 ++ 6 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/fbdevdrm/fbdevdrm_modeset.c create mode 100644 drivers/gpu/drm/fbdevdrm/fbdevdrm_modeset.h