summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/gud/gud_connector.c
diff options
context:
space:
mode:
authorNoralf Trønnes <noralf@tronnes.org>2021-03-13 14:25:45 +0300
committerNoralf Trønnes <noralf@tronnes.org>2021-03-16 15:12:46 +0300
commit40e1a70b4aedf2859a1829991b48ef0ebe650bf2 (patch)
treeae60ad86e3fbfd4b5d9978d11a76b6ff43e0eba7 /drivers/gpu/drm/gud/gud_connector.c
parentdc659a4e852b591771fc2e5abb60f4455b0cf316 (diff)
downloadlinux-40e1a70b4aedf2859a1829991b48ef0ebe650bf2.tar.xz
drm: Add GUD USB Display driver
This adds a USB display driver with the intention that it can be used with future USB interfaced low end displays/adapters. The Linux gadget device driver will serve as the canonical device implementation. The following DRM properties are supported: - Plane rotation - Connector TV properties There is also support for backlight brightness exposed as a backlight device. Display modes can be made available to the host driver either as DRM display modes or through EDID. If both are present, EDID is just passed on to userspace. Performance is preferred over color depth, so if the device supports RGB565, DRM_CAP_DUMB_PREFERRED_DEPTH will return 16. If the device transfer buffer can't fit an uncompressed framebuffer update, the update is split up into parts that do fit. Optimal user experience is achieved by providing damage reports either by setting FB_DAMAGE_CLIPS on pageflips or calling DRM_IOCTL_MODE_DIRTYFB. LZ4 compression is used if the device supports it. The driver supports a one bit monochrome transfer format: R1. This is not implemented in the gadget driver. It is added in preparation for future monochrome e-ink displays. The driver is MIT licensed to smooth the path for any BSD port of the driver. v2: - Use devm_drm_dev_alloc() and drmm_mode_config_init() - drm_fbdev_generic_setup: Use preferred_bpp=0, 16 was a copy paste error - The drm_backlight_helper is dropped, copy in the code - Support protocol version backwards compatibility for device v3: - Use donated Openmoko USB pid - Use direct compression from framebuffer when pitch matches, not only on full frames, so split updates can benefit - Use __le16 in struct gud_drm_req_get_connector_status - Set edid property when the device only provides edid - Clear compression fields in struct gud_drm_req_set_buffer - Fix protocol version negotiation - Remove mode->vrefresh, it's calculated v4: - Drop the status req polling which was a workaround for something that turned out to be a dwc2 udc driver problem - Add a flag for the Linux gadget to require a status request on SET operations. Other devices will only get status req on STALL errors - Use protocol specific error codes (Peter) - Add a flag for devices that want to receive the entire framebuffer on each flush (Lubomir) - Retry a failed framebuffer flush - If mode has changed wait for worker and clear pending damage before queuing up new damage, fb width/height might have changed - Increase error counter on bulk transfer failures - Use DRM_MODE_CONNECTOR_USB - Handle R1 kmalloc error (Peter) - Don't try and replicate the USB get descriptor request standard for the display descriptor (Peter) - Make max_buffer_size optional (Peter), drop the pow2 requirement since it's not necessary anymore. - Don't pre-alloc a control request buffer, it was only 4k - Let gud.h describe the whole protocol explicitly and don't let DRM leak into it (Peter) - Drop display mode .hskew and .vscan from the protocol - Shorten names: s/GUD_DRM_/GUD_/ s/gud_drm_/gud_/ (Peter) - Fix gud_pipe_check() connector picking when switching connector - Drop gud_drm_driver_gem_create_object() cached is default now - Retrieve USB device from struct drm_device.dev instead of keeping a pointer - Honour fb->offsets[0] - Fix mode fetching when connector status is forced - Check EDID length reported by the device - Use drm_do_get_edid() so userspace can overrride EDID - Set epoch counter to signal connector status change - gud_drm_driver can be const now v5: - GUD_DRM_FORMAT_R1: Use non-human ascii values (Daniel) - Change name to: GUD USB Display (Thomas, Simon) - Change one __u32 -> __le32 in protocol header - Always log fb flush errors, unless the previous one failed - Run backlight update in a worker to avoid upsetting lockdep (Daniel) - Drop backlight_ops.get_brightness, there's no readback from the device so it doesn't really add anything. - Set dma mask, needed by dma-buf importers v6: - Use obj-y in Makefile (Peter) - Fix missing le32_to_cpu() when using GUD_DISPLAY_MAGIC (Peter) - Set initial brightness on backlight device v7: - LZ4_compress_default() can return zero, check for that - Fix memory leak in gud_pipe_check() error path (Peter) - Improve debug and error messages (Peter) - Don't pass length in protocol structs (Peter) - Pass USB interface to gud_usb_control_msg() et al. (Peter) - Improve gud_connector_fill_properties() (Peter) - Add GUD_PIXEL_FORMAT_RGB111 (Peter) - Remove GUD_REQ_SET_VERSION (Peter) - Fix DRM_IOCTL_MODE_OBJ_SETPROPERTY and the rotation property - Fix dma-buf import (Thomas) v8: - Forgot to filter RGB111 from reaching userspace - Handle a device that only returns unknown device properties (Peter) - s/GUD_PIXEL_FORMAT_RGB111/GUD_PIXEL_FORMAT_XRGB1111/ (Peter) - Fix R1 and XRGB1111 format conversion - Add FIXME about Big Endian being broken (Peter, Ilia) Cc: Lubomir Rintel <lkundrak@v3.sk> Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch> Reviewed-by: Peter Stuge <peter@stuge.se> Tested-by: Peter Stuge <peter@stuge.se> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Link: https://patchwork.freedesktop.org/patch/msgid/20210313112545.37527-4-noralf@tronnes.org
Diffstat (limited to 'drivers/gpu/drm/gud/gud_connector.c')
-rw-r--r--drivers/gpu/drm/gud/gud_connector.c729
1 files changed, 729 insertions, 0 deletions
diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c
new file mode 100644
index 000000000000..ec495dcd6122
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_connector.c
@@ -0,0 +1,729 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/backlight.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_file.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/gud.h>
+
+#include "gud_internal.h"
+
+struct gud_connector {
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+ struct backlight_device *backlight;
+ struct work_struct backlight_work;
+
+ /* Supported properties */
+ u16 *properties;
+ unsigned int num_properties;
+
+ /* Initial gadget tv state if applicable, applied on state reset */
+ struct drm_tv_connector_state initial_tv_state;
+
+ /*
+ * Initial gadget backlight brightness if applicable, applied on state reset.
+ * The value -ENODEV is used to signal no backlight.
+ */
+ int initial_brightness;
+};
+
+static inline struct gud_connector *to_gud_connector(struct drm_connector *connector)
+{
+ return container_of(connector, struct gud_connector, connector);
+}
+
+static void gud_conn_err(struct drm_connector *connector, const char *msg, int ret)
+{
+ dev_err(connector->dev->dev, "%s: %s (ret=%d)\n", connector->name, msg, ret);
+}
+
+/*
+ * Use a worker to avoid taking kms locks inside the backlight lock.
+ * Other display drivers use backlight within their kms locks.
+ * This avoids inconsistent locking rules, which would upset lockdep.
+ */
+static void gud_connector_backlight_update_status_work(struct work_struct *work)
+{
+ struct gud_connector *gconn = container_of(work, struct gud_connector, backlight_work);
+ struct drm_connector *connector = &gconn->connector;
+ struct drm_connector_state *connector_state;
+ struct drm_device *drm = connector->dev;
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_atomic_state *state;
+ int idx, ret;
+
+ if (!drm_dev_enter(drm, &idx))
+ return;
+
+ state = drm_atomic_state_alloc(drm);
+ if (!state) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ drm_modeset_acquire_init(&ctx, 0);
+ state->acquire_ctx = &ctx;
+retry:
+ connector_state = drm_atomic_get_connector_state(state, connector);
+ if (IS_ERR(connector_state)) {
+ ret = PTR_ERR(connector_state);
+ goto out;
+ }
+
+ /* Reuse tv.brightness to avoid having to subclass */
+ connector_state->tv.brightness = gconn->backlight->props.brightness;
+
+ ret = drm_atomic_commit(state);
+out:
+ if (ret == -EDEADLK) {
+ drm_atomic_state_clear(state);
+ drm_modeset_backoff(&ctx);
+ goto retry;
+ }
+
+ drm_atomic_state_put(state);
+
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+exit:
+ drm_dev_exit(idx);
+
+ if (ret)
+ dev_err(drm->dev, "Failed to update backlight, err=%d\n", ret);
+}
+
+static int gud_connector_backlight_update_status(struct backlight_device *bd)
+{
+ struct drm_connector *connector = bl_get_data(bd);
+ struct gud_connector *gconn = to_gud_connector(connector);
+
+ /* The USB timeout is 5 seconds so use system_long_wq for worst case scenario */
+ queue_work(system_long_wq, &gconn->backlight_work);
+
+ return 0;
+}
+
+static const struct backlight_ops gud_connector_backlight_ops = {
+ .update_status = gud_connector_backlight_update_status,
+};
+
+static int gud_connector_backlight_register(struct gud_connector *gconn)
+{
+ struct drm_connector *connector = &gconn->connector;
+ struct backlight_device *bd;
+ const char *name;
+ const struct backlight_properties props = {
+ .type = BACKLIGHT_RAW,
+ .scale = BACKLIGHT_SCALE_NON_LINEAR,
+ .max_brightness = 100,
+ .brightness = gconn->initial_brightness,
+ };
+
+ name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
+ connector->dev->primary->index, connector->name);
+ if (!name)
+ return -ENOMEM;
+
+ bd = backlight_device_register(name, connector->kdev, connector,
+ &gud_connector_backlight_ops, &props);
+ kfree(name);
+ if (IS_ERR(bd))
+ return PTR_ERR(bd);
+
+ gconn->backlight = bd;
+
+ return 0;
+}
+
+static int gud_connector_detect(struct drm_connector *connector,
+ struct drm_modeset_acquire_ctx *ctx, bool force)
+{
+ struct gud_device *gdrm = to_gud_device(connector->dev);
+ int idx, ret;
+ u8 status;
+
+ if (!drm_dev_enter(connector->dev, &idx))
+ return connector_status_disconnected;
+
+ if (force) {
+ ret = gud_usb_set(gdrm, GUD_REQ_SET_CONNECTOR_FORCE_DETECT,
+ connector->index, NULL, 0);
+ if (ret) {
+ ret = connector_status_unknown;
+ goto exit;
+ }
+ }
+
+ ret = gud_usb_get_u8(gdrm, GUD_REQ_GET_CONNECTOR_STATUS, connector->index, &status);
+ if (ret) {
+ ret = connector_status_unknown;
+ goto exit;
+ }
+
+ switch (status & GUD_CONNECTOR_STATUS_CONNECTED_MASK) {
+ case GUD_CONNECTOR_STATUS_DISCONNECTED:
+ ret = connector_status_disconnected;
+ break;
+ case GUD_CONNECTOR_STATUS_CONNECTED:
+ ret = connector_status_connected;
+ break;
+ default:
+ ret = connector_status_unknown;
+ break;
+ };
+
+ if (status & GUD_CONNECTOR_STATUS_CHANGED)
+ connector->epoch_counter += 1;
+exit:
+ drm_dev_exit(idx);
+
+ return ret;
+}
+
+struct gud_connector_get_edid_ctx {
+ void *buf;
+ size_t len;
+ bool edid_override;
+};
+
+static int gud_connector_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
+{
+ struct gud_connector_get_edid_ctx *ctx = data;
+ size_t start = block * EDID_LENGTH;
+
+ ctx->edid_override = false;
+
+ if (start + len > ctx->len)
+ return -1;
+
+ memcpy(buf, ctx->buf + start, len);
+
+ return 0;
+}
+
+static int gud_connector_get_modes(struct drm_connector *connector)
+{
+ struct gud_device *gdrm = to_gud_device(connector->dev);
+ struct gud_display_mode_req *reqmodes = NULL;
+ struct gud_connector_get_edid_ctx edid_ctx;
+ unsigned int i, num_modes = 0;
+ struct edid *edid = NULL;
+ int idx, ret;
+
+ if (!drm_dev_enter(connector->dev, &idx))
+ return 0;
+
+ edid_ctx.edid_override = true;
+ edid_ctx.buf = kmalloc(GUD_CONNECTOR_MAX_EDID_LEN, GFP_KERNEL);
+ if (!edid_ctx.buf)
+ goto out;
+
+ ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_EDID, connector->index,
+ edid_ctx.buf, GUD_CONNECTOR_MAX_EDID_LEN);
+ if (ret > 0 && ret % EDID_LENGTH) {
+ gud_conn_err(connector, "Invalid EDID size", ret);
+ } else if (ret > 0) {
+ edid_ctx.len = ret;
+ edid = drm_do_get_edid(connector, gud_connector_get_edid_block, &edid_ctx);
+ }
+
+ kfree(edid_ctx.buf);
+ drm_connector_update_edid_property(connector, edid);
+
+ if (edid && edid_ctx.edid_override)
+ goto out;
+
+ reqmodes = kmalloc_array(GUD_CONNECTOR_MAX_NUM_MODES, sizeof(*reqmodes), GFP_KERNEL);
+ if (!reqmodes)
+ goto out;
+
+ ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_MODES, connector->index,
+ reqmodes, GUD_CONNECTOR_MAX_NUM_MODES * sizeof(*reqmodes));
+ if (ret <= 0)
+ goto out;
+ if (ret % sizeof(*reqmodes)) {
+ gud_conn_err(connector, "Invalid display mode array size", ret);
+ goto out;
+ }
+
+ num_modes = ret / sizeof(*reqmodes);
+
+ for (i = 0; i < num_modes; i++) {
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ num_modes = i;
+ goto out;
+ }
+
+ gud_to_display_mode(mode, &reqmodes[i]);
+ drm_mode_probed_add(connector, mode);
+ }
+out:
+ if (!num_modes)
+ num_modes = drm_add_edid_modes(connector, edid);
+
+ kfree(reqmodes);
+ kfree(edid);
+ drm_dev_exit(idx);
+
+ return num_modes;
+}
+
+static int gud_connector_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_state *state)
+{
+ struct drm_connector_state *new_state;
+ struct drm_crtc_state *new_crtc_state;
+ struct drm_connector_state *old_state;
+
+ new_state = drm_atomic_get_new_connector_state(state, connector);
+ if (!new_state->crtc)
+ return 0;
+
+ old_state = drm_atomic_get_old_connector_state(state, connector);
+ new_crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);
+
+ if (old_state->tv.margins.left != new_state->tv.margins.left ||
+ old_state->tv.margins.right != new_state->tv.margins.right ||
+ old_state->tv.margins.top != new_state->tv.margins.top ||
+ old_state->tv.margins.bottom != new_state->tv.margins.bottom ||
+ old_state->tv.mode != new_state->tv.mode ||
+ old_state->tv.brightness != new_state->tv.brightness ||
+ old_state->tv.contrast != new_state->tv.contrast ||
+ old_state->tv.flicker_reduction != new_state->tv.flicker_reduction ||
+ old_state->tv.overscan != new_state->tv.overscan ||
+ old_state->tv.saturation != new_state->tv.saturation ||
+ old_state->tv.hue != new_state->tv.hue)
+ new_crtc_state->connectors_changed = true;
+
+ return 0;
+}
+
+static const struct drm_connector_helper_funcs gud_connector_helper_funcs = {
+ .detect_ctx = gud_connector_detect,
+ .get_modes = gud_connector_get_modes,
+ .atomic_check = gud_connector_atomic_check,
+};
+
+static int gud_connector_late_register(struct drm_connector *connector)
+{
+ struct gud_connector *gconn = to_gud_connector(connector);
+
+ if (gconn->initial_brightness < 0)
+ return 0;
+
+ return gud_connector_backlight_register(gconn);
+}
+
+static void gud_connector_early_unregister(struct drm_connector *connector)
+{
+ struct gud_connector *gconn = to_gud_connector(connector);
+
+ backlight_device_unregister(gconn->backlight);
+ cancel_work_sync(&gconn->backlight_work);
+}
+
+static void gud_connector_destroy(struct drm_connector *connector)
+{
+ struct gud_connector *gconn = to_gud_connector(connector);
+
+ drm_connector_cleanup(connector);
+ kfree(gconn->properties);
+ kfree(gconn);
+}
+
+static void gud_connector_reset(struct drm_connector *connector)
+{
+ struct gud_connector *gconn = to_gud_connector(connector);
+
+ drm_atomic_helper_connector_reset(connector);
+ connector->state->tv = gconn->initial_tv_state;
+ /* Set margins from command line */
+ drm_atomic_helper_connector_tv_reset(connector);
+ if (gconn->initial_brightness >= 0)
+ connector->state->tv.brightness = gconn->initial_brightness;
+}
+
+static const struct drm_connector_funcs gud_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .late_register = gud_connector_late_register,
+ .early_unregister = gud_connector_early_unregister,
+ .destroy = gud_connector_destroy,
+ .reset = gud_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/*
+ * The tv.mode property is shared among the connectors and its enum names are
+ * driver specific. This means that if more than one connector uses tv.mode,
+ * the enum names has to be the same.
+ */
+static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connector *connector)
+{
+ size_t buf_len = GUD_CONNECTOR_TV_MODE_MAX_NUM * GUD_CONNECTOR_TV_MODE_NAME_LEN;
+ const char *modes[GUD_CONNECTOR_TV_MODE_MAX_NUM];
+ unsigned int i, num_modes;
+ char *buf;
+ int ret;
+
+ buf = kmalloc(buf_len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES,
+ connector->index, buf, buf_len);
+ if (ret < 0)
+ goto free;
+ if (!ret || ret % GUD_CONNECTOR_TV_MODE_NAME_LEN) {
+ ret = -EIO;
+ goto free;
+ }
+
+ num_modes = ret / GUD_CONNECTOR_TV_MODE_NAME_LEN;
+ for (i = 0; i < num_modes; i++)
+ modes[i] = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN];
+
+ ret = drm_mode_create_tv_properties(connector->dev, num_modes, modes);
+free:
+ kfree(buf);
+ if (ret < 0)
+ gud_conn_err(connector, "Failed to add TV modes", ret);
+
+ return ret;
+}
+
+static struct drm_property *
+gud_connector_property_lookup(struct drm_connector *connector, u16 prop)
+{
+ struct drm_mode_config *config = &connector->dev->mode_config;
+
+ switch (prop) {
+ case GUD_PROPERTY_TV_LEFT_MARGIN:
+ return config->tv_left_margin_property;
+ case GUD_PROPERTY_TV_RIGHT_MARGIN:
+ return config->tv_right_margin_property;
+ case GUD_PROPERTY_TV_TOP_MARGIN:
+ return config->tv_top_margin_property;
+ case GUD_PROPERTY_TV_BOTTOM_MARGIN:
+ return config->tv_bottom_margin_property;
+ case GUD_PROPERTY_TV_MODE:
+ return config->tv_mode_property;
+ case GUD_PROPERTY_TV_BRIGHTNESS:
+ return config->tv_brightness_property;
+ case GUD_PROPERTY_TV_CONTRAST:
+ return config->tv_contrast_property;
+ case GUD_PROPERTY_TV_FLICKER_REDUCTION:
+ return config->tv_flicker_reduction_property;
+ case GUD_PROPERTY_TV_OVERSCAN:
+ return config->tv_overscan_property;
+ case GUD_PROPERTY_TV_SATURATION:
+ return config->tv_saturation_property;
+ case GUD_PROPERTY_TV_HUE:
+ return config->tv_hue_property;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static unsigned int *gud_connector_tv_state_val(u16 prop, struct drm_tv_connector_state *state)
+{
+ switch (prop) {
+ case GUD_PROPERTY_TV_LEFT_MARGIN:
+ return &state->margins.left;
+ case GUD_PROPERTY_TV_RIGHT_MARGIN:
+ return &state->margins.right;
+ case GUD_PROPERTY_TV_TOP_MARGIN:
+ return &state->margins.top;
+ case GUD_PROPERTY_TV_BOTTOM_MARGIN:
+ return &state->margins.bottom;
+ case GUD_PROPERTY_TV_MODE:
+ return &state->mode;
+ case GUD_PROPERTY_TV_BRIGHTNESS:
+ return &state->brightness;
+ case GUD_PROPERTY_TV_CONTRAST:
+ return &state->contrast;
+ case GUD_PROPERTY_TV_FLICKER_REDUCTION:
+ return &state->flicker_reduction;
+ case GUD_PROPERTY_TV_OVERSCAN:
+ return &state->overscan;
+ case GUD_PROPERTY_TV_SATURATION:
+ return &state->saturation;
+ case GUD_PROPERTY_TV_HUE:
+ return &state->hue;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static int gud_connector_add_properties(struct gud_device *gdrm, struct gud_connector *gconn)
+{
+ struct drm_connector *connector = &gconn->connector;
+ struct drm_device *drm = &gdrm->drm;
+ struct gud_property_req *properties;
+ unsigned int i, num_properties;
+ int ret;
+
+ properties = kcalloc(GUD_CONNECTOR_PROPERTIES_MAX_NUM, sizeof(*properties), GFP_KERNEL);
+ if (!properties)
+ return -ENOMEM;
+
+ ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_PROPERTIES, connector->index,
+ properties, GUD_CONNECTOR_PROPERTIES_MAX_NUM * sizeof(*properties));
+ if (ret <= 0)
+ goto out;
+ if (ret % sizeof(*properties)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ num_properties = ret / sizeof(*properties);
+ ret = 0;
+
+ gconn->properties = kcalloc(num_properties, sizeof(*gconn->properties), GFP_KERNEL);
+ if (!gconn->properties) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < num_properties; i++) {
+ u16 prop = le16_to_cpu(properties[i].prop);
+ u64 val = le64_to_cpu(properties[i].val);
+ struct drm_property *property;
+ unsigned int *state_val;
+
+ drm_dbg(drm, "property: %u = %llu(0x%llx)\n", prop, val, val);
+
+ switch (prop) {
+ case GUD_PROPERTY_TV_LEFT_MARGIN:
+ fallthrough;
+ case GUD_PROPERTY_TV_RIGHT_MARGIN:
+ fallthrough;
+ case GUD_PROPERTY_TV_TOP_MARGIN:
+ fallthrough;
+ case GUD_PROPERTY_TV_BOTTOM_MARGIN:
+ ret = drm_mode_create_tv_margin_properties(drm);
+ if (ret)
+ goto out;
+ break;
+ case GUD_PROPERTY_TV_MODE:
+ ret = gud_connector_add_tv_mode(gdrm, connector);
+ if (ret)
+ goto out;
+ break;
+ case GUD_PROPERTY_TV_BRIGHTNESS:
+ fallthrough;
+ case GUD_PROPERTY_TV_CONTRAST:
+ fallthrough;
+ case GUD_PROPERTY_TV_FLICKER_REDUCTION:
+ fallthrough;
+ case GUD_PROPERTY_TV_OVERSCAN:
+ fallthrough;
+ case GUD_PROPERTY_TV_SATURATION:
+ fallthrough;
+ case GUD_PROPERTY_TV_HUE:
+ /* This is a no-op if already added. */
+ ret = drm_mode_create_tv_properties(drm, 0, NULL);
+ if (ret)
+ goto out;
+ break;
+ case GUD_PROPERTY_BACKLIGHT_BRIGHTNESS:
+ if (val > 100) {
+ ret = -EINVAL;
+ goto out;
+ }
+ gconn->initial_brightness = val;
+ break;
+ default:
+ /* New ones might show up in future devices, skip those we don't know. */
+ drm_dbg(drm, "Ignoring unknown property: %u\n", prop);
+ continue;
+ }
+
+ gconn->properties[gconn->num_properties++] = prop;
+
+ if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS)
+ continue; /* not a DRM property */
+
+ property = gud_connector_property_lookup(connector, prop);
+ if (WARN_ON(IS_ERR(property)))
+ continue;
+
+ state_val = gud_connector_tv_state_val(prop, &gconn->initial_tv_state);
+ if (WARN_ON(IS_ERR(state_val)))
+ continue;
+
+ *state_val = val;
+ drm_object_attach_property(&connector->base, property, 0);
+ }
+out:
+ kfree(properties);
+
+ return ret;
+}
+
+int gud_connector_fill_properties(struct drm_connector_state *connector_state,
+ struct gud_property_req *properties)
+{
+ struct gud_connector *gconn = to_gud_connector(connector_state->connector);
+ unsigned int i;
+
+ for (i = 0; i < gconn->num_properties; i++) {
+ u16 prop = gconn->properties[i];
+ u64 val;
+
+ if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS) {
+ val = connector_state->tv.brightness;
+ } else {
+ unsigned int *state_val;
+
+ state_val = gud_connector_tv_state_val(prop, &connector_state->tv);
+ if (WARN_ON_ONCE(IS_ERR(state_val)))
+ return PTR_ERR(state_val);
+
+ val = *state_val;
+ }
+
+ properties[i].prop = cpu_to_le16(prop);
+ properties[i].val = cpu_to_le64(val);
+ }
+
+ return gconn->num_properties;
+}
+
+static int gud_connector_create(struct gud_device *gdrm, unsigned int index,
+ struct gud_connector_descriptor_req *desc)
+{
+ struct drm_device *drm = &gdrm->drm;
+ struct gud_connector *gconn;
+ struct drm_connector *connector;
+ struct drm_encoder *encoder;
+ int ret, connector_type;
+ u32 flags;
+
+ gconn = kzalloc(sizeof(*gconn), GFP_KERNEL);
+ if (!gconn)
+ return -ENOMEM;
+
+ INIT_WORK(&gconn->backlight_work, gud_connector_backlight_update_status_work);
+ gconn->initial_brightness = -ENODEV;
+ flags = le32_to_cpu(desc->flags);
+ connector = &gconn->connector;
+
+ drm_dbg(drm, "Connector: index=%u type=%u flags=0x%x\n", index, desc->connector_type, flags);
+
+ switch (desc->connector_type) {
+ case GUD_CONNECTOR_TYPE_PANEL:
+ connector_type = DRM_MODE_CONNECTOR_USB;
+ break;
+ case GUD_CONNECTOR_TYPE_VGA:
+ connector_type = DRM_MODE_CONNECTOR_VGA;
+ break;
+ case GUD_CONNECTOR_TYPE_DVI:
+ connector_type = DRM_MODE_CONNECTOR_DVID;
+ break;
+ case GUD_CONNECTOR_TYPE_COMPOSITE:
+ connector_type = DRM_MODE_CONNECTOR_Composite;
+ break;
+ case GUD_CONNECTOR_TYPE_SVIDEO:
+ connector_type = DRM_MODE_CONNECTOR_SVIDEO;
+ break;
+ case GUD_CONNECTOR_TYPE_COMPONENT:
+ connector_type = DRM_MODE_CONNECTOR_Component;
+ break;
+ case GUD_CONNECTOR_TYPE_DISPLAYPORT:
+ connector_type = DRM_MODE_CONNECTOR_DisplayPort;
+ break;
+ case GUD_CONNECTOR_TYPE_HDMI:
+ connector_type = DRM_MODE_CONNECTOR_HDMIA;
+ break;
+ default: /* future types */
+ connector_type = DRM_MODE_CONNECTOR_USB;
+ break;
+ };
+
+ drm_connector_helper_add(connector, &gud_connector_helper_funcs);
+ ret = drm_connector_init(drm, connector, &gud_connector_funcs, connector_type);
+ if (ret) {
+ kfree(connector);
+ return ret;
+ }
+
+ if (WARN_ON(connector->index != index))
+ return -EINVAL;
+
+ if (flags & GUD_CONNECTOR_FLAGS_POLL_STATUS)
+ connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT);
+ if (flags & GUD_CONNECTOR_FLAGS_INTERLACE)
+ connector->interlace_allowed = true;
+ if (flags & GUD_CONNECTOR_FLAGS_DOUBLESCAN)
+ connector->doublescan_allowed = true;
+
+ ret = gud_connector_add_properties(gdrm, gconn);
+ if (ret) {
+ gud_conn_err(connector, "Failed to add properties", ret);
+ return ret;
+ }
+
+ /* The first connector is attached to the existing simple pipe encoder */
+ if (!connector->index) {
+ encoder = &gdrm->pipe.encoder;
+ } else {
+ encoder = &gconn->encoder;
+
+ ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
+ if (ret)
+ return ret;
+
+ encoder->possible_crtcs = 1;
+ }
+
+ return drm_connector_attach_encoder(connector, encoder);
+}
+
+int gud_get_connectors(struct gud_device *gdrm)
+{
+ struct gud_connector_descriptor_req *descs;
+ unsigned int i, num_connectors;
+ int ret;
+
+ descs = kmalloc_array(GUD_CONNECTORS_MAX_NUM, sizeof(*descs), GFP_KERNEL);
+ if (!descs)
+ return -ENOMEM;
+
+ ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTORS, 0,
+ descs, GUD_CONNECTORS_MAX_NUM * sizeof(descs));
+ if (ret < 0)
+ goto free;
+ if (!ret || ret % sizeof(*descs)) {
+ ret = -EIO;
+ goto free;
+ }
+
+ num_connectors = ret / sizeof(*descs);
+
+ for (i = 0; i < num_connectors; i++) {
+ ret = gud_connector_create(gdrm, i, &descs[i]);
+ if (ret)
+ goto free;
+ }
+free:
+ kfree(descs);
+
+ return ret;
+}