From: Ho Jie Feng <hjf3108@gmail.com>
To: dri-devel@lists.freedesktop.org
Cc: Ho Jie Feng <hjf3108@gmail.com>,
Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
Maxime Ripard <mripard@kernel.org>,
Thomas Zimmermann <tzimmermann@suse.de>,
David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
linux-kernel@vger.kernel.org
Subject: [PATCH v1 3/4] drm/ms91xx: Add atomic modesetting and USB framebuffer transfers
Date: Mon, 19 Oct 2026 23:48:07 +0800 [thread overview]
Message-ID: <20261019154820.68659-4-hjf3108@gmail.com> (raw)
In-Reply-To: <20261019154820.68659-1-hjf3108@gmail.com>
Implements:
- DRM core code
- Double buffered USB framebuffer transfers
- Pending-transfer replacement with the latest pixels and merged damage
- Cache maintenance for converted vmalloc transfer buffers
- USB driver matching
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/gpu/drm/ms91xx/ms91xx.h | 217 ++++++++++
drivers/gpu/drm/ms91xx/ms91xx_drv.c | 518 +++++++++++++++++++++++
drivers/gpu/drm/ms91xx/ms91xx_transfer.c | 218 ++++++++++
3 files changed, 953 insertions(+)
create mode 100644 drivers/gpu/drm/ms91xx/ms91xx.h
create mode 100644 drivers/gpu/drm/ms91xx/ms91xx_drv.c
create mode 100644 drivers/gpu/drm/ms91xx/ms91xx_transfer.c
diff --git a/drivers/gpu/drm/ms91xx/ms91xx.h b/drivers/gpu/drm/ms91xx/ms91xx.h
new file mode 100644
index 0000000000000..83d3ca96e7b22
--- /dev/null
+++ b/drivers/gpu/drm/ms91xx/ms91xx.h
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef MS91XX_H
+#define MS91XX_H
+
+#include <linux/bits.h>
+#include <linux/compiler_attributes.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/mutex.h>
+#include <linux/scatterlist.h>
+#include <linux/timer_types.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_rect.h>
+
+struct drm_format_conv_state;
+struct drm_framebuffer;
+struct iosys_map;
+
+#define DRIVER_NAME "ms91xx"
+#define DRIVER_DESC "MacroSilicon USB to VGA/HDMI"
+
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+
+#define MS91XX_BULK_OUT_ENDPOINT 4
+
+#define MS91XX_REQ_TYPE_WRITE_6_BYTES 0xa6
+#define MS91XX_REQ_TYPE_READ_BYTE 0xb5
+#define MS91XX_REQ_TYPE_READ_FLASH 0xf5
+
+#define MS91XX_REG_VIDEO_PORT 0x0031
+#define MS91XX_REG_DISPLAY_STATUS 0x0032
+#define MS91XX_REG_EDID_BASE 0xc000
+
+#define MS91XX_REG_MODE_SEQUENCE_0 0x0030
+#define MS91XX_REG_MODE_SEQUENCE_1 0x0033
+#define MS91XX_REG_MODE_SEQUENCE_2 0xc620
+
+#define MS91XX_CMD_RESOLUTION 0x01
+#define MS91XX_CMD_MODE 0x02
+#define MS91XX_CMD_UNKNOWN1 0x03
+#define MS91XX_CMD_UNKNOWN2 0x04
+#define MS91XX_CMD_OUTPUT_ENABLE 0x05
+#define MS91XX_CMD_POWER 0x07
+
+#define MS913X_REG_CHIP_ID 0xff00
+#define MS912X_REG_CHIP_ID 0xf000
+#define MS913X_CHIP_ID_SIGNATURE_MSB 0x13
+#define MS912X_CHIP_ID_SIGNATURE_MSB 0x16
+#define MS91XX_CHIP_ID_SIGNATURE_LSB 0x0a
+#define MS913X_CUSTOM_TIMING_BASE 0xfc50
+#define MS912X_CUSTOM_TIMING_BASE 0x1c00
+#define MS91XX_CUSTOM_TIMING_OFFSET 0x10
+#define MS91XX_CUSTOM_TIMING_STRIDE 0x20
+
+#define MS91XX_TIMING_PROGRESSIVE BIT(0)
+#define MS91XX_TIMING_POSITIVE_HSYNC BIT(1)
+#define MS91XX_TIMING_POSITIVE_VSYNC BIT(2)
+
+enum ms91xx_video_port {
+ MS91XX_VIDEO_PORT_CVBS = 0,
+ MS91XX_VIDEO_PORT_SVIDEO = 1,
+ MS91XX_VIDEO_PORT_VGA = 2,
+ MS91XX_VIDEO_PORT_YPBPR = 3,
+ MS91XX_VIDEO_PORT_CVBS_SVIDEO = 4,
+ MS91XX_VIDEO_PORT_HDMI = 5,
+ MS91XX_VIDEO_PORT_DIGITAL = 6,
+ MS91XX_VIDEO_PORT_UNKNOWN = 0xff,
+};
+
+struct ms91xx_usb_request {
+ void *transfer_buffer;
+ struct ms91xx_device *ms91xx;
+ size_t transfer_len;
+ struct drm_rect transfer_rect;
+ struct sg_table transfer_sgt;
+ struct usb_sg_request sgr;
+ struct work_struct work;
+ struct timer_list timer;
+ struct completion done;
+};
+
+struct ms91xx_mode {
+ int width;
+ int height;
+ int hz;
+ int mode;
+};
+
+struct ms91xx_custom_mode {
+ struct ms91xx_mode mode;
+ struct drm_display_mode display_mode;
+};
+
+#define MS91XX_PIXFMT_RGB888 0x11
+#define MS91XX_PIXFMT_UYVY 0x22
+#define MS91XX_BYTE_SELECT_UYVY 0x00
+
+#define MS91XX_MODE(w, h, z, m) \
+ { \
+ .width = w, .height = h, .hz = z, .mode = m \
+ }
+
+struct ms91xx_device {
+ struct drm_device drm;
+ struct usb_interface *intf;
+ unsigned int bulk_pipe;
+ enum ms91xx_video_port port_type;
+ struct workqueue_struct *workqueue;
+ /* Serializes HID control request/response sequences */
+ struct mutex ctrl_lock;
+
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+ struct drm_crtc crtc;
+ struct drm_plane plane;
+
+ struct ms91xx_custom_mode custom_modes[2];
+ unsigned int num_custom_modes;
+
+ struct drm_rect update_rect;
+
+ /* Double buffer to allow memcpy and transfer
+ * to happen in parallel
+ */
+ int current_request;
+ struct ms91xx_usb_request requests[2];
+};
+
+struct ms91xx_request {
+ u8 type;
+ __be16 addr;
+ u8 data[5];
+} __packed;
+
+struct ms91xx_write_request {
+ u8 type;
+ u8 addr;
+ u8 data[6];
+} __packed;
+
+struct ms91xx_resolution_request {
+ __be16 width;
+ __be16 height;
+ u8 pixel_format;
+ u8 byte_select;
+} __packed;
+
+struct ms91xx_mode_request {
+ u8 mode;
+ u8 pixel_format;
+ __be16 width;
+ __be16 height;
+} __packed;
+
+struct ms91xx_frame_update_header {
+ __be16 marker;
+ u8 position[3]; /* x:y, packed 12-bit big-endian values */
+ u8 dimensions[3]; /* width:height, packed 12-bit big-endian values */
+} __packed;
+
+struct ms91xx_flash_read_request {
+ u8 type;
+ u8 addr[3];
+ u8 reserved[4];
+} __packed;
+
+struct ms91xx_custom_timing_record {
+ u8 vic;
+ u8 polarity;
+ __le16 htotal;
+ __le16 vtotal;
+ __le16 hactive;
+ __le16 vactive;
+ __le16 pixclk;
+ __le16 vfreq;
+ __le16 hoffset;
+ __le16 voffset;
+ __le16 hsyncwidth;
+ __le16 vsyncwidth;
+} __packed;
+
+#define MS91XX_FRAME_OVERHEAD 16
+#define MS91XX_MAX_WIDTH 1920
+#define MS91XX_MAX_HEIGHT 1200
+#define MS91XX_MAX_TRANSFER_LEN \
+ (MS91XX_MAX_WIDTH * MS91XX_MAX_HEIGHT * 2 + MS91XX_FRAME_OVERHEAD)
+
+#define to_ms91xx(x) container_of(x, struct ms91xx_device, drm)
+
+int ms91xx_read_byte(struct ms91xx_device *ms91xx, u16 address);
+int ms91xx_read_custom_timing(struct ms91xx_device *ms91xx);
+int ms91xx_connector_init(struct ms91xx_device *ms91xx);
+int ms91xx_set_resolution(struct ms91xx_device *ms91xx,
+ const struct ms91xx_mode *mode);
+
+int ms91xx_power_on(struct ms91xx_device *ms91xx);
+int ms91xx_power_off(struct ms91xx_device *ms91xx);
+
+int ms91xx_fb_send_rect(struct drm_framebuffer *fb, const struct iosys_map *map,
+ struct drm_format_conv_state *fmtcnv_state,
+ struct drm_rect *rect);
+
+void ms91xx_free_request(struct ms91xx_usb_request *request);
+int ms91xx_init_request(struct ms91xx_device *ms91xx,
+ struct ms91xx_usb_request *request, size_t len);
+#endif
diff --git a/drivers/gpu/drm/ms91xx/ms91xx_drv.c b/drivers/gpu/drm/ms91xx/ms91xx_drv.c
new file mode 100644
index 0000000000000..2ca855f2363c8
--- /dev/null
+++ b/drivers/gpu/drm/ms91xx/ms91xx_drv.c
@@ -0,0 +1,518 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/array_size.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fbdev_shmem.h>
+#include <drm/drm_file.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_mode_config.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_rect.h>
+
+#include "ms91xx.h"
+
+static int ms91xx_usb_suspend(struct usb_interface *interface,
+ pm_message_t message)
+{
+ struct ms91xx_device *ms91xx = usb_get_intfdata(interface);
+
+ return drm_mode_config_helper_suspend(&ms91xx->drm);
+}
+
+static int ms91xx_usb_resume(struct usb_interface *interface)
+{
+ struct ms91xx_device *ms91xx = usb_get_intfdata(interface);
+
+ return drm_mode_config_helper_resume(&ms91xx->drm);
+}
+
+DEFINE_DRM_GEM_FOPS(ms91xx_driver_fops);
+
+static const struct drm_driver ms91xx_drm_driver = {
+ .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET,
+
+ /* GEM hooks */
+ .fops = &ms91xx_driver_fops,
+ DRM_GEM_SHMEM_DRIVER_OPS,
+ DRM_FBDEV_SHMEM_DRIVER_OPS,
+
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+};
+
+static const struct drm_mode_config_funcs ms91xx_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create_with_dirty,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static const struct drm_mode_config_helper_funcs
+ms91xx_mode_config_helper_funcs = {
+ .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+};
+
+static const struct ms91xx_mode ms91xx_mode_list[] = {
+ /* Found in captures of the Windows driver and dumped from device */
+ MS91XX_MODE(720, 480, 60, 0x02), /* 60.00 Hz */
+ MS91XX_MODE(720, 576, 50, 0x11), /* 50.00 Hz */
+ MS91XX_MODE(1280, 720, 50, 0x13), /* 50.00 Hz */
+ MS91XX_MODE(1920, 1080, 50, 0x1f), /* 50.00 Hz */
+ MS91XX_MODE(1920, 1080, 30, 0x22), /* 30.00 Hz */
+ MS91XX_MODE(640, 480, 60, 0x40), /* 59.94 Hz */
+ MS91XX_MODE(800, 600, 60, 0x42), /* 60.32 Hz */
+ MS91XX_MODE(800, 600, 75, 0x44), /* 75.00 Hz */
+ MS91XX_MODE(1024, 768, 60, 0x47), /* 60.00 Hz */
+ MS91XX_MODE(1024, 768, 75, 0x49), /* 75.03 Hz */
+ MS91XX_MODE(1152, 864, 60, 0x4c), /* 60.00 Hz */
+ MS91XX_MODE(1280, 600, 60, 0x4e), /* 60.00 Hz */
+ MS91XX_MODE(1280, 720, 60, 0x4f), /* 60.00 Hz */
+ MS91XX_MODE(1280, 768, 60, 0x54), /* 59.87 Hz */
+ MS91XX_MODE(1280, 768, 75, 0x56), /* 74.89 Hz */
+ MS91XX_MODE(1280, 800, 60, 0x57), /* 59.81 Hz */
+ MS91XX_MODE(1280, 960, 60, 0x5b), /* 60.00 Hz */
+ MS91XX_MODE(1280, 1024, 60, 0x60), /* 60.02 Hz */
+ MS91XX_MODE(1280, 1024, 75, 0x61), /* 75.02 Hz */
+ MS91XX_MODE(1360, 768, 60, 0x64), /* 60.02 Hz */
+ MS91XX_MODE(1366, 768, 60, 0x66), /* 59.79 Hz */
+ MS91XX_MODE(1400, 1050, 60, 0x67), /* 59.98 Hz */
+ MS91XX_MODE(1440, 900, 60, 0x6b), /* 59.89 Hz */
+ MS91XX_MODE(1600, 1200, 60, 0x73), /* 60.00 Hz */
+ MS91XX_MODE(1680, 1050, 60, 0x78), /* 59.95 Hz */
+ MS91XX_MODE(1920, 1080, 60, 0x81), /* 60.00 Hz */
+};
+
+static const struct ms91xx_mode *
+ms91xx_get_mode(struct ms91xx_device *ms91xx,
+ const struct drm_display_mode *mode)
+{
+ unsigned int i;
+ int width = mode->hdisplay;
+ int height = mode->vdisplay;
+ int hz = drm_mode_vrefresh(mode);
+
+ for (i = 0; i < ms91xx->num_custom_modes; i++) {
+ const struct ms91xx_mode *custom_mode =
+ &ms91xx->custom_modes[i].mode;
+
+ if (custom_mode->width == width &&
+ custom_mode->height == height &&
+ custom_mode->hz == hz)
+ return custom_mode;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ms91xx_mode_list); i++) {
+ if (ms91xx_mode_list[i].width == width &&
+ ms91xx_mode_list[i].height == height &&
+ ms91xx_mode_list[i].hz == hz) {
+ return &ms91xx_mode_list[i];
+ }
+ }
+ return NULL;
+}
+
+static void ms91xx_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *crtc_state =
+ drm_atomic_get_new_crtc_state(state, crtc);
+ struct drm_device *dev = crtc->dev;
+ struct ms91xx_device *ms91xx = to_ms91xx(dev);
+ const struct ms91xx_mode *ms_mode;
+ int ret;
+
+ ret = ms91xx_power_on(ms91xx);
+ if (ret) {
+ drm_err(dev, "failed to power on display: %d\n", ret);
+ return;
+ }
+
+ ms_mode = ms91xx_get_mode(ms91xx, &crtc_state->mode);
+ if (!ms_mode) {
+ drm_err(dev, "unsupported mode passed to CRTC enable\n");
+ return;
+ }
+
+ ret = ms91xx_set_resolution(ms91xx, ms_mode);
+ if (ret)
+ drm_err(dev, "failed to set display mode: %d\n", ret);
+}
+
+static void ms91xx_cancel_transfer_work(struct ms91xx_device *ms91xx)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ms91xx->requests); i++) {
+ struct ms91xx_usb_request *request = &ms91xx->requests[i];
+
+ if (cancel_work_sync(&request->work))
+ complete(&request->done);
+ }
+}
+
+static void ms91xx_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_device *dev = crtc->dev;
+ struct ms91xx_device *ms91xx = to_ms91xx(dev);
+ int ret;
+
+ ms91xx_cancel_transfer_work(ms91xx);
+ ret = ms91xx_power_off(ms91xx);
+ if (ret && ret != -ENODEV)
+ drm_err(dev, "failed to power off display: %d\n", ret);
+}
+
+static enum drm_mode_status
+ms91xx_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct ms91xx_device *ms91xx = to_ms91xx(crtc->dev);
+ const struct ms91xx_mode *ms_mode = ms91xx_get_mode(ms91xx, mode);
+
+ if (!ms_mode)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+static int ms91xx_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *old_plane_state;
+ struct drm_plane_state *new_plane_state;
+ struct drm_crtc_state *crtc_state = NULL;
+
+ old_plane_state = drm_atomic_get_old_plane_state(state, plane);
+ new_plane_state = drm_atomic_get_new_plane_state(state, plane);
+ if (new_plane_state->crtc)
+ crtc_state = drm_atomic_get_new_crtc_state(state,
+ new_plane_state->crtc);
+
+ /*
+ * A mode set does change how the device interprets the framebuffer
+ * so ask for a full plane update for the first frame after one.
+ */
+ new_plane_state->ignore_damage_clips =
+ old_plane_state->fb != new_plane_state->fb ||
+ (crtc_state && drm_atomic_crtc_needs_modeset(crtc_state));
+
+ return drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
+ DRM_PLANE_NO_SCALING,
+ DRM_PLANE_NO_SCALING,
+ false, false);
+}
+
+static void ms91xx_clear_rect(struct drm_rect *rect)
+{
+ rect->x1 = INT_MAX;
+ rect->y1 = INT_MAX;
+ rect->x2 = 0;
+ rect->y2 = 0;
+}
+
+static void ms91xx_merge_rects(struct drm_rect *dest, struct drm_rect *r1,
+ struct drm_rect *r2)
+{
+ dest->x1 = min(r1->x1, r2->x1);
+ dest->y1 = min(r1->y1, r2->y1);
+ dest->x2 = max(r1->x2, r2->x2);
+ dest->y2 = max(r1->y2, r2->y2);
+}
+
+static void ms91xx_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *old_plane_state;
+ struct drm_plane_state *new_plane_state;
+ struct drm_crtc_state *new_crtc_state;
+ struct drm_shadow_plane_state *shadow_plane_state;
+ struct ms91xx_device *ms91xx = to_ms91xx(plane->dev);
+ struct ms91xx_usb_request *current_request, *previous_request;
+ struct drm_rect current_rect, rect;
+
+ old_plane_state = drm_atomic_get_old_plane_state(state, plane);
+ new_plane_state = drm_atomic_get_new_plane_state(state, plane);
+
+ if (!new_plane_state->fb)
+ return;
+ new_crtc_state = drm_atomic_get_new_crtc_state(state,
+ new_plane_state->crtc);
+ shadow_plane_state = to_drm_shadow_plane_state(new_plane_state);
+
+ /* Damage from the previous mode may not fit the new framebuffer. */
+ if (drm_atomic_crtc_needs_modeset(new_crtc_state))
+ ms91xx_clear_rect(&ms91xx->update_rect);
+
+ if (drm_atomic_helper_damage_merged(old_plane_state, new_plane_state,
+ ¤t_rect)) {
+ current_request = &ms91xx->requests[ms91xx->current_request];
+ previous_request = &ms91xx->requests[!ms91xx->current_request];
+
+ /* Latency reduction: requeue with the latest frame data. */
+ if (cancel_work(&previous_request->work)) {
+ complete(&previous_request->done);
+ ms91xx_merge_rects(¤t_rect, ¤t_rect,
+ &previous_request->transfer_rect);
+ current_request = previous_request;
+ ms91xx->current_request = !ms91xx->current_request;
+ }
+
+ current_request->transfer_rect = current_rect;
+
+ /*
+ * The device double buffers, so we need to send the update
+ * rects of the last two frames.
+ */
+ ms91xx_merge_rects(&rect, ¤t_rect, &ms91xx->update_rect);
+ if (ms91xx_fb_send_rect(new_plane_state->fb,
+ &shadow_plane_state->data[0],
+ &shadow_plane_state->fmtcnv_state,
+ &rect)) {
+ /* In case of error, merge the rects to update later */
+ ms91xx_merge_rects(&ms91xx->update_rect,
+ &ms91xx->update_rect, &rect);
+ } else {
+ ms91xx->update_rect = current_rect;
+ }
+ }
+}
+
+static const struct drm_crtc_helper_funcs ms91xx_crtc_helper_funcs = {
+ .mode_valid = ms91xx_crtc_mode_valid,
+ .atomic_check = drm_crtc_helper_atomic_check,
+ .atomic_enable = ms91xx_crtc_atomic_enable,
+ .atomic_disable = ms91xx_crtc_atomic_disable,
+};
+
+static const struct drm_crtc_funcs ms91xx_crtc_funcs = {
+ .atomic_create_state = drm_atomic_helper_crtc_create_state,
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+static const struct drm_plane_helper_funcs ms91xx_plane_helper_funcs = {
+ DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
+ .atomic_check = ms91xx_plane_atomic_check,
+ .atomic_update = ms91xx_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs ms91xx_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = drm_plane_cleanup,
+ DRM_GEM_SHADOW_PLANE_FUNCS,
+};
+
+static const u32 ms91xx_plane_formats[] = {
+ DRM_FORMAT_XRGB8888,
+};
+
+static const u8 ms91xx_bulk_out_endpoints[] = {
+ MS91XX_BULK_OUT_ENDPOINT, 0
+};
+
+static int ms91xx_usb_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ int ret;
+ unsigned int i;
+ struct ms91xx_device *ms91xx;
+ struct drm_device *dev;
+ struct device *dma_dev;
+ struct usb_device *usbdev;
+
+ ms91xx = devm_drm_dev_alloc(&interface->dev, &ms91xx_drm_driver,
+ struct ms91xx_device, drm);
+ if (IS_ERR(ms91xx))
+ return PTR_ERR(ms91xx);
+
+ ms91xx->intf = interface;
+ ret = devm_mutex_init(&interface->dev, &ms91xx->ctrl_lock);
+ if (ret)
+ return ret;
+
+ if (!usb_check_bulk_endpoints(interface, ms91xx_bulk_out_endpoints))
+ return -ENXIO;
+
+ ret = ms91xx_read_custom_timing(ms91xx);
+ if (ret < 0) {
+ drm_warn(&ms91xx->drm,
+ "failed to read custom timing: %d\n", ret);
+ }
+ for (i = 0; i < ms91xx->num_custom_modes; i++) {
+ const struct ms91xx_mode *mode = &ms91xx->custom_modes[i].mode;
+
+ drm_info(&ms91xx->drm,
+ "custom mode %dx%d@%d uses mode 0x%02x\n",
+ mode->width, mode->height, mode->hz, mode->mode);
+ }
+
+ usbdev = interface_to_usbdev(interface);
+ ms91xx->bulk_pipe =
+ usb_sndbulkpipe(usbdev, MS91XX_BULK_OUT_ENDPOINT);
+ dev = &ms91xx->drm;
+
+ dma_dev = usb_intf_get_dma_device(interface);
+ if (dma_dev) {
+ drm_dev_set_dma_dev(dev, dma_dev);
+ put_device(dma_dev);
+ } else {
+ drm_warn(dev,
+ "buffer sharing not supported\n"); /* not an error */
+ }
+ ret = drmm_mode_config_init(dev);
+ if (ret)
+ return ret;
+
+ dev->mode_config.min_width = 0;
+ dev->mode_config.max_width = MS91XX_MAX_WIDTH;
+ dev->mode_config.min_height = 0;
+ dev->mode_config.max_height = MS91XX_MAX_HEIGHT;
+ dev->mode_config.funcs = &ms91xx_mode_config_funcs;
+ dev->mode_config.helper_private = &ms91xx_mode_config_helper_funcs;
+
+ /* This stops weird behavior in the device */
+ ret = ms91xx_set_resolution(ms91xx, &ms91xx_mode_list[0]);
+ if (ret)
+ return ret;
+
+ ms91xx->workqueue =
+ drmm_alloc_ordered_workqueue(dev, DRIVER_NAME, 0);
+ if (IS_ERR(ms91xx->workqueue))
+ return PTR_ERR(ms91xx->workqueue);
+
+ ret = ms91xx_init_request(ms91xx, &ms91xx->requests[0],
+ MS91XX_MAX_TRANSFER_LEN);
+ if (ret)
+ return ret;
+
+ ret = ms91xx_init_request(ms91xx, &ms91xx->requests[1],
+ MS91XX_MAX_TRANSFER_LEN);
+ if (ret)
+ goto err_free_request_0;
+
+ ret = drm_universal_plane_init(dev, &ms91xx->plane, 0,
+ &ms91xx_plane_funcs,
+ ms91xx_plane_formats,
+ ARRAY_SIZE(ms91xx_plane_formats), NULL,
+ DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (ret)
+ goto err_free_request_1;
+
+ drm_plane_helper_add(&ms91xx->plane, &ms91xx_plane_helper_funcs);
+ drm_plane_enable_fb_damage_clips(&ms91xx->plane);
+ ms91xx_clear_rect(&ms91xx->update_rect);
+
+ ret = drm_crtc_init_with_planes(dev, &ms91xx->crtc, &ms91xx->plane,
+ NULL, &ms91xx_crtc_funcs, NULL);
+ if (ret)
+ goto err_free_request_1;
+
+ drm_crtc_helper_add(&ms91xx->crtc, &ms91xx_crtc_helper_funcs);
+
+ ret = ms91xx_connector_init(ms91xx);
+ if (ret)
+ goto err_free_request_1;
+
+ drm_mode_config_reset(dev);
+
+ usb_set_intfdata(interface, ms91xx);
+
+ drmm_kms_helper_poll_init(dev);
+
+ ret = drm_dev_register(dev, 0);
+ if (ret)
+ goto err_free_request_1;
+
+ drm_client_setup(dev, NULL);
+
+ return 0;
+
+err_free_request_1:
+ ms91xx_free_request(&ms91xx->requests[1]);
+err_free_request_0:
+ ms91xx_free_request(&ms91xx->requests[0]);
+ return ret;
+}
+
+static void ms91xx_usb_disconnect(struct usb_interface *interface)
+{
+ struct ms91xx_device *ms91xx = usb_get_intfdata(interface);
+ struct drm_device *dev = &ms91xx->drm;
+
+ drm_dev_unplug(dev);
+ drm_atomic_helper_shutdown(dev);
+ ms91xx_cancel_transfer_work(ms91xx);
+ ms91xx_free_request(&ms91xx->requests[0]);
+ ms91xx_free_request(&ms91xx->requests[1]);
+}
+
+static void ms91xx_usb_shutdown(struct usb_interface *interface)
+{
+ struct ms91xx_device *ms91xx = usb_get_intfdata(interface);
+
+ drm_atomic_helper_shutdown(&ms91xx->drm);
+}
+
+static const struct usb_device_id id_table[] = {
+ /* USB 2 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x534d, 0x6021, 0xff, 0x00, 0x00) },
+ /* USB 2 Sometimes this PID will pop up */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x534d, 0x0821, 0xff, 0x00, 0x00) },
+ /* USB 3 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x345f, 0x9132, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x345f, 0x9133, 0xff, 0x00, 0x00) },
+ { USB_DEVICE_AND_INTERFACE_INFO(0x345f, 0x9135, 0xff, 0x00, 0x00) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_driver ms91xx_driver = {
+ .name = DRIVER_NAME,
+ .probe = ms91xx_usb_probe,
+ .disconnect = ms91xx_usb_disconnect,
+ .suspend = ms91xx_usb_suspend,
+ .resume = ms91xx_usb_resume,
+ .reset_resume = ms91xx_usb_resume,
+ .shutdown = ms91xx_usb_shutdown,
+ .id_table = id_table,
+};
+module_usb_driver(ms91xx_driver);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/ms91xx/ms91xx_transfer.c b/drivers/gpu/drm/ms91xx/ms91xx_transfer.c
new file mode 100644
index 0000000000000..8b88b2206ce3d
--- /dev/null
+++ b/drivers/gpu/drm/ms91xx/ms91xx_transfer.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <asm/byteorder.h>
+#include <linux/align.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/dma-direction.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/highmem.h>
+#include <linux/iosys-map.h>
+#include <linux/jiffies.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/mm.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_drv.h>
+#include <drm/drm_format_helper.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_rect.h>
+
+#include "ms91xx.h"
+
+static void ms91xx_request_timeout(struct timer_list *t)
+{
+ struct ms91xx_usb_request *request = timer_container_of(request, t, timer);
+
+ usb_sg_cancel(&request->sgr);
+}
+
+static void ms91xx_request_work(struct work_struct *work)
+{
+ struct ms91xx_usb_request *request =
+ container_of(work, struct ms91xx_usb_request, work);
+ struct ms91xx_device *ms91xx = request->ms91xx;
+ struct usb_device *usbdev = interface_to_usbdev(ms91xx->intf);
+ struct usb_sg_request *sgr = &request->sgr;
+ struct sg_table *transfer_sgt = &request->transfer_sgt;
+ int idx, ret;
+
+ if (!drm_dev_enter(&ms91xx->drm, &idx)) {
+ ret = -ENODEV;
+ goto complete;
+ }
+
+ ret = usb_sg_init(sgr, usbdev, ms91xx->bulk_pipe, 0, transfer_sgt->sgl,
+ transfer_sgt->nents, request->transfer_len,
+ GFP_KERNEL);
+ if (ret)
+ goto dev_exit;
+
+ mod_timer(&request->timer, jiffies + msecs_to_jiffies(5000));
+ usb_sg_wait(sgr);
+
+ if (!timer_delete_sync(&request->timer))
+ ret = -ETIMEDOUT;
+ else if (sgr->status < 0)
+ ret = sgr->status;
+ else if (sgr->bytes != request->transfer_len)
+ ret = -EIO;
+
+dev_exit:
+ drm_dev_exit(idx);
+complete:
+ if (ret < 0 && ret != -ENODEV)
+ drm_err_ratelimited(&ms91xx->drm,
+ "failed to send framebuffer: %d\n", ret);
+ complete(&request->done);
+}
+
+void ms91xx_free_request(struct ms91xx_usb_request *request)
+{
+ if (!request->transfer_buffer)
+ return;
+
+ timer_shutdown_sync(&request->timer);
+ sg_free_table(&request->transfer_sgt);
+ vfree(request->transfer_buffer);
+ request->transfer_buffer = NULL;
+}
+
+int ms91xx_init_request(struct ms91xx_device *ms91xx,
+ struct ms91xx_usb_request *request, size_t len)
+{
+ int ret;
+ unsigned int i, num_pages;
+ void *data;
+ struct page **pages;
+ void *ptr;
+
+ data = vmalloc_32(len);
+ if (!data)
+ return -ENOMEM;
+
+ num_pages = DIV_ROUND_UP(len, PAGE_SIZE);
+ pages = kmalloc_array(num_pages, sizeof(struct page *), GFP_KERNEL);
+ if (!pages) {
+ ret = -ENOMEM;
+ goto err_vfree;
+ }
+
+ for (i = 0, ptr = data; i < num_pages; i++, ptr += PAGE_SIZE)
+ pages[i] = vmalloc_to_page(ptr);
+ ret = sg_alloc_table_from_pages(&request->transfer_sgt, pages,
+ num_pages, 0, len, GFP_KERNEL);
+ kfree(pages);
+ if (ret)
+ goto err_vfree;
+
+ request->transfer_buffer = data;
+ request->ms91xx = ms91xx;
+
+ init_completion(&request->done);
+ complete(&request->done);
+ timer_setup(&request->timer, ms91xx_request_timeout, 0);
+ INIT_WORK(&request->work, ms91xx_request_work);
+ return 0;
+
+err_vfree:
+ vfree(data);
+ return ret;
+}
+
+static const u8 ms91xx_end_of_buffer[] = { 0xff, 0xc0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+
+static void ms91xx_fb_xrgb8888_to_yuv422(void *dst,
+ const struct iosys_map *src,
+ struct drm_framebuffer *fb,
+ const struct drm_rect *rect,
+ struct drm_format_conv_state *fmtcnv_state)
+{
+ struct ms91xx_frame_update_header *header = dst;
+ struct iosys_map dst_map;
+ u32 position, dimensions;
+ int x, y1, width;
+
+ y1 = rect->y1;
+ x = rect->x1;
+ width = drm_rect_width(rect);
+
+ header->marker = cpu_to_be16(0xff00);
+ position = ((x & 0xfff) << 12) | (y1 & 0xfff);
+ dimensions = ((width & 0xfff) << 12) |
+ (drm_rect_height(rect) & 0xfff);
+ put_unaligned_be24(position, header->position);
+ put_unaligned_be24(dimensions, header->dimensions);
+ dst += sizeof(*header);
+
+ iosys_map_set_vaddr(&dst_map, dst);
+ drm_fb_xrgb8888_to_uyvy(&dst_map, NULL, src, fb, rect, fmtcnv_state);
+ dst += width * drm_rect_height(rect) * 2;
+
+ memcpy(dst, ms91xx_end_of_buffer, sizeof(ms91xx_end_of_buffer));
+}
+
+int ms91xx_fb_send_rect(struct drm_framebuffer *fb, const struct iosys_map *map,
+ struct drm_format_conv_state *fmtcnv_state,
+ struct drm_rect *rect)
+{
+ int ret = 0, idx;
+ struct ms91xx_device *ms91xx = to_ms91xx(fb->dev);
+ struct drm_device *drm = &ms91xx->drm;
+ struct ms91xx_usb_request *current_request;
+ int x, width;
+
+ /* UYVY stores pixels in pairs. Expand damage to a complete pair. */
+ x = ALIGN_DOWN(rect->x1, 2);
+ width = min_t(int, ALIGN(rect->x2, 2), fb->width) - x;
+ rect->x1 = x;
+ rect->x2 = x + width;
+ rect->y2 = min_t(unsigned int, rect->y2, fb->height);
+ current_request = &ms91xx->requests[ms91xx->current_request];
+
+ if (!drm_dev_enter(drm, &idx))
+ return -ENODEV;
+
+ /* Transfer buffer still in use, drop this frame. */
+ if (!wait_for_completion_timeout(¤t_request->done,
+ msecs_to_jiffies(10))) {
+ ret = -ETIMEDOUT;
+ goto dev_exit;
+ }
+
+ ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE);
+ if (ret < 0)
+ goto request_complete;
+
+ ms91xx_fb_xrgb8888_to_yuv422(current_request->transfer_buffer,
+ map, fb, rect, fmtcnv_state);
+ current_request->transfer_len =
+ width * 2 * drm_rect_height(rect) + MS91XX_FRAME_OVERHEAD;
+ flush_kernel_vmap_range(current_request->transfer_buffer,
+ current_request->transfer_len);
+
+ drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
+
+ queue_work(ms91xx->workqueue, ¤t_request->work);
+ ms91xx->current_request = 1 - ms91xx->current_request;
+ goto dev_exit;
+
+request_complete:
+ complete(¤t_request->done);
+dev_exit:
+ drm_dev_exit(idx);
+ return ret;
+}
--
2.55.0
next prev parent reply other threads:[~2026-10-19 15:48 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 15:48 [PATCH v1 0/4] Add MacroSilicon MS91xx USB display driver Ho Jie Feng
2026-10-19 15:48 ` [PATCH v1 1/4] drm/format-helper: Add XRGB8888 to UYVY conversion Ho Jie Feng
2026-10-19 15:48 ` [PATCH v1 2/4] drm/ms91xx: Add register access and connector support Ho Jie Feng
2026-10-19 15:48 ` Ho Jie Feng [this message]
2026-10-19 15:48 ` [PATCH v1 4/4] drm/ms91xx: Add Kconfig and Makefile integration Ho Jie Feng
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=20261019154820.68659-4-hjf3108@gmail.com \
--to=hjf3108@gmail.com \
--cc=airlied@gmail.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@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
all inboxes | Powered by JetHome®