Add HID register and flash access, decode custom device timings, and enumerate connector modes for MacroSilicon MS912x/MS913x adapters.
Signed-off-by: Ho Jie Feng <[email protected]> --- drivers/gpu/drm/ms91xx/ms91xx.h | 221 ++++++++++++ drivers/gpu/drm/ms91xx/ms91xx_connector.c | 267 +++++++++++++++ drivers/gpu/drm/ms91xx/ms91xx_registers.c | 393 ++++++++++++++++++++++ 3 files changed, 881 insertions(+) create mode 100644 drivers/gpu/drm/ms91xx/ms91xx.h create mode 100644 drivers/gpu/drm/ms91xx/ms91xx_connector.c create mode 100644 drivers/gpu/drm/ms91xx/ms91xx_registers.c diff --git a/drivers/gpu/drm/ms91xx/ms91xx.h b/drivers/gpu/drm/ms91xx/ms91xx.h new file mode 100644 index 000000000..6e8c4d64a --- /dev/null +++ b/drivers/gpu/drm/ms91xx/ms91xx.h @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2022-2026 Ho Jie Feng <[email protected]> + * Copyright (C) 2023 MacroSilicon Technology Co., Ltd. + */ + +#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_connector.c b/drivers/gpu/drm/ms91xx/ms91xx_connector.c new file mode 100644 index 000000000..08371d121 --- /dev/null +++ b/drivers/gpu/drm/ms91xx/ms91xx_connector.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022-2026 Ho Jie Feng <[email protected]> + */ + +#include <linux/array_size.h> +#include <linux/types.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_edid.h> +#include <drm/drm_encoder.h> +#include <drm/drm_mode_config.h> +#include <drm/drm_modes.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_print.h> + +#include "ms91xx.h" + +static int ms91xx_get_connector_type(struct ms91xx_device *ms91xx) +{ + int port_type; + + port_type = ms91xx_read_byte(ms91xx, MS91XX_REG_VIDEO_PORT); + if (port_type < 0) { + drm_warn(&ms91xx->drm, + "failed to read video port type: %d\n", port_type); + ms91xx->port_type = MS91XX_VIDEO_PORT_UNKNOWN; + return DRM_MODE_CONNECTOR_Unknown; + } + + ms91xx->port_type = port_type; + + switch (ms91xx->port_type) { + case MS91XX_VIDEO_PORT_CVBS: + return DRM_MODE_CONNECTOR_Composite; + case MS91XX_VIDEO_PORT_VGA: + return DRM_MODE_CONNECTOR_VGA; + case MS91XX_VIDEO_PORT_YPBPR: + return DRM_MODE_CONNECTOR_Component; + /* + * Combo VGA+HDMI device seems to report this as connector type + * Since there doesn't seem to be any S-Video devices out there, + * set this to HDMI instead. + */ + case MS91XX_VIDEO_PORT_SVIDEO: + case MS91XX_VIDEO_PORT_HDMI: + return DRM_MODE_CONNECTOR_HDMIA; + case MS91XX_VIDEO_PORT_DIGITAL: + return DRM_MODE_CONNECTOR_DPI; + case MS91XX_VIDEO_PORT_CVBS_SVIDEO: + return DRM_MODE_CONNECTOR_9PinDIN; + default: + drm_warn(&ms91xx->drm, + "unknown video port type: %d\n", port_type); + ms91xx->port_type = MS91XX_VIDEO_PORT_UNKNOWN; + return DRM_MODE_CONNECTOR_Unknown; + } +} + +static int +ms91xx_get_encoder_type(enum ms91xx_video_port port_type) +{ + switch (port_type) { + case MS91XX_VIDEO_PORT_VGA: + return DRM_MODE_ENCODER_DAC; + case MS91XX_VIDEO_PORT_CVBS: + case MS91XX_VIDEO_PORT_YPBPR: + case MS91XX_VIDEO_PORT_CVBS_SVIDEO: + return DRM_MODE_ENCODER_TVDAC; + /* See above comment */ + case MS91XX_VIDEO_PORT_SVIDEO: + case MS91XX_VIDEO_PORT_HDMI: + return DRM_MODE_ENCODER_TMDS; + case MS91XX_VIDEO_PORT_DIGITAL: + return DRM_MODE_ENCODER_DPI; + case MS91XX_VIDEO_PORT_UNKNOWN: + default: + return DRM_MODE_ENCODER_NONE; + } +} + +static int ms91xx_read_edid(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct ms91xx_device *ms91xx = data; + int offset = block * EDID_LENGTH; + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + u16 address = MS91XX_REG_EDID_BASE + offset + i; + + ret = ms91xx_read_byte(ms91xx, address); + if (ret < 0) + return ret; + buf[i] = ret; + } + return 0; +} + +static int ms91xx_add_cea_modes(struct drm_connector *connector, + const u8 *vics, size_t num_vics) +{ + struct drm_display_mode *mode; + unsigned int i; + int count = 0; + + drm_edid_connector_update(connector, NULL); + + for (i = 0; i < num_vics; i++) { + mode = drm_display_mode_from_cea_vic(connector->dev, vics[i]); + if (!mode) + continue; + + if (!count) + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + count++; + } + + return count; +} + +static int ms91xx_add_cvbs_svideo_modes(struct drm_connector *connector) +{ + static const u8 vics[] = { + 2, /* 720x480p60 */ + 17, /* 720x576p50 */ + }; + + return ms91xx_add_cea_modes(connector, vics, ARRAY_SIZE(vics)); +} + +static int ms91xx_add_ypbpr_modes(struct drm_connector *connector) +{ + static const u8 vics[] = { + 4, /* 1280x720p60 */ + 16, /* 1920x1080p60 */ + 2, /* 720x480p60 */ + 17, /* 720x576p50 */ + }; + + return ms91xx_add_cea_modes(connector, vics, ARRAY_SIZE(vics)); +} + +static int +ms91xx_add_default_hdmi_vga_modes(struct drm_connector *connector) +{ + struct drm_mode_config *mode_config = &connector->dev->mode_config; + int count; + + drm_edid_connector_update(connector, NULL); + count = drm_add_modes_noedid(connector, mode_config->max_width, + mode_config->max_height); + if (count) + drm_set_preferred_mode(connector, 1024, 768); + + return count; +} + +static int ms91xx_add_custom_mode(struct drm_connector *connector) +{ + struct ms91xx_device *ms91xx = to_ms91xx(connector->dev); + struct drm_display_mode *mode; + unsigned int i; + int count = 0; + + for (i = 0; i < ms91xx->num_custom_modes; i++) { + mode = drm_mode_duplicate( + connector->dev, + &ms91xx->custom_modes[i].display_mode); + if (!mode) + continue; + + drm_mode_probed_add(connector, mode); + count++; + } + + return count; +} + +static int ms91xx_connector_get_modes(struct drm_connector *connector) +{ + struct ms91xx_device *ms91xx = to_ms91xx(connector->dev); + const struct drm_edid *edid; + int ret = ms91xx_add_custom_mode(connector); + + if (ms91xx->port_type == MS91XX_VIDEO_PORT_CVBS || + ms91xx->port_type == MS91XX_VIDEO_PORT_CVBS_SVIDEO || + ms91xx->port_type == MS91XX_VIDEO_PORT_UNKNOWN) + return ret + ms91xx_add_cvbs_svideo_modes(connector); + + if (ms91xx->port_type == MS91XX_VIDEO_PORT_YPBPR) + return ret + ms91xx_add_ypbpr_modes(connector); + + edid = drm_edid_read_custom(connector, ms91xx_read_edid, ms91xx); + if (!edid) + return ret + ms91xx_add_default_hdmi_vga_modes(connector); + + if (drm_edid_connector_update(connector, edid) >= 0) + ret += drm_edid_connector_add_modes(connector); + drm_edid_free(edid); + return ret; +} + +static enum drm_connector_status ms91xx_detect(struct drm_connector *connector, + bool force) +{ + struct ms91xx_device *ms91xx = to_ms91xx(connector->dev); + int status = ms91xx_read_byte(ms91xx, MS91XX_REG_DISPLAY_STATUS); + + if (status < 0) + return connector_status_unknown; + + return status == 1 ? connector_status_connected : + connector_status_disconnected; +} + +static const struct drm_encoder_funcs ms91xx_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static const struct drm_connector_helper_funcs ms91xx_connector_helper_funcs = { + .get_modes = ms91xx_connector_get_modes, +}; + +static const struct drm_connector_funcs ms91xx_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .detect = ms91xx_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, +}; + +int ms91xx_connector_init(struct ms91xx_device *ms91xx) +{ + int ret; + int connector_type; + int encoder_type; + + connector_type = ms91xx_get_connector_type(ms91xx); + encoder_type = ms91xx_get_encoder_type(ms91xx->port_type); + + ret = drm_encoder_init(&ms91xx->drm, &ms91xx->encoder, + &ms91xx_encoder_funcs, encoder_type, NULL); + if (ret) + return ret; + ms91xx->encoder.possible_crtcs = drm_crtc_mask(&ms91xx->crtc); + + drm_connector_helper_add(&ms91xx->connector, + &ms91xx_connector_helper_funcs); + ret = drm_connector_init(&ms91xx->drm, &ms91xx->connector, + &ms91xx_connector_funcs, connector_type); + if (ret) + return ret; + + ms91xx->connector.polled = + DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + ms91xx->connector.interlace_allowed = true; + + return drm_connector_attach_encoder(&ms91xx->connector, + &ms91xx->encoder); +} diff --git a/drivers/gpu/drm/ms91xx/ms91xx_registers.c b/drivers/gpu/drm/ms91xx/ms91xx_registers.c new file mode 100644 index 000000000..2a45ebc86 --- /dev/null +++ b/drivers/gpu/drm/ms91xx/ms91xx_registers.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022-2026 Ho Jie Feng <[email protected]> + * Copyright (C) 2023 MacroSilicon Technology Co., Ltd. + */ + +#include <asm/byteorder.h> +#include <linux/errno.h> +#include <linux/gfp.h> +#include <linux/hid.h> +#include <linux/math.h> +#include <linux/minmax.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/usb.h> + +#include <drm/drm_drv.h> +#include <drm/drm_modes.h> + +#include "ms91xx.h" + +int ms91xx_read_byte(struct ms91xx_device *ms91xx, u16 address) +{ + struct ms91xx_request request; + struct usb_device *usb_dev; + int idx, ret; + + if (!drm_dev_enter(&ms91xx->drm, &idx)) + return -ENODEV; + + mutex_lock(&ms91xx->ctrl_lock); + + usb_dev = interface_to_usbdev(ms91xx->intf); + memset(&request, 0, sizeof(request)); + request.type = MS91XX_REQ_TYPE_READ_BYTE; + request.addr = cpu_to_be16(address); + + ret = usb_control_msg_send(usb_dev, 0, HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + 0x0300, 0, &request, sizeof(request), + USB_CTRL_SET_TIMEOUT, GFP_KERNEL); + if (ret) + goto ctrl_unlock; + + ret = usb_control_msg_recv(usb_dev, 0, HID_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + 0x0300, 0, &request, sizeof(request), + USB_CTRL_GET_TIMEOUT, GFP_KERNEL); + if (!ret) + ret = request.data[0]; + +ctrl_unlock: + mutex_unlock(&ms91xx->ctrl_lock); + drm_dev_exit(idx); + return ret; +} + +static int ms91xx_read_flash(struct ms91xx_device *ms91xx, u32 address, + void *data, size_t len) +{ + struct ms91xx_flash_read_request request; + struct usb_device *usb_dev; + size_t offset = 0; + u8 *dst = data; + int idx, ret = 0; + + if (address > 0xffffff || len > 0x1000000 - address) + return -ERANGE; + + if (!drm_dev_enter(&ms91xx->drm, &idx)) + return -ENODEV; + + mutex_lock(&ms91xx->ctrl_lock); + usb_dev = interface_to_usbdev(ms91xx->intf); + + while (offset < len) { + size_t chunk = min_t(size_t, len - offset, sizeof(request)); + u32 current_address = address + offset; + + memset(&request, 0, sizeof(request)); + request.type = MS91XX_REQ_TYPE_READ_FLASH; + request.addr[0] = current_address >> 16; + request.addr[1] = current_address >> 8; + request.addr[2] = current_address; + + ret = usb_control_msg_send(usb_dev, 0, HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + 0x0300, 0, &request, + sizeof(request), + USB_CTRL_SET_TIMEOUT, GFP_KERNEL); + if (ret) + goto ctrl_unlock; + + ret = usb_control_msg_recv(usb_dev, 0, HID_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + 0x0300, 0, &request, + sizeof(request), + USB_CTRL_GET_TIMEOUT, GFP_KERNEL); + if (ret) + goto ctrl_unlock; + + memcpy(dst + offset, &request, chunk); + offset += chunk; + } + +ctrl_unlock: + mutex_unlock(&ms91xx->ctrl_lock); + drm_dev_exit(idx); + return ret; +} + +static int ms91xx_custom_timing_base(struct ms91xx_device *ms91xx) +{ + u8 signature[3]; + int ret; + + /* Read chip signature */ + ret = ms91xx_read_byte(ms91xx, MS913X_REG_CHIP_ID); + if (ret < 0) + return ret; + signature[0] = ret; + + ret = ms91xx_read_byte(ms91xx, MS913X_REG_CHIP_ID + 1); + if (ret < 0) + return ret; + signature[1] = ret; + + ret = ms91xx_read_byte(ms91xx, MS913X_REG_CHIP_ID + 2); + if (ret < 0) + return ret; + signature[2] = ret; + + if (signature[1] == MS913X_CHIP_ID_SIGNATURE_MSB && + signature[2] == MS91XX_CHIP_ID_SIGNATURE_LSB) + return MS913X_CUSTOM_TIMING_BASE; + + ret = ms91xx_read_byte(ms91xx, MS912X_REG_CHIP_ID); + if (ret < 0) + return ret; + signature[0] = ret; + + ret = ms91xx_read_byte(ms91xx, MS912X_REG_CHIP_ID + 1); + if (ret < 0) + return ret; + signature[1] = ret; + + ret = ms91xx_read_byte(ms91xx, MS912X_REG_CHIP_ID + 2); + if (ret < 0) + return ret; + signature[2] = ret; + + if (signature[1] == MS912X_CHIP_ID_SIGNATURE_MSB && + signature[2] == MS91XX_CHIP_ID_SIGNATURE_LSB) + return MS912X_CUSTOM_TIMING_BASE; + + return -ENODEV; +} + +static int +ms91xx_decode_custom_timing(struct ms91xx_custom_mode *custom_mode, + const struct ms91xx_custom_timing_record *record) +{ + struct drm_display_mode *display_mode = &custom_mode->display_mode; + u16 hsyncwidth = le16_to_cpu(record->hsyncwidth); + u16 vsyncwidth = le16_to_cpu(record->vsyncwidth); + u16 hoffset = le16_to_cpu(record->hoffset); + u16 voffset = le16_to_cpu(record->voffset); + u16 hactive = le16_to_cpu(record->hactive); + u16 vactive = le16_to_cpu(record->vactive); + u16 htotal = le16_to_cpu(record->htotal); + u16 vtotal = le16_to_cpu(record->vtotal); + u16 pixclk = le16_to_cpu(record->pixclk); + u16 vfreq = le16_to_cpu(record->vfreq); + unsigned int hblank, vblank; + int refresh; + + if (!hactive || hactive > MS91XX_MAX_WIDTH || (hactive & 1) || + !vactive || vactive > MS91XX_MAX_HEIGHT || + htotal <= hactive || vtotal <= vactive || !pixclk || !vfreq) + return -EINVAL; + + hblank = htotal - hactive; + vblank = vtotal - vactive; + if (!hsyncwidth || hsyncwidth > hoffset || hoffset > hblank || + !vsyncwidth || vsyncwidth > voffset || voffset > vblank) + return -EINVAL; + + memset(display_mode, 0, sizeof(*display_mode)); + display_mode->clock = pixclk * 10; + display_mode->hdisplay = hactive; + display_mode->hsync_start = htotal - hoffset; + display_mode->hsync_end = display_mode->hsync_start + hsyncwidth; + display_mode->htotal = htotal; + display_mode->vdisplay = vactive; + display_mode->vsync_start = vtotal - voffset; + display_mode->vsync_end = display_mode->vsync_start + vsyncwidth; + display_mode->vtotal = vtotal; + display_mode->type = DRM_MODE_TYPE_DRIVER; + display_mode->flags = + record->polarity & MS91XX_TIMING_POSITIVE_HSYNC ? + DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC; + display_mode->flags |= + record->polarity & MS91XX_TIMING_POSITIVE_VSYNC ? + DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC; + if (!(record->polarity & MS91XX_TIMING_PROGRESSIVE)) + display_mode->flags |= DRM_MODE_FLAG_INTERLACE; + + refresh = DIV_ROUND_CLOSEST(vfreq, 100); + if (!refresh || drm_mode_vrefresh(display_mode) != refresh) + return -EINVAL; + + drm_mode_set_name(display_mode); + custom_mode->mode.width = hactive; + custom_mode->mode.height = vactive; + custom_mode->mode.hz = refresh; + custom_mode->mode.mode = record->vic; + + return 0; +} + +int ms91xx_read_custom_timing(struct ms91xx_device *ms91xx) +{ + struct ms91xx_custom_timing_record record; + unsigned int num_records, i; + int error = 0; + u8 marker[7]; + int base, ret; + + ms91xx->num_custom_modes = 0; + + base = ms91xx_custom_timing_base(ms91xx); + if (base < 0) + return base; + + ret = ms91xx_read_flash(ms91xx, base, marker, sizeof(marker)); + if (ret) + return ret; + + if (!memcmp(marker, "modify1", sizeof(marker))) + num_records = 1; + else if (!memcmp(marker, "modify2", sizeof(marker))) + num_records = 2; + else + return 0; + + for (i = 0; i < num_records; i++) { + ret = ms91xx_read_flash(ms91xx, + base + MS91XX_CUSTOM_TIMING_OFFSET + + i * MS91XX_CUSTOM_TIMING_STRIDE, + &record, sizeof(record)); + if (ret) { + error = ret; + continue; + } + + ret = ms91xx_decode_custom_timing( + &ms91xx->custom_modes[ms91xx->num_custom_modes], + &record); + if (ret) { + error = ret; + continue; + } + + ms91xx->num_custom_modes++; + } + + if (ms91xx->num_custom_modes) + ms91xx->custom_modes[0].display_mode.type |= DRM_MODE_TYPE_PREFERRED; + + return ms91xx->num_custom_modes ? ms91xx->num_custom_modes : error; +} + +static int ms91xx_write_6_bytes(struct ms91xx_device *ms91xx, + u8 address, const void *data) +{ + struct ms91xx_write_request request; + struct usb_device *usb_dev; + int idx, ret; + + if (!drm_dev_enter(&ms91xx->drm, &idx)) + return -ENODEV; + + mutex_lock(&ms91xx->ctrl_lock); + + usb_dev = interface_to_usbdev(ms91xx->intf); + request.type = MS91XX_REQ_TYPE_WRITE_6_BYTES; + request.addr = address; + memcpy(request.data, data, sizeof(request.data)); + + ret = usb_control_msg_send(usb_dev, 0, HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + 0x0300, 0, &request, sizeof(request), + USB_CTRL_SET_TIMEOUT, GFP_KERNEL); + mutex_unlock(&ms91xx->ctrl_lock); + drm_dev_exit(idx); + + return ret; +} + +int ms91xx_power_on(struct ms91xx_device *ms91xx) +{ + u8 data[6]; + + memset(data, 0, sizeof(data)); + data[0] = 0x01; + data[1] = 0x02; + + return ms91xx_write_6_bytes(ms91xx, MS91XX_CMD_POWER, data); +} + +int ms91xx_power_off(struct ms91xx_device *ms91xx) +{ + u8 data[6]; + + memset(data, 0, sizeof(data)); + + return ms91xx_write_6_bytes(ms91xx, MS91XX_CMD_POWER, data); +} + +int ms91xx_set_resolution(struct ms91xx_device *ms91xx, + const struct ms91xx_mode *mode) +{ + int ret; + u8 data[6]; + struct ms91xx_resolution_request resolution_request; + struct ms91xx_mode_request mode_request; + + int width = mode->width; + int height = mode->height; + int mode_num = mode->mode; + + /* Sequence obtained from Windows USB captures */ + memset(data, 0, sizeof(data)); + data[0] = 0; + ret = ms91xx_write_6_bytes(ms91xx, MS91XX_CMD_UNKNOWN2, data); + if (ret < 0) + return ret; + + ret = ms91xx_read_byte(ms91xx, MS91XX_REG_MODE_SEQUENCE_0); + if (ret < 0) + return ret; + ret = ms91xx_read_byte(ms91xx, MS91XX_REG_MODE_SEQUENCE_1); + if (ret < 0) + return ret; + ret = ms91xx_read_byte(ms91xx, MS91XX_REG_MODE_SEQUENCE_2); + if (ret < 0) + return ret; + + memset(data, 0, sizeof(data)); + data[0] = 0x03; + ret = ms91xx_write_6_bytes(ms91xx, MS91XX_CMD_UNKNOWN1, 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 = MS91XX_PIXFMT_UYVY; + resolution_request.byte_select = MS91XX_BYTE_SELECT_UYVY; + ret = ms91xx_write_6_bytes(ms91xx, MS91XX_CMD_RESOLUTION, + &resolution_request); + if (ret < 0) + return ret; + + /* Write mode */ + mode_request.mode = mode_num; + mode_request.pixel_format = 0x01; + mode_request.width = cpu_to_be16(width); + mode_request.height = cpu_to_be16(height); + ret = ms91xx_write_6_bytes(ms91xx, MS91XX_CMD_MODE, &mode_request); + if (ret < 0) + return ret; + + memset(data, 0, sizeof(data)); + data[0] = 1; + ret = ms91xx_write_6_bytes(ms91xx, MS91XX_CMD_UNKNOWN2, data); + if (ret < 0) + return ret; + + memset(data, 0, sizeof(data)); + data[0] = 1; + ret = ms91xx_write_6_bytes(ms91xx, MS91XX_CMD_OUTPUT_ENABLE, data); + if (ret < 0) + return ret; + + return 0; +} -- 2.55.0
