From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.215.179 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792424925; b=i3YT+WvE5mPNCjwuJqeXFH3RXJDhwVcUz7vv4JpEhxEVkRyUtV6GKnkImZ36ID5PzfTj vkDsk15TRHxe269myxvXzvaIK+9f4IeoyJabZu4WXgHVKi3002jFxAe4gMC47I9cQsdlF VmVWPIhJfyjdoX5rPe3qDZdacJibTwbl8FjK2huFEdmx9+JqsLnOP7ODEwuxEcQpX8XwI JBodZk+Iil7qkUnV9wpp3kHbfJAA21WZzJrGs0fF6WcWGIlVbG14J5ViZoYvADICQ9km0 J9+Z6jNA1YlSpOAfFnOOrzuHwYSzY5hVclULahBlKYgBVzvbpwSySCGUZr16xusgxjg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792424925; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:To:Cc:Subject:Date:Message-ID:X-Mailer:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding; bh=q1Ii+kS6GcqCuy8FpwraDGcd1HAWUkHGu9ihz0l8o30=; b=LlgZiZ0/GMO5kRbAvDoZtwnh7lYOHhrssMnp6OLl7jPVBHQIAyQkgIAacBgI5tzIh7Ut tc8hrFoWzul/wFznx/r0oWFdjWE9c52hATh/NZnNHAwiXyzuzgzUJxGsvPw1xvUO0HW6O nRaW6wfW5GZSl3u8btaODut3cDio/tqvlpTR9XSiu35sssjpkKwO0G/KAt+cZvTJmuSug d2xSMI9ZZINkssoU84lCAAon4zvVx9EVDTxNz2ChyZBJAKMouCpbPo8harnOLx3/ds0pv 6w/CwhKjgk+heYIKygaPE9uCC+3AVFdhUxTT4jxXOyLT6chBqZx+1HquB/hF2tg3ibA== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b="Kjhs/VzU"; arc=none smtp.remote-ip=209.85.215.179 Received: from mail-pg1-f179.google.com (mail-pg1-f179.google.com [209.85.215.179]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 23AB2608606E for ; Mon, 19 Oct 2026 15:48:45 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 23AB2608606E Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=Kjhs/VzU Received: by mail-pg1-f179.google.com with SMTP id 41be03b00d2f7-cc758830601so1560993a12.2 for ; Mon, 19 Oct 2026 08:48:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792424924; x=1793029724; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=q1Ii+kS6GcqCuy8FpwraDGcd1HAWUkHGu9ihz0l8o30=; b=Kjhs/VzUR627vKipidBRlS/ACkXpqKqZtjqNN2+lRBjMJgRETIX6R3sZ+qtcbvXM3B y7NZsCqG0Etj5tdkoRtN0NdEAUiKU2qjOfDiPp5Co1hHRxHeJFUszwfpoC/l/9wyXy+K 3kbv0UmdjztI/JjUVHc4XlEa3qMeEYrnu26XR56w0DiEQx84kcfvydPYP9NlrkMJ9Gpl aQvHXSugoavJ5qGqX9qK3av8Th23KdwYWKOTjTSO7QvWKbUIzDKGiz/QShGwQiQlS3zO 4Nn7VYqvS9SupMwGNlawpdi+jD/hoVQepkLcIyaBzY3qxGXg9jvQSyLdsNIA3W7mZ53A lsFw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792424924; x=1793029724; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=q1Ii+kS6GcqCuy8FpwraDGcd1HAWUkHGu9ihz0l8o30=; b=cZcaMvkVwRJqIUsZKFzGvxYSj4/raaAU2jciH3H4PnkHtjl4eNwA20dz946S/P67RL xZNX4KuryWWoDWaBUyq3qOoWIfleeMBBdGaMR2LRcDeE5YY6r4i9O8dDhX/qOPkdEoBo Gh3/DJ27RlUTyE3Ji7MpDYC+jyd9p5t3ITjjIbnvI+jb0G7J5qu6AS7HtZbgEThbaxvH kHCI3txtO2tNygf6/HcTBw0Zwtg7AdgH8bLC3NpIRVlt0j4yUvkw/XWRxqNhpJDIgtm/ f7Ca7elQSWg0yowKGxu/RFAPRbGNYiT0IaD5ondKqL9BqlYE4RWHD7EKuzB8Kbu4KthW DSmg== X-Forwarded-Encrypted: i=1; AKwUvByHoRfxTj8x8i3DX168zo+/lyHl1TCJkylj4fB3nL2F7HjGNS7g0x6lTn0TZ2tgN4W3lw7MYuMhB1IAEMk=@vger.kernel.org X-Gm-Message-State: AFq9FYLs3vBwiHExosTfzm+NsYML01poi/Mtcy3SjEl/kYrgUo+XEdzL aD/YNwQHBWkF+wY3YuQYpYTeP6K8iJM4p6hMGj3Y9Uso7Zap3qepjHY4 X-Gm-Gg: AYBFou0fXSklqCgTVyYbMkv4DnKRwiaArrOk9FHOJA+f5sESx+gAxXDIURALOGAXYN1 NLE9iaslwhbfFdsgiUOeazkVRX2ApEXh9FmkyIbJXGUnes7ed2RFEDINJACCewYK9hZgbgGbZor xLzbdlrt5KJpHZMAB3RmthpVyVc5fBk/+DxCBdDbHzqgCNEqPHivJ2jAvVe3lHCf+cQ2azHQukQ NnCfk9NLd5LWarCUvzbOTCqvYHw5eoNjmQGx+zH2zxVvo06KgbAWOehXZd4ci60a8tdr/qv/dc7 V+sDSngNkCo2mjIithlkmp0mTjngXSqZVt6E+8roti5iUbczNaekaJEZ7vNBloM4zIKWHBVrX44 PbhtC285OK1Ny8jn07lGp9Oq/9nBOYWE7D0JxMqXRxKAaS6ARtb4dEL9a+ZoX+xf3j1ePNLVKUM brDjOdPFycCnU3Kf1O6Srbqo61hBRa/EnVMFztwA+Mtfgk0RyA81ZfXjkPuHaYpOL6Kx0v3P5a0 iEZOV13PX7AV5QO8spUXD5zBqekhH1AEwcZabVPsBScLAlYyks= X-Received: by 2002:a17:90a:6082:b0:3ac:ffb8:183b with SMTP id 98e67ed59e1d1-3acffb826b6mr3428244a91.51.1792424924349; Mon, 19 Oct 2026 08:48:44 -0700 (PDT) Received: from hostname.tail4fb79.ts.net ([2401:7400:c804:b668:a144:150d:733a:5ba2]) by smtp.googlemail.com with ESMTPSA id 98e67ed59e1d1-3ad5cc13658sm63271a91.4.2026.10.19.08.48.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 Oct 2026 08:48:43 -0700 (PDT) From: Ho Jie Feng To: dri-devel@lists.freedesktop.org Cc: Ho Jie Feng , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , linux-kernel@vger.kernel.org Subject: [PATCH v1 2/4] drm/ms91xx: Add register access and connector support Date: Mon, 19 Oct 2026 23:48:06 +0800 Message-ID: <20261019154820.68659-3-hjf3108@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261019154820.68659-1-hjf3108@gmail.com> References: <20261019154820.68659-1-hjf3108@gmail.com> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Implement for the ms91xx driver: - USB control requests for register and flash access - display power - mode programming - custom timings stored in device flash - connector detection - EDID mode - fallback modes Signed-off-by: Ho Jie Feng --- drivers/gpu/drm/ms91xx/ms91xx_connector.c | 263 +++++++++++++++ drivers/gpu/drm/ms91xx/ms91xx_registers.c | 386 ++++++++++++++++++++++ 2 files changed, 649 insertions(+) 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_connector.c b/drivers/gpu/drm/ms91xx/ms91xx_connector.c new file mode 100644 index 0000000000000..d04f818fa09f2 --- /dev/null +++ b/drivers/gpu/drm/ms91xx/ms91xx_connector.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + + 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 0000000000000..84cbdc78002b1 --- /dev/null +++ b/drivers/gpu/drm/ms91xx/ms91xx_registers.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 || + !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 | DRM_MODE_TYPE_PREFERRED; + 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++; + } + + 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