From: Leander Kieweg <kieweg.leander@gmail.com>
To: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org
Cc: airlied@gmail.com, simona@ffwll.ch,
maarten.lankhorst@linux.intel.com, mripard@kernel.org,
tzimmermann@suse.de, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, Leander Kieweg <kieweg.leander@gmail.com>
Subject: [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU
Date: Tue, 14 Jul 2026 12:11:44 +0200 [thread overview]
Message-ID: <20260714101146.200416-3-kieweg.leander@gmail.com> (raw)
In-Reply-To: <20260714101146.200416-1-kieweg.leander@gmail.com>
Introduce the core DRM/KMS driver for GlandaGPU. This driver
supports basic modesetting, atomic updates, and custom 2D hardware
acceleration IOCTLs.
Signed-off-by: Leander Kieweg <kieweg.leander@gmail.com>
---
MAINTAINERS | 7 +
drivers/gpu/drm/tiny/Kconfig | 11 +
drivers/gpu/drm/tiny/Makefile | 1 +
drivers/gpu/drm/tiny/glandagpu.c | 769 +++++++++++++++++++++++++++++++
include/uapi/drm/glanda_drm.h | 40 ++
5 files changed, 828 insertions(+)
create mode 100644 drivers/gpu/drm/tiny/glandagpu.c
create mode 100644 include/uapi/drm/glanda_drm.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 6dea93a41..49bbb5c43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8076,6 +8076,13 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
F: drivers/gpu/drm/gud/
F: include/drm/gud.h
+DRM DRIVER FOR GLANDAGPU
+M: Leander Kieweg <kieweg.leander@gmail.com>
+S: Maintained
+F: Documentation/devicetree/bindings/display/glanda,gpu.yaml
+F: drivers/gpu/drm/tiny/glandagpu.c
+F: include/uapi/drm/glanda_drm.h
+
DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
M: Hans de Goede <hansg@kernel.org>
S: Maintained
diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
index f0e72d4b6..7a15bf95a 100644
--- a/drivers/gpu/drm/tiny/Kconfig
+++ b/drivers/gpu/drm/tiny/Kconfig
@@ -56,6 +56,17 @@ config DRM_CIRRUS_QEMU
- qxl (DRM_QXL, qemu -vga qxl, works best with spice)
- virtio (DRM_VIRTIO_GPU), qemu -vga virtio)
+config DRM_GLANDA
+ tristate "GlandaGPU DRM driver"
+ depends on DRM
+ select DRM_KMS_HELPER
+ select DRM_GEM_SHMEM_HELPER
+ help
+ DRM/KMS driver for the GlandaGPU hardware-accelerated 2D
+ display controller (FPGA soft IP). This driver supports
+ basic modesetting, dumb buffers, and simple 2D drawing
+ acceleration via custom hardware IOCTLs.
+
config DRM_GM12U320
tristate "GM12U320 driver for USB projectors"
depends on DRM && USB && MMU
diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile
index 48d30bf61..b4fa1554a 100644
--- a/drivers/gpu/drm/tiny/Makefile
+++ b/drivers/gpu/drm/tiny/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_APPLETBDRM) += appletbdrm.o
obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o
obj-$(CONFIG_DRM_BOCHS) += bochs.o
obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus-qemu.o
+obj-$(CONFIG_DRM_GLANDA) += glandagpu.o
obj-$(CONFIG_DRM_GM12U320) += gm12u320.o
obj-$(CONFIG_DRM_PANEL_MIPI_DBI) += panel-mipi-dbi.o
obj-$(CONFIG_DRM_PIXPAPER) += pixpaper.o
diff --git a/drivers/gpu/drm/tiny/glandagpu.c b/drivers/gpu/drm/tiny/glandagpu.c
new file mode 100644
index 000000000..8f87ae096
--- /dev/null
+++ b/drivers/gpu/drm/tiny/glandagpu.c
@@ -0,0 +1,769 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/delay.h> /* udelay (polling) */
+#include <linux/mod_devicetable.h> /* Device Tree parsing */
+#include <linux/of.h>
+#include <linux/slab.h> /* GFP_KERNEL */
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/iosys-map.h>
+
+#include <drm/drm_drv.h>
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_vblank.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_print.h>
+
+#include <uapi/drm/glanda_drm.h>
+
+/* Hardware Constants */
+#define GLANDA_WIDTH 640
+#define GLANDA_HEIGHT 480
+#define GLANDA_VRAM_SIZE (GLANDA_WIDTH * GLANDA_HEIGHT * 4)
+#define GLANDA_MMIO_SIZE 32
+#define GLANDA_MMIO_OFFSET 0x00200000
+
+/* Base addresses used by the x86 test device. */
+#define BRIDGE_BASE 0xC0000000
+#define GLANDA_VRAM_BASE (BRIDGE_BASE + 0x00000000)
+#define GLANDA_MMIO_BASE (BRIDGE_BASE + GLANDA_MMIO_OFFSET)
+#define GLANDA_BASE_SIZE (BRIDGE_BASE + 0x01000000 - 1)
+
+/* Register Offsets */
+#define REG_STATUS 0x00
+#define REG_CTRL 0x04
+#define REG_COORD0 0x08
+#define REG_COORD1 0x0C
+#define REG_COLOR 0x10
+#define REG_ISR 0x14
+#define REG_IER 0x18
+
+/* Bit Masks */
+#define INT_DONE BIT(0)
+#define INT_VSYNC BIT(1)
+
+#define STATUS_BUSY BIT(0)
+#define CMD_CLEAR (0x1)
+#define CMD_RECT (0x2)
+#define CMD_LINE (0x3)
+#define CTRL_START BIT(4)
+
+struct glanda_device {
+ struct drm_device drm;
+
+ /* hw */
+ void __iomem *mmio_base;
+ void __iomem *vram_base;
+ struct device *dev;
+ phys_addr_t vram_phys;
+
+ int irq;
+ wait_queue_head_t cmd_wq;
+ bool cmd_done;
+
+ struct mutex lock; /* for every ineration with the hardware */
+
+ /* drm */
+ struct drm_plane primary_plane;
+ struct drm_crtc crtc;
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+};
+
+#define to_glanda(dev) container_of(dev, struct glanda_device, drm)
+
+static const u32 glanda_plane_formats[] = {
+ DRM_FORMAT_XRGB8888,
+};
+
+static int glanda_wait_idle(struct glanda_device *gdev)
+{
+ int ret;
+ unsigned int status;
+
+ status = readl(gdev->mmio_base + REG_STATUS);
+ if (!(status & STATUS_BUSY))
+ return 0;
+
+ if (gdev->irq < 0) {
+ int timeout = 10000;
+
+ do {
+ status = readl(gdev->mmio_base + REG_STATUS);
+ if (!(status & STATUS_BUSY))
+ return 0;
+ udelay(1);
+ } while (--timeout > 0);
+
+ dev_err(gdev->dev, "GlandaGPU: polling wait_idle timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ gdev->cmd_done = false;
+
+ ret = wait_event_interruptible_timeout(gdev->cmd_wq, gdev->cmd_done ||
+ !(readl(gdev->mmio_base + REG_STATUS) & STATUS_BUSY),
+ msecs_to_jiffies(500));
+
+ if (ret == 0) {
+ dev_err(gdev->dev, "GlandaGPU: IRQ wait_idle timeout\n");
+ return -ETIMEDOUT;
+ } else if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static int glanda_hw_clear(struct glanda_device *gdev, int color)
+{
+ u32 ctrl;
+ int ret;
+
+ if (mutex_lock_interruptible(&gdev->lock))
+ return -ERESTARTSYS;
+
+ ret = glanda_wait_idle(gdev);
+ if (ret) {
+ mutex_unlock(&gdev->lock);
+ return ret;
+ }
+
+ writel(color, gdev->mmio_base + REG_COLOR);
+ ctrl = CTRL_START | CMD_CLEAR;
+ writel(ctrl, gdev->mmio_base + REG_CTRL);
+
+ mutex_unlock(&gdev->lock);
+ return 0;
+}
+
+static int glanda_hw_draw_rect(struct glanda_device *gdev,
+ int x, int y, int w, int h, int color)
+{
+ u32 coord0, coord1, ctrl;
+ int ret;
+
+ if (mutex_lock_interruptible(&gdev->lock))
+ return -ERESTARTSYS;
+
+ ret = glanda_wait_idle(gdev);
+ if (ret) {
+ mutex_unlock(&gdev->lock);
+ return ret;
+ }
+
+ coord0 = (y << 16) | (x & 0x3FF);
+ coord1 = (h << 16) | (w & 0x3FF);
+
+ writel(coord0, gdev->mmio_base + REG_COORD0);
+ writel(coord1, gdev->mmio_base + REG_COORD1);
+ writel(color, gdev->mmio_base + REG_COLOR);
+
+ ctrl = CTRL_START | CMD_RECT;
+ writel(ctrl, gdev->mmio_base + REG_CTRL);
+
+ mutex_unlock(&gdev->lock);
+ return 0;
+}
+
+static int glanda_hw_draw_line(struct glanda_device *gdev,
+ int x1, int y1, int x2, int y2, int color)
+{
+ u32 coord0, coord1, ctrl;
+ int ret;
+
+ if (mutex_lock_interruptible(&gdev->lock))
+ return -ERESTARTSYS;
+
+ ret = glanda_wait_idle(gdev);
+ if (ret) {
+ mutex_unlock(&gdev->lock);
+ return ret;
+ }
+
+ coord0 = (y1 << 16) | (x1 & 0x3FF);
+ coord1 = (y2 << 16) | (x2 & 0x3FF);
+
+ writel(coord0, gdev->mmio_base + REG_COORD0);
+ writel(coord1, gdev->mmio_base + REG_COORD1);
+ writel(color, gdev->mmio_base + REG_COLOR);
+
+ ctrl = CTRL_START | CMD_LINE;
+ writel(ctrl, gdev->mmio_base + REG_CTRL);
+
+ mutex_unlock(&gdev->lock);
+ return 0;
+}
+
+static int glanda_drm_ioctl_clear(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct glanda_device *gdev = to_glanda(dev);
+ struct glanda_clear_cmd *cmd = data;
+
+ return glanda_hw_clear(gdev, cmd->color);
+}
+
+static bool glanda_rect_cmd_is_valid(const struct glanda_draw_rect_cmd *cmd)
+{
+ if (cmd->x >= GLANDA_WIDTH || cmd->y >= GLANDA_HEIGHT)
+ return false;
+ if (cmd->w > GLANDA_WIDTH || cmd->h > GLANDA_HEIGHT)
+ return false;
+ if (cmd->x + cmd->w > GLANDA_WIDTH)
+ return false;
+ if (cmd->y + cmd->h > GLANDA_HEIGHT)
+ return false;
+
+ return true;
+}
+
+static int glanda_drm_ioctl_draw_rect(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct glanda_device *gdev = to_glanda(dev);
+ struct glanda_draw_rect_cmd *cmd = data;
+
+ if (!glanda_rect_cmd_is_valid(cmd))
+ return -EINVAL;
+
+ return glanda_hw_draw_rect(gdev, cmd->x, cmd->y, cmd->w, cmd->h,
+ cmd->color);
+}
+
+static bool glanda_line_cmd_is_valid(const struct glanda_draw_line_cmd *cmd)
+{
+ if (cmd->x0 >= GLANDA_WIDTH || cmd->y0 >= GLANDA_HEIGHT)
+ return false;
+ if (cmd->x1 >= GLANDA_WIDTH || cmd->y1 >= GLANDA_HEIGHT)
+ return false;
+
+ return true;
+}
+
+static int glanda_drm_ioctl_draw_line(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct glanda_device *gdev = to_glanda(dev);
+ struct glanda_draw_line_cmd *cmd = data;
+
+ if (!glanda_line_cmd_is_valid(cmd))
+ return -EINVAL;
+
+ return glanda_hw_draw_line(gdev, cmd->x0, cmd->y0, cmd->x1, cmd->y1,
+ cmd->color);
+}
+
+static void glanda_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
+ struct drm_framebuffer *fb = new_state->fb;
+ struct glanda_device *gdev = to_glanda(plane->dev);
+ struct drm_gem_shmem_object *shmem;
+ struct iosys_map map;
+ u32 src_pitch;
+ u32 width;
+ u32 height;
+ int ret;
+
+ if (!fb)
+ return;
+
+ shmem = to_drm_gem_shmem_obj(fb->obj[0]);
+ if (!shmem) {
+ drm_err(&gdev->drm, "GlandaGPU: framebuffer is not a shmem GEM object\n");
+ return;
+ }
+
+ dma_resv_lock(shmem->base.resv, NULL);
+ ret = drm_gem_shmem_vmap_locked(shmem, &map);
+ if (ret) {
+ dma_resv_unlock(shmem->base.resv);
+ drm_err(&gdev->drm,
+ "GlandaGPU: failed to vmap GEM shmem object\n");
+ return;
+ }
+
+ mutex_lock(&gdev->lock);
+
+ ret = glanda_wait_idle(gdev);
+ if (ret) {
+ drm_err(&gdev->drm, "GlandaGPU: timed out waiting for idle\n");
+ mutex_unlock(&gdev->lock);
+ drm_gem_shmem_vunmap_locked(shmem, &map);
+ dma_resv_unlock(shmem->base.resv);
+ return;
+ }
+
+ src_pitch = fb->pitches[0];
+ width = min_t(u32, fb->width, GLANDA_WIDTH);
+ height = min_t(u32, fb->height, GLANDA_HEIGHT);
+
+ u8 __iomem *dst_base = gdev->vram_base;
+ u8 *src_base = map.vaddr;
+ u32 y;
+
+ for (y = 0; y < height; y++) {
+ u32 *src = (u32 *)(src_base + y * src_pitch);
+ u32 __iomem *dst = (u32 __iomem *)(dst_base + y * GLANDA_WIDTH * sizeof(u32));
+ u32 x;
+
+ for (x = 0; x < width; x++) {
+ u32 pixel = src[x];
+ u32 packed = ((pixel >> 12) & 0x0F00) |
+ ((pixel >> 8) & 0x00F0) |
+ ((pixel >> 4) & 0x000F);
+
+ writel_relaxed(packed, &dst[x]);
+ }
+ }
+
+ mutex_unlock(&gdev->lock);
+ drm_gem_shmem_vunmap_locked(shmem, &map);
+ dma_resv_unlock(shmem->base.resv);
+}
+
+static int glanda_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
+ struct drm_crtc_state *crtc_state;
+
+ if (!new_plane_state->crtc)
+ return 0;
+
+ crtc_state = drm_atomic_get_new_crtc_state(state, new_plane_state->crtc);
+
+ return drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
+ DRM_PLANE_NO_SCALING, DRM_PLANE_NO_SCALING,
+ false, /* can_position */
+ false /* can_update_disabled */);
+}
+
+static const struct drm_plane_helper_funcs glanda_plane_helper_funcs = {
+ .atomic_update = glanda_plane_atomic_update,
+ .atomic_check = glanda_plane_atomic_check,
+};
+
+static const struct drm_plane_funcs glanda_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = drm_plane_cleanup,
+ .reset = drm_atomic_helper_plane_reset,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static int glanda_connector_get_modes(struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ dev_err(connector->dev->dev, "GlandaGPU: failed to create display mode\n");
+ return 0;
+ }
+
+ /* Standard VGA timing: 640x480 @ 60 Hz. */
+ mode->hdisplay = 640;
+ mode->hsync_start = 656;
+ mode->hsync_end = 752;
+ mode->htotal = 800;
+
+ mode->vdisplay = 480;
+ mode->vsync_start = 490;
+ mode->vsync_end = 492;
+ mode->vtotal = 525;
+
+ mode->clock = 25175; /* 25.175 MHz pixel clock */
+
+ mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static enum drm_connector_status glanda_connector_detect(struct drm_connector
+ *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static int glanda_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct glanda_device *gdev = to_glanda(crtc->dev);
+ u32 ier;
+
+ ier = readl(gdev->mmio_base + REG_IER);
+ writel(ier | INT_VSYNC, gdev->mmio_base + REG_IER);
+
+ return 0;
+}
+
+static void glanda_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct glanda_device *gdev = to_glanda(crtc->dev);
+ u32 ier = readl(gdev->mmio_base + REG_IER);
+
+ writel(ier & ~INT_VSYNC, gdev->mmio_base + REG_IER);
+}
+
+static void glanda_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ drm_crtc_vblank_on(crtc);
+}
+
+static void glanda_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ drm_crtc_vblank_off(crtc);
+}
+
+static void glanda_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state, crtc);
+ struct drm_pending_vblank_event *event;
+
+ if (new_state && new_state->event) {
+ event = new_state->event;
+
+ new_state->event = NULL;
+
+ spin_lock_irq(&crtc->dev->event_lock);
+
+ if (drm_crtc_vblank_get(crtc) == 0)
+ drm_crtc_arm_vblank_event(crtc, event);
+ else
+ drm_crtc_send_vblank_event(crtc, event);
+
+ spin_unlock_irq(&crtc->dev->event_lock);
+ }
+}
+
+static const struct drm_crtc_funcs glanda_crtc_funcs = {
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = drm_atomic_helper_crtc_reset,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .enable_vblank = glanda_crtc_enable_vblank,
+ .disable_vblank = glanda_crtc_disable_vblank,
+};
+
+static const struct drm_crtc_helper_funcs glanda_crtc_helper_funcs = {
+ .atomic_enable = glanda_crtc_atomic_enable,
+ .atomic_disable = glanda_crtc_atomic_disable,
+ .atomic_flush = glanda_crtc_atomic_flush,
+};
+
+static const struct drm_connector_helper_funcs glanda_connector_helper_funcs = {
+ .get_modes = glanda_connector_get_modes,
+};
+
+static const struct drm_connector_funcs glanda_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .detect = glanda_connector_detect,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_framebuffer_funcs glanda_fb_funcs = {
+ .destroy = drm_gem_fb_destroy,
+ .create_handle = drm_gem_fb_create_handle,
+ .dirty = drm_atomic_helper_dirtyfb,
+};
+
+static struct drm_framebuffer *glanda_fb_create(struct drm_device *dev,
+ struct drm_file *file,
+ const struct drm_format_info *info,
+ const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ return drm_gem_fb_create_with_funcs(dev, file, info, mode_cmd, &glanda_fb_funcs);
+}
+
+static const struct drm_mode_config_funcs glanda_mode_config_funcs = {
+ .fb_create = glanda_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+/*
+ * RFC NOTE: These three fixed-function ioctls (clear/rect/line) are a
+ * minimal placeholder UAPI to demonstrate the hardware's 2D drawing
+ * capability end-to-end. Given plans to add polygon/3D rendering support
+ * in the future, feedback is explicitly requested on whether a generic
+ * command-buffer submission ioctl (similar to virtio_gpu) would
+ * be a better long-term UAPI direction before this is treated as stable.
+ */
+static const struct drm_ioctl_desc glanda_ioctls[] = {
+ DRM_IOCTL_DEF_DRV(GLANDA_CLEAR, glanda_drm_ioctl_clear,
+ DRM_AUTH | DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(GLANDA_DRAW_RECT, glanda_drm_ioctl_draw_rect,
+ DRM_AUTH | DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(GLANDA_DRAW_LINE, glanda_drm_ioctl_draw_line,
+ DRM_AUTH | DRM_RENDER_ALLOW),
+};
+
+DEFINE_DRM_GEM_FOPS(glanda_drm_fops);
+
+static const struct drm_driver glanda_drm_driver = {
+ .driver_features =
+ DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_RENDER,
+ .name = "glandagpu",
+ .desc = "GlandaGPU Hardware Accelerated DRM Driver",
+ .major = 1,
+ .minor = 0,
+ .fops = &glanda_drm_fops,
+ .dumb_create = drm_gem_shmem_dumb_create,
+ .ioctls = glanda_ioctls,
+ .num_ioctls = ARRAY_SIZE(glanda_ioctls),
+};
+
+static irqreturn_t glanda_irq_handler(int irq, void *dev_id)
+{
+ struct glanda_device *gdev = dev_id;
+
+ if (!gdev || !gdev->mmio_base)
+ return IRQ_NONE;
+
+ u32 isr = readl(gdev->mmio_base + REG_ISR);
+
+ if (!isr)
+ return IRQ_NONE;
+
+ if (isr & INT_DONE) {
+ gdev->cmd_done = true;
+ wake_up_interruptible(&gdev->cmd_wq);
+ }
+
+ if (isr & INT_VSYNC)
+ drm_crtc_handle_vblank(&gdev->crtc);
+
+ /* Clear interrupt(W1C) */
+ writel(isr, gdev->mmio_base + REG_ISR);
+ return IRQ_HANDLED;
+}
+
+static int glandagpu_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct glanda_device *gdev;
+ int ret;
+
+ dev_info(&pdev->dev, "GlandaGPU Probe started\n");
+
+ gdev = devm_drm_dev_alloc(&pdev->dev, &glanda_drm_driver, struct glanda_device, drm);
+ if (IS_ERR(gdev))
+ return PTR_ERR(gdev);
+
+ gdev->dev = &pdev->dev;
+ platform_set_drvdata(pdev, gdev);
+
+ mutex_init(&gdev->lock);
+ /* Interrupt setup */
+ init_waitqueue_head(&gdev->cmd_wq);
+ gdev->irq = -1;
+ /* Map VRAM */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ gdev->vram_phys = res->start;
+ gdev->vram_base = devm_ioremap(&pdev->dev, res->start, GLANDA_VRAM_SIZE);
+ gdev->mmio_base = devm_ioremap(&pdev->dev, res->start + GLANDA_MMIO_OFFSET,
+ GLANDA_MMIO_SIZE);
+
+ if (!gdev->vram_base || !gdev->mmio_base) {
+ drm_err(&gdev->drm, "failed to ioremap\n");
+ return -ENOMEM;
+ }
+
+ writel(0, gdev->mmio_base + REG_IER);
+ writel(0xFFFFFFFF, gdev->mmio_base + REG_ISR); /* clear flags */
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret > 0) {
+ gdev->irq = ret;
+ ret = devm_request_irq(&pdev->dev, gdev->irq, glanda_irq_handler,
+ IRQF_SHARED, "glandagpu", gdev);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to request IRQ %d\n",
+ gdev->irq);
+ return ret;
+ }
+
+ writel(INT_DONE, gdev->mmio_base + REG_IER);
+ drm_info(&gdev->drm, "IRQ %d requested and enabled\n", gdev->irq);
+ } else {
+ drm_warn(&gdev->drm, "No IRQ found, falling back to polling\n");
+ }
+
+ /* DRM mode config */
+ drm_mode_config_init(&gdev->drm);
+ gdev->drm.mode_config.min_width = 640;
+ gdev->drm.mode_config.min_height = 480;
+ gdev->drm.mode_config.max_width = 640;
+ gdev->drm.mode_config.max_height = 480;
+ gdev->drm.mode_config.funcs = &glanda_mode_config_funcs;
+
+ ret = drm_universal_plane_init(&gdev->drm, &gdev->primary_plane, 1 << 0,
+ &glanda_plane_funcs,
+ glanda_plane_formats,
+ ARRAY_SIZE(glanda_plane_formats), NULL,
+ DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize primary plane\n");
+ goto err_mode_cleanup;
+ }
+ drm_plane_helper_add(&gdev->primary_plane, &glanda_plane_helper_funcs);
+
+ /* VBlank init */
+ ret = drm_vblank_init(&gdev->drm, 1);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize vblank\n");
+ goto err_mode_cleanup;
+ }
+
+ /* CRTC init */
+ ret = drm_crtc_init_with_planes(&gdev->drm, &gdev->crtc,
+ &gdev->primary_plane, NULL,
+ &glanda_crtc_funcs, NULL);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize CRTC with planes\n");
+ goto err_mode_cleanup;
+ }
+ drm_crtc_helper_add(&gdev->crtc, &glanda_crtc_helper_funcs);
+
+ ret = drm_simple_encoder_init(&gdev->drm, &gdev->encoder, DRM_MODE_ENCODER_DAC);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize encoder\n");
+ goto err_mode_cleanup;
+ }
+ gdev->encoder.possible_crtcs = 1;
+
+ ret = drm_connector_init(&gdev->drm, &gdev->connector,
+ &glanda_connector_funcs, DRM_MODE_CONNECTOR_VGA);
+ if (ret) {
+ drm_err(&gdev->drm, "Failed to initialize connector\n");
+ goto err_mode_cleanup;
+ }
+ drm_connector_helper_add(&gdev->connector, &glanda_connector_helper_funcs);
+
+ drm_connector_attach_encoder(&gdev->connector, &gdev->encoder);
+
+ /* Populate connector state early so userspace can enumerate modes. */
+ mutex_lock(&gdev->drm.mode_config.mutex);
+ drm_helper_probe_single_connector_modes(&gdev->connector, 1024, 768);
+ mutex_unlock(&gdev->drm.mode_config.mutex);
+
+ drm_mode_config_reset(&gdev->drm);
+
+ ret = drm_dev_register(&gdev->drm, 0);
+ if (ret)
+ goto err_mode_cleanup;
+
+ drm_info(&gdev->drm, "GlandaGPU DRM Initialized (/dev/dri/cardX created)\n");
+ return 0;
+
+err_mode_cleanup:
+ drm_mode_config_cleanup(&gdev->drm);
+ return ret;
+}
+
+static void glandagpu_remove(struct platform_device *pdev)
+{
+ struct glanda_device *gdev = platform_get_drvdata(pdev);
+
+ /* Disable interrupts first so no new IRQ work can race the teardown
+ * below, and wake up anyone still blocked in glanda_wait_idle().
+ */
+ writel(0, gdev->mmio_base + REG_IER);
+ gdev->cmd_done = true;
+ wake_up_interruptible(&gdev->cmd_wq);
+
+ drm_info(&gdev->drm, "GlandaGPU DRM Driver removed\n");
+ drm_dev_unregister(&gdev->drm);
+ drm_mode_config_cleanup(&gdev->drm);
+}
+
+/* Device Tree match table. */
+static const struct of_device_id glanda_of_match[] = {
+ {.compatible = "glanda,gpu-1.0", },
+ { /* end of table */ }
+};
+
+MODULE_DEVICE_TABLE(of, glanda_of_match);
+
+static struct platform_driver glandagpu_driver = {
+ .driver = {
+ .name = "glandagpu",
+ .of_match_table = glanda_of_match,
+ },
+ .probe = glandagpu_probe,
+ .remove = glandagpu_remove,
+};
+
+static int __init glandagpu_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&glandagpu_driver);
+ if (ret) {
+ pr_err("GlandaGPU: Failed to register platform driver\n");
+ return ret;
+ }
+
+ pr_info("GlandaGPU: Module loaded successfully\n");
+ return 0;
+}
+
+static void __exit glandagpu_exit(void)
+{
+ platform_driver_unregister(&glandagpu_driver);
+ pr_info("GlandaGPU: Module unloaded\n");
+}
+
+module_init(glandagpu_init);
+module_exit(glandagpu_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Leander Kieweg <kieweg.leander@gmail.com>");
+MODULE_DESCRIPTION("DRM driver for GlandaGPU, an FPGA-based 2D GPU with VGA output");
diff --git a/include/uapi/drm/glanda_drm.h b/include/uapi/drm/glanda_drm.h
new file mode 100644
index 000000000..35d25ba83
--- /dev/null
+++ b/include/uapi/drm/glanda_drm.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef _GLANDA_DRM_H_
+#define _GLANDA_DRM_H_
+
+#include <linux/types.h>
+#include <drm/drm.h>
+
+struct glanda_clear_cmd {
+ __u32 color;
+};
+
+struct glanda_draw_rect_cmd {
+ __u16 x;
+ __u16 y;
+ __u16 w;
+ __u16 h;
+ __u32 color;
+};
+
+struct glanda_draw_line_cmd {
+ __u16 x0;
+ __u16 y0;
+ __u16 x1;
+ __u16 y1;
+ __u32 color;
+};
+
+#define DRM_GLANDA_CLEAR 0x00
+#define DRM_GLANDA_DRAW_RECT 0x01
+#define DRM_GLANDA_DRAW_LINE 0x02
+
+#define DRM_IOCTL_GLANDA_CLEAR \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_GLANDA_CLEAR, struct glanda_clear_cmd)
+#define DRM_IOCTL_GLANDA_DRAW_RECT \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_GLANDA_DRAW_RECT, struct glanda_draw_rect_cmd)
+#define DRM_IOCTL_GLANDA_DRAW_LINE \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_GLANDA_DRAW_LINE, struct glanda_draw_line_cmd)
+
+#endif
--
2.43.0
next prev parent reply other threads:[~2026-07-14 10:13 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-14 10:11 [RFC PATCH 0/3] drm: Add DRM driver for GlandaGPU (VHDL soft-IP GPU) Leander Kieweg
2026-07-14 10:11 ` [RFC PATCH 1/3] dt-bindings: display: Add GlandaGPU binding Leander Kieweg
2026-07-14 10:23 ` sashiko-bot
2026-07-14 10:11 ` Leander Kieweg [this message]
2026-07-14 10:33 ` [RFC PATCH 2/3] drm/glanda: Add initial DRM driver for GlandaGPU sashiko-bot
2026-07-14 12:14 ` Uwe Kleine-König
2026-07-14 10:11 ` [RFC PATCH 3/3] NOT FOR MERGE: drm/glanda: Add x86 platform test device Leander Kieweg
2026-07-14 10:47 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260714101146.200416-3-kieweg.leander@gmail.com \
--to=kieweg.leander@gmail.com \
--cc=airlied@gmail.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=krzk+dt@kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=robh@kernel.org \
--cc=simona@ffwll.ch \
--cc=tzimmermann@suse.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox