diff --git a/Makefile b/Makefile index 9071577..a8c1420 100644 --- a/Makefile +++ b/Makefile @@ -2,18 +2,29 @@ ms912x-y := \ ms912x_registers.o \ ms912x_connector.o \ ms912x_transfer.o \ - ms912x_drv.o + ms912x_drv.o obj-m := ms912x.o KVER ?= $(shell uname -r) KSRC ?= /lib/modules/$(KVER)/build +CURDIR := $(shell pwd) + +USE_SSE2 ?= 0 + +ifeq ($(USE_SSE2),1) + CFLAGS_EXTRA += -msse2 +endif + +EXTRA_CFLAGS += $(CFLAGS_EXTRA) all: modules modules: - make CHECK="/usr/bin/sparse" -C $(KSRC) M=$(PWD) modules + $(MAKE) CHECK="/usr/bin/sparse" -C $(KSRC) M=$(CURDIR) modules clean: - make -C $(KSRC) M=$(PWD) clean - rm -f $(PWD)/Module.symvers $(PWD)/*.ur-safe + $(MAKE) -C $(KSRC) M=$(CURDIR) clean + rm -f $(CURDIR)/Module.symvers $(CURDIR)/*.ur-safe + +.PHONY: all modules clean diff --git a/README.md b/README.md index 7d01807..7ea954c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,13 @@ There are two variants: - VID/PID is 534d:6021. Device is USB 2 - VID/PID is 345f:9132. Device is USB 3 -For kernel 6.1 checkout branch kernel-6.1 +- Improved performance and Driver adaptation for Linux kernel 6.15. by Andrey Rodríguez Araya + +- For kernel 6.16 checkout branch kernel-6.16 +- For kernel 6.15 checkout branch kernel-6.15 +- For kernel 6.12 checkout branch kernel-6.12 +- For kernel 6.11 ? +- For kernel 6.8 - 6.10 checkout branch kernel-6.8 TODOs: @@ -23,3 +29,12 @@ Driver is written by analyzing wireshark captures of the device. Run `sudo dkms install .` +- make clean +- make all -j +- sudo rmmod ms912x # It will not work if the device is in use. +- sudo modprobe drm_shmem_helper +- sudo insmod ms912x.ko + +Forked From: https://github.com/rhgndf/ms912x + + diff --git a/ms912x.h b/ms912x.h index fa51cb6..5acd71c 100644 --- a/ms912x.h +++ b/ms912x.h @@ -23,6 +23,7 @@ struct ms912x_usb_request { void *transfer_buffer; + void *temp_buffer; struct ms912x_device *ms912x; size_t transfer_len; size_t alloc_len; @@ -40,7 +41,7 @@ struct ms912x_device { struct drm_connector connector; struct drm_simple_display_pipe display_pipe; - + struct drm_rect update_rect; /* Double buffer to allow memcpy and transfer @@ -48,6 +49,7 @@ struct ms912x_device { */ int current_request; struct ms912x_usb_request requests[2]; + unsigned long last_send_jiffies; }; struct ms912x_request { @@ -116,4 +118,5 @@ int ms912x_fb_send_rect(struct drm_framebuffer *fb, const struct iosys_map *map, void ms912x_free_request(struct ms912x_usb_request *request); int ms912x_init_request(struct ms912x_device *ms912x, struct ms912x_usb_request *request, size_t len); -#endif \ No newline at end of file +void ms912x_init_yuv_lut(void); +#endif diff --git a/ms912x_connector.c b/ms912x_connector.c index 93c3d55..a949342 100644 --- a/ms912x_connector.c +++ b/ms912x_connector.c @@ -1,4 +1,3 @@ - #include #include #include @@ -7,14 +6,13 @@ #include "ms912x.h" -static int ms912x_read_edid(void *data, u8 *buf, unsigned int block, size_t len) +static int ms912x_read_edid_block(struct ms912x_device *ms912x, u8 *buf, + unsigned int offset, size_t len) { - struct ms912x_device *ms912x = data; - int offset = block * EDID_LENGTH; - int i, ret; - for (i = 0; i < len; i++) { - u16 address = 0xc000 + offset + i; - ret = ms912x_read_byte(ms912x, address); + const u16 base = 0xc000 + offset; + for (size_t i = 0; i < len; i++) { + u16 address = base + i; + int ret = ms912x_read_byte(ms912x, address); if (ret < 0) return ret; buf[i] = ret; @@ -22,20 +20,63 @@ static int ms912x_read_edid(void *data, u8 *buf, unsigned int block, size_t len) return 0; } +static int ms912x_read_edid(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct ms912x_device *ms912x = data; + const int offset = block * EDID_LENGTH; + int ret = ms912x_read_edid_block(ms912x, buf, offset, len); + if (ret < 0) + pr_err("ms912x: failed to read EDID block %u\n", block); + return ret; +} + +static void ms912x_add_fallback_mode(struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (!mode) + return; + + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + mode->clock = 65000; + mode->hdisplay = 1024; + mode->hsync_start = 1048; + mode->hsync_end = 1184; + mode->htotal = 1344; + mode->vdisplay = 768; + mode->vsync_start = 771; + mode->vsync_end = 777; + mode->vtotal = 806; + mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC; + + drm_mode_probed_add(connector, mode); +} + static int ms912x_connector_get_modes(struct drm_connector *connector) { - int ret; + int ret = 0; struct ms912x_device *ms912x = to_ms912x(connector->dev); const struct drm_edid *edid; + edid = drm_edid_read_custom(connector, ms912x_read_edid, ms912x); - if (!edid) - return 0; + if (!edid) { + pr_warn("ms912x: EDID not found, falling back to default mode\n"); + ms912x_add_fallback_mode(connector); + return 1; + } + ret = drm_edid_connector_update(connector, edid); if (ret < 0) { + pr_err("ms912x: failed to update EDID connector\n"); ret = 0; goto edid_free; } + ret = drm_edid_connector_add_modes(connector); + edid_free: drm_edid_free(edid); return ret; @@ -47,12 +88,15 @@ static enum drm_connector_status ms912x_detect(struct drm_connector *connector, struct ms912x_device *ms912x = to_ms912x(connector->dev); int status = ms912x_read_byte(ms912x, 0x32); - if (status < 0) + if (status < 0) { + pr_err("ms912x: failed to detect HDMI status\n"); return connector_status_unknown; + } - return status == 1 ? connector_status_connected : - connector_status_disconnected; + return (status == 1) ? connector_status_connected : + connector_status_disconnected; } + static const struct drm_connector_helper_funcs ms912x_connector_helper_funcs = { .get_modes = ms912x_connector_get_modes, }; @@ -69,12 +113,20 @@ static const struct drm_connector_funcs ms912x_connector_funcs = { int ms912x_connector_init(struct ms912x_device *ms912x) { int ret; + drm_connector_helper_add(&ms912x->connector, &ms912x_connector_helper_funcs); + ret = drm_connector_init(&ms912x->drm, &ms912x->connector, &ms912x_connector_funcs, DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + pr_err("ms912x: failed to initialize connector\n"); + return ret; + } + ms912x->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; - return ret; + + return 0; } diff --git a/ms912x_drv.c b/ms912x_drv.c index 7ad2ccc..59160cd 100644 --- a/ms912x_drv.c +++ b/ms912x_drv.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only #include - #include #include #include @@ -17,6 +16,8 @@ #include #include #include +#include +#include #include "ms912x.h" @@ -24,27 +25,19 @@ static int ms912x_usb_suspend(struct usb_interface *interface, pm_message_t message) { struct drm_device *dev = usb_get_intfdata(interface); - return drm_mode_config_helper_suspend(dev); } static int ms912x_usb_resume(struct usb_interface *interface) { struct drm_device *dev = usb_get_intfdata(interface); - return drm_mode_config_helper_resume(dev); } -/* - * FIXME: Dma-buf sharing requires DMA support by the importing device. - * This function is a workaround to make USB devices work as well. - * See todo.rst for how to fix the issue in the dma-buf framework. - */ static struct drm_gem_object * ms912x_driver_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf) { struct ms912x_device *ms912x = to_ms912x(dev); - if (!ms912x->dmadev) return ERR_PTR(-ENODEV); @@ -54,19 +47,17 @@ ms912x_driver_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf) DEFINE_DRM_GEM_FOPS(ms912x_driver_fops); static const struct drm_driver driver = { - .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, - - /* GEM hooks */ + .driver_features = + DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, .fops = &ms912x_driver_fops, DRM_GEM_SHMEM_DRIVER_OPS, .gem_prime_import = ms912x_driver_gem_prime_import, - .name = DRIVER_NAME, .desc = DRIVER_DESC, - .date = DRIVER_DATE, .major = DRIVER_MAJOR, .minor = DRIVER_MINOR, .patchlevel = DRIVER_PATCHLEVEL, + DRM_FBDEV_TTM_DRIVER_OPS, }; static const struct drm_mode_config_funcs ms912x_mode_config_funcs = { @@ -75,7 +66,7 @@ static const struct drm_mode_config_funcs ms912x_mode_config_funcs = { .atomic_commit = drm_atomic_helper_commit, }; -static const struct ms912x_mode ms912x_mode_list[] = { +struct ms912x_mode ms912x_mode_list[] = { /* Found in captures of the Windows driver */ MS912X_MODE( 800, 600, 60, 0x4200, MS912X_PIXFMT_UYVY), MS912X_MODE(1024, 768, 60, 0x4700, MS912X_PIXFMT_UYVY), @@ -110,6 +101,7 @@ ms912x_get_mode(const struct drm_display_mode *mode) int width = mode->hdisplay; int height = mode->vdisplay; int hz = drm_mode_vrefresh(mode); + for (i = 0; i < ARRAY_SIZE(ms912x_mode_list); i++) { if (ms912x_mode_list[i].width == width && ms912x_mode_list[i].height == height && @@ -137,31 +129,50 @@ static void ms912x_pipe_enable(struct drm_simple_display_pipe *pipe, static void ms912x_pipe_disable(struct drm_simple_display_pipe *pipe) { struct ms912x_device *ms912x = to_ms912x(pipe->crtc.dev); - ms912x_power_off(ms912x); } -enum drm_mode_status +static enum drm_mode_status ms912x_pipe_mode_valid(struct drm_simple_display_pipe *pipe, const struct drm_display_mode *mode) { const struct ms912x_mode *ret = ms912x_get_mode(mode); - if (IS_ERR(ret)) { - return MODE_BAD; - } - return MODE_OK; + return IS_ERR(ret) ? MODE_BAD : MODE_OK; } -int ms912x_pipe_check(struct drm_simple_display_pipe *pipe, - struct drm_plane_state *new_plane_state, - struct drm_crtc_state *new_crtc_state) +static int ms912x_pipe_check(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *new_plane_state, + struct drm_crtc_state *new_crtc_state) { return 0; } -static void ms912x_merge_rects(struct drm_rect *dest, struct drm_rect *r1, - struct drm_rect *r2) +#define INVALID_COORD 0x7fffffff + +static void ms912x_update_rect_init(struct drm_rect *rect) +{ + rect->x1 = INVALID_COORD; + rect->y1 = INVALID_COORD; + rect->x2 = 0; + rect->y2 = 0; +} + +static bool ms912x_rect_is_valid(const struct drm_rect *rect) { + return rect->x1 <= rect->x2 && rect->y1 <= rect->y2; +} + +static void ms912x_merge_rects(struct drm_rect *dest, const struct drm_rect *r1, + const struct drm_rect *r2) +{ + if (!ms912x_rect_is_valid(r1)) { + *dest = *r2; + return; + } + if (!ms912x_rect_is_valid(r2)) { + *dest = *r1; + return; + } dest->x1 = min(r1->x1, r2->x1); dest->y1 = min(r1->y1, r2->y1); dest->x2 = max(r1->x2, r2->x2); @@ -174,22 +185,26 @@ static void ms912x_pipe_update(struct drm_simple_display_pipe *pipe, struct drm_plane_state *state = pipe->plane.state; struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state); - struct ms912x_device *ms912x; + + if (!state->fb) + return; + + struct ms912x_device *ms912x = to_ms912x(state->fb->dev); struct drm_rect current_rect, rect; + if (!ms912x_rect_is_valid(&ms912x->update_rect)) + ms912x_update_rect_init(&ms912x->update_rect); + if (drm_atomic_helper_damage_merged(old_state, state, ¤t_rect)) { - /* The device double buffers, so we need to send the update - * rects of the last two frames. - */ - ms912x = to_ms912x(state->fb->dev); ms912x_merge_rects(&rect, ¤t_rect, &ms912x->update_rect); - if (ms912x_fb_send_rect(state->fb, &shadow_plane_state->data[0], - &rect)) { - /* In case of error, merge the rects to update later */ + + int ret = ms912x_fb_send_rect( + state->fb, &shadow_plane_state->data[0], &rect); + if (ret == 0) { + ms912x_update_rect_init(&ms912x->update_rect); + } else { ms912x_merge_rects(&ms912x->update_rect, &ms912x->update_rect, &rect); - } else { - ms912x->update_rect = current_rect; } } } @@ -207,9 +222,18 @@ static const uint32_t ms912x_pipe_formats[] = { DRM_FORMAT_XRGB8888, }; +static DEFINE_MUTEX(yuv_lut_mutex); +static bool yuv_lut_initialized = false; static int ms912x_usb_probe(struct usb_interface *interface, const struct usb_device_id *id) { + mutex_lock(&yuv_lut_mutex); + if (!yuv_lut_initialized) { + ms912x_init_yuv_lut(); + yuv_lut_initialized = true; + } + mutex_unlock(&yuv_lut_mutex); + int ret; struct ms912x_device *ms912x; struct drm_device *dev; @@ -224,8 +248,7 @@ static int ms912x_usb_probe(struct usb_interface *interface, ms912x->dmadev = usb_intf_get_dma_device(interface); if (!ms912x->dmadev) - drm_warn(dev, - "buffer sharing not supported"); /* not an error */ + drm_warn(dev, "buffer sharing not supported"); ret = drmm_mode_config_init(dev); if (ret) @@ -237,7 +260,6 @@ static int ms912x_usb_probe(struct usb_interface *interface, dev->mode_config.max_height = 2048; dev->mode_config.funcs = &ms912x_mode_config_funcs; - /* This stops weird behavior in the device */ ms912x_set_resolution(ms912x, &ms912x_mode_list[0]); ret = ms912x_init_request(ms912x, &ms912x->requests[0], @@ -264,18 +286,17 @@ static int ms912x_usb_probe(struct usb_interface *interface, goto err_free_request_1; drm_plane_enable_fb_damage_clips(&ms912x->display_pipe.plane); - drm_mode_config_reset(dev); - usb_set_intfdata(interface, ms912x); - drm_kms_helper_poll_init(dev); + dev->dev_private = ms912x; + ret = drm_dev_register(dev, 0); if (ret) goto err_free_request_1; - drm_fbdev_ttm_setup(dev, 0); + drm_client_setup(dev, 0); return 0; @@ -305,16 +326,45 @@ static void ms912x_usb_disconnect(struct usb_interface *interface) } 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) }, {}, }; MODULE_DEVICE_TABLE(usb, id_table); +// Forward declaration of the setter function +static int mode_set(const char *val, const struct kernel_param *kp); + +// Parameter operations structure +static const struct kernel_param_ops mode_set_ops = { + .set = mode_set, + .get = NULL, // write-only +}; + +// Module parameter definition +module_param_cb(mode_set, &mode_set_ops, NULL, S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(mode_set, "Sets the mode_num field of ms912x_mode_list[0]"); + +// Setter function for the mode_set parameter +static int mode_set(const char *val, const struct kernel_param *kp) { + int ret; + unsigned int new_value; + + ret = kstrtouint(val, 0, &new_value); + if (ret) { + pr_debug("ms912x: Invalid value for mode_set: %s\n", val); + return ret; + } + + // Update the mode_num field only for the first element + ms912x_mode_list[0].mode = new_value; + + pr_debug("ms912x: ms912x_mode_list[0].mode_num set to 0x%x\n", new_value); + + return 0; +} + static struct usb_driver ms912x_driver = { .name = "ms912x", .probe = ms912x_usb_probe, @@ -325,3 +375,4 @@ static struct usb_driver ms912x_driver = { }; module_usb_driver(ms912x_driver); MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("USB to HDMI driver for ms912x"); diff --git a/ms912x_registers.c b/ms912x_registers.c index 07635c8..487400c 100644 --- a/ms912x_registers.c +++ b/ms912x_registers.c @@ -1,6 +1,4 @@ - #include - #include "ms912x.h" int ms912x_read_byte(struct ms912x_device *ms912x, u16 address) @@ -10,21 +8,24 @@ int ms912x_read_byte(struct ms912x_device *ms912x, u16 address) struct usb_device *usb_dev = interface_to_usbdev(intf); struct ms912x_request *request = kzalloc(8, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->type = 0xb5; request->addr = cpu_to_be16(address); + usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), HID_REQ_SET_REPORT, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0x0300, 0, request, 8, USB_CTRL_SET_TIMEOUT); + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), HID_REQ_GET_REPORT, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0x0300, 0, request, 8, USB_CTRL_GET_TIMEOUT); - if (ret > 0) - ret = request->data[0]; - else if (ret == 0) - ret = -EIO; + ret = (ret > 0) ? request->data[0] : (ret == 0) ? -EIO : ret; + kfree(request); return ret; } @@ -32,60 +33,45 @@ int ms912x_read_byte(struct ms912x_device *ms912x, u16 address) static inline int ms912x_write_6_bytes(struct ms912x_device *ms912x, u16 address, void *data) { - int ret; struct usb_interface *intf = ms912x->intf; struct usb_device *usb_dev = interface_to_usbdev(intf); - struct ms912x_write_request *request = kzalloc(8, GFP_KERNEL); + struct ms912x_write_request *request = + kzalloc(sizeof(struct ms912x_write_request), GFP_KERNEL); + + if (!request) + return -ENOMEM; request->type = 0xa6; request->addr = address; memcpy(request->data, data, 6); - ret = usb_control_msg( + int ret = usb_control_msg( usb_dev, usb_sndctrlpipe(usb_dev, 0), HID_REQ_SET_REPORT, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0x0300, 0, - request, 8, USB_CTRL_SET_TIMEOUT); + request, sizeof(*request), USB_CTRL_SET_TIMEOUT); + kfree(request); return ret; } int ms912x_power_on(struct ms912x_device *ms912x) { - int ret; - u8 data[6]; - memset(data, 0, sizeof(data)); - data[0] = 0x01; - data[1] = 0x02; - ret = ms912x_write_6_bytes(ms912x, 0x07, data); - - return ret; + u8 data[6] = { 0x01, 0x02 }; + return ms912x_write_6_bytes(ms912x, 0x07, data); } int ms912x_power_off(struct ms912x_device *ms912x) { - int ret; - u8 data[6]; - memset(data, 0, sizeof(data)); - ret = ms912x_write_6_bytes(ms912x, 0x07, data); - - return ret; + u8 data[6] = { 0 }; + return ms912x_write_6_bytes(ms912x, 0x07, data); } + int ms912x_set_resolution(struct ms912x_device *ms912x, const struct ms912x_mode *mode) { + u8 data[6] = { 0 }; int ret; - u8 data[6]; - struct ms912x_resolution_request resolution_request; - struct ms912x_mode_request mode_request; - - int width = mode->width; - int height = mode->height; - int pixel_format = mode->pix_fmt; - int mode_num = mode->mode; - - /* ??? Unknown */ - memset(data, 0, sizeof(data)); - data[0] = 0; + ret = ms912x_write_6_bytes(ms912x, 0x04, data); if (ret < 0) return ret; @@ -94,42 +80,35 @@ int ms912x_set_resolution(struct ms912x_device *ms912x, ms912x_read_byte(ms912x, 0x33); ms912x_read_byte(ms912x, 0xc620); - /* ??? Unknown */ - memset(data, 0, sizeof(data)); data[0] = 0x03; ret = ms912x_write_6_bytes(ms912x, 0x03, data); if (ret < 0) return ret; - /* Write resolution */ - resolution_request.width = cpu_to_be16(width); - resolution_request.height = cpu_to_be16(height); - resolution_request.pixel_format = cpu_to_be16(pixel_format); + struct ms912x_resolution_request resolution_request = { + .width = cpu_to_be16(mode->width), + .height = cpu_to_be16(mode->height), + .pixel_format = cpu_to_be16(mode->pix_fmt) + }; ret = ms912x_write_6_bytes(ms912x, 0x01, &resolution_request); if (ret < 0) return ret; - /* Write mode */ - mode_request.mode = cpu_to_be16(mode_num); - mode_request.width = cpu_to_be16(width); - mode_request.height = cpu_to_be16(height); + struct ms912x_mode_request mode_request = { + .mode = cpu_to_be16(mode->mode), + .width = cpu_to_be16(mode->width), + .height = cpu_to_be16(mode->height) + }; ret = ms912x_write_6_bytes(ms912x, 0x02, &mode_request); if (ret < 0) return ret; - /* ??? Unknown */ - memset(data, 0, sizeof(data)); data[0] = 1; ret = ms912x_write_6_bytes(ms912x, 0x04, data); if (ret < 0) return ret; - /* ??? Unknown */ - memset(data, 0, sizeof(data)); - data[0] = 1; + /* Same data reused here */ ret = ms912x_write_6_bytes(ms912x, 0x05, data); - if (ret < 0) - return ret; - - return 0; -} \ No newline at end of file + return ret; +} diff --git a/ms912x_transfer.c b/ms912x_transfer.c index f14b064..c209565 100644 --- a/ms912x_transfer.c +++ b/ms912x_transfer.c @@ -1,15 +1,18 @@ - #include #include #include #include +#include #include "ms912x.h" +#define MS912X_REQUEST_TYPE 0xb5 +#define MS912X_WRITE_TYPE 0xa6 + static void ms912x_request_timeout(struct timer_list *t) { - struct ms912x_usb_request *request = from_timer(request, t, timer); + struct ms912x_usb_request *request = timer_container_of(request, t, timer); usb_sg_cancel(&request->sgr); } @@ -28,17 +31,20 @@ static void ms912x_request_work(struct work_struct *work) request->transfer_len, GFP_KERNEL); mod_timer(&request->timer, jiffies + msecs_to_jiffies(5000)); usb_sg_wait(sgr); - del_timer_sync(&request->timer); + timer_delete_sync(&request->timer); complete(&request->done); } void ms912x_free_request(struct ms912x_usb_request *request) { - if (!request->transfer_buffer) - return; - sg_free_table(&request->transfer_sgt); - vfree(request->transfer_buffer); - request->transfer_buffer = NULL; + if (request->transfer_buffer) { + sg_free_table(&request->transfer_sgt); + vfree(request->transfer_buffer); + request->transfer_buffer = NULL; + } + + kfree(request->temp_buffer); + request->temp_buffer = NULL; request->alloc_len = 0; } @@ -55,6 +61,12 @@ int ms912x_init_request(struct ms912x_device *ms912x, if (!data) return -ENOMEM; + request->temp_buffer = kmalloc(1920 * 4, GFP_KERNEL); + if (!request->temp_buffer) { + vfree(data); + return -ENOMEM; + } + num_pages = DIV_ROUND_UP(len, PAGE_SIZE); pages = kmalloc_array(num_pages, sizeof(struct page *), GFP_KERNEL); if (!pages) { @@ -64,6 +76,7 @@ int ms912x_init_request(struct ms912x_device *ms912x, 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); @@ -77,28 +90,50 @@ int ms912x_init_request(struct ms912x_device *ms912x, init_completion(&request->done); INIT_WORK(&request->work, ms912x_request_work); return 0; + err_vfree: vfree(data); return ret; } -static inline unsigned int ms912x_rgb_to_y(unsigned int r, unsigned int g, - unsigned int b) +struct ms912x_yuv_lut { + u16 y_r[256], y_g[256], y_b[256]; + u16 u_r[256], u_g[256], u_b[256]; + u16 v_r[256], v_g[256], v_b[256]; +}; + +static struct ms912x_yuv_lut yuv_lut; + +void ms912x_init_yuv_lut(void) { - const unsigned int luma = (16 << 16) + 16763 * r + 32904 * g + 6391 * b; - return luma >> 16; + for (int i = 0; i < 256; i++) { + yuv_lut.y_r[i] = (16763 * i) >> 16; + yuv_lut.y_g[i] = (32904 * i) >> 16; + yuv_lut.y_b[i] = (6391 * i) >> 16; + + yuv_lut.u_r[i] = (-9676 * i) >> 16; + yuv_lut.u_g[i] = (-18996 * i) >> 16; + yuv_lut.u_b[i] = (28672 * i) >> 16; + + yuv_lut.v_r[i] = (28672 * i) >> 16; + yuv_lut.v_g[i] = (-24009 * i) >> 16; + yuv_lut.v_b[i] = (-4663 * i) >> 16; + } } -static inline unsigned int ms912x_rgb_to_u(unsigned int r, unsigned int g, - unsigned int b) + +static inline unsigned int ms912x_rgb_to_y(u8 r, u8 g, u8 b) { - const unsigned int u = (128 << 16) - 9676 * r - 18996 * g + 28672 * b; - return u >> 16; + return 16 + yuv_lut.y_r[r] + yuv_lut.y_g[g] + yuv_lut.y_b[b]; } -static inline unsigned int ms912x_rgb_to_v(unsigned int r, unsigned int g, - unsigned int b) + +static inline unsigned int ms912x_rgb_to_u(u8 r, u8 g, u8 b) { - const unsigned int v = (128 << 16) + 28672 * r - 24009 * g - 4663 * b; - return v >> 16; + return 128 + yuv_lut.u_r[r] + yuv_lut.u_g[g] + yuv_lut.u_b[b]; +} + +static inline unsigned int ms912x_rgb_to_v(u8 r, u8 g, u8 b) +{ + return 128 + yuv_lut.v_r[r] + yuv_lut.v_g[g] + yuv_lut.v_b[b]; } static int ms912x_xrgb_to_yuv422_line(u8 *transfer_buffer, @@ -110,6 +145,7 @@ static int ms912x_xrgb_to_yuv422_line(u8 *transfer_buffer, unsigned int pixel1, pixel2; unsigned int r1, g1, b1, r2, g2, b2; unsigned int v, y1, u, y2; + unsigned int avg_r, avg_g, avg_b; iosys_map_memcpy_from(temp_buffer, xrgb_buffer, offset, width * 4); for (i = 0; i < width; i += 2) { pixel1 = temp_buffer[i]; @@ -125,12 +161,12 @@ static int ms912x_xrgb_to_yuv422_line(u8 *transfer_buffer, y1 = ms912x_rgb_to_y(r1, g1, b1); y2 = ms912x_rgb_to_y(r2, g2, b2); - v = (ms912x_rgb_to_v(r1, g1, b1) + - ms912x_rgb_to_v(r2, g2, b2)) / - 2; - u = (ms912x_rgb_to_u(r1, g1, b1) + - ms912x_rgb_to_u(r2, g2, b2)) / - 2; + avg_r = (r1 + r2) >> 1; + avg_g = (g1 + g2) >> 1; + avg_b = (b1 + b2) >> 1; + + v = ms912x_rgb_to_v(avg_r, avg_g, avg_b); + u = ms912x_rgb_to_u(avg_r, avg_g, avg_b); transfer_buffer[dst_offset++] = u; transfer_buffer[dst_offset++] = y1; @@ -145,23 +181,19 @@ static const u8 ms912x_end_of_buffer[8] = { 0xff, 0xc0, 0x00, 0x00, static int ms912x_fb_xrgb8888_to_yuv422(void *dst, const struct iosys_map *src, struct drm_framebuffer *fb, - struct drm_rect *rect) + struct drm_rect *rect, + void *temp_buffer) { struct ms912x_frame_update_header *header = (struct ms912x_frame_update_header *)dst; struct iosys_map fb_map; int i, x, y1, y2, width; - void *temp_buffer; y1 = rect->y1; - y2 = min((unsigned int)rect->y2, fb->height); + y2 = (rect->y2 < fb->height) ? rect->y2 : fb->height; x = rect->x1; width = drm_rect_width(rect); - temp_buffer = kmalloc(width * 4, GFP_KERNEL); - if (!temp_buffer) - return -ENOMEM; - header->header = cpu_to_be16(0xff00); header->x = x / 16; header->y = cpu_to_be16(y1); @@ -171,25 +203,30 @@ static int ms912x_fb_xrgb8888_to_yuv422(void *dst, const struct iosys_map *src, fb_map = IOSYS_MAP_INIT_OFFSET(src, y1 * fb->pitches[0]); for (i = y1; i < y2; i++) { - ms912x_xrgb_to_yuv422_line(dst, &fb_map, x * 4, width, temp_buffer); + ms912x_xrgb_to_yuv422_line(dst, &fb_map, x * 4, width, + temp_buffer); iosys_map_incr(&fb_map, fb->pitches[0]); dst += width * 2; } - kfree(temp_buffer); memcpy(dst, ms912x_end_of_buffer, sizeof(ms912x_end_of_buffer)); return 0; } + int ms912x_fb_send_rect(struct drm_framebuffer *fb, const struct iosys_map *map, struct drm_rect *rect) { - int ret = 0, idx; struct ms912x_device *ms912x = to_ms912x(fb->dev); + unsigned long now = jiffies; + if (time_before(now, ms912x->last_send_jiffies + msecs_to_jiffies(16))) + return 0; + + int ret = 0, idx; struct drm_device *drm = &ms912x->drm; struct ms912x_usb_request *prev_request, *current_request; int x, width; - + /* Seems like hardware can only update framebuffer * in multiples of 16 horizontally */ @@ -200,6 +237,7 @@ int ms912x_fb_send_rect(struct drm_framebuffer *fb, const struct iosys_map *map, width = min(ALIGN(rect->x2, 16), ALIGN_DOWN((int)fb->width, 16)) - x; rect->x1 = x; rect->x2 = x + width; + current_request = &ms912x->requests[ms912x->current_request]; prev_request = &ms912x->requests[1 - ms912x->current_request]; @@ -210,16 +248,16 @@ int ms912x_fb_send_rect(struct drm_framebuffer *fb, const struct iosys_map *map, goto dev_exit; ret = ms912x_fb_xrgb8888_to_yuv422(current_request->transfer_buffer, - map, fb, rect); - + map, fb, rect, + current_request->temp_buffer); + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); if (ret < 0) goto dev_exit; /* Sending frames too fast, drop it */ if (!wait_for_completion_timeout(&prev_request->done, - msecs_to_jiffies(10))) { - + msecs_to_jiffies(1))) { ret = -ETIMEDOUT; goto dev_exit; } @@ -227,6 +265,8 @@ int ms912x_fb_send_rect(struct drm_framebuffer *fb, const struct iosys_map *map, current_request->transfer_len = width * 2 * drm_rect_height(rect) + 16; queue_work(system_long_wq, ¤t_request->work); ms912x->current_request = 1 - ms912x->current_request; + ms912x->last_send_jiffies = jiffies; + dev_exit: drm_dev_exit(idx); return ret;