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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.