[PATCH v4 3/3] drm: Add Generic USB Display driver

Noralf Trønnes noralf at tronnes.org
Sun Jan 24 16:17:54 UTC 2021



Den 20.01.2021 19.02, skrev Daniel Vetter:
> On Wed, Jan 20, 2021 at 6:11 PM Noralf Trønnes <noralf at tronnes.org> wrote:
>>
>> This adds a generic 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
> 
> I think the backlight is still a bit problematic, since you're using
> kms locks within the backlight callbacks. Other display drivers use
> backlight within their kms locks. This means inconsistent locking
> rules, which upsets lockdep.
> 
> Since you're already handling brightness as a special case in many
> places I don't think it's a big shuffle:
> - add a mutex to the connector struct
> - move brightness value to connector struct, out of the connector_state
> - use the new mutex to protect backlight state both from modeset side
> (if needed, I'm not entirely sure about that) and the backlight side
> 

It's not enough to store the value I need to send it to the device as
well. Currently I send the entire state each time there's a change. To
continue that I would need to keep a copy of the state that I can use
when brightness changes. Or I can treat backlight as an exception and
add a USB control request just for backlight.

There is some special treatment of the backlight in the driver, but I
would really like to handle the backlight brightness through the atomic
machinery. I want to avoid special treatment of backlight in the USB
protocol.

I can avoid the lockdep problem by letting a worker commit the state and
schedule it from the backlight update callback. I'll do that unless you
see other issues with that approach.

Is it ok to take the connection_mutex lock in the get_brightness
callback to get to the connector state and the brightness value?

i915 takes that lock in intel_backlight_device_update_status() and
intel_backlight_device_get_brightness().

> Some more things below, but in general I'd say Acked-by: Daniel Vetter
> <daniel.vetter> fwiw (probably not so much).
> 

Thanks for taking a look, much appreciated.

Noralf.


>> diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c
>> new file mode 100644
>> index 000000000000..a4b9bbf48e19
>> --- /dev/null
>> +++ b/drivers/gpu/drm/gud/gud_connector.c
>> @@ -0,0 +1,722 @@
>> +// SPDX-License-Identifier: MIT
>> +/*
>> + * Copyright 2020 Noralf Trønnes
>> + */
>> +
>> +#include <linux/backlight.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;
>> +
>> +       /* 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;
>> +
>> +       unsigned int num_modes;
>> +       size_t edid_len;
>> +};
>> +
>> +static inline struct gud_connector *to_gud_connector(struct drm_connector *connector)
>> +{
>> +       return container_of(connector, struct gud_connector, connector);
>> +}
>> +
>> +static int gud_connector_backlight_update_status(struct backlight_device *bd)
>> +{
>> +       struct drm_connector *connector = bl_get_data(bd);
>> +       struct drm_connector_state *connector_state;
>> +       struct drm_device *dev = connector->dev;
>> +       struct drm_modeset_acquire_ctx ctx;
>> +       struct drm_atomic_state *state;
>> +       int ret;
>> +
>> +       state = drm_atomic_state_alloc(dev);
>> +       if (!state)
>> +               return -ENOMEM;
>> +
>> +       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 = bd->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);
>> +
>> +       return ret;
>> +}
>> +
>> +static int gud_connector_backlight_get_brightness(struct backlight_device *bd)
>> +{
>> +       struct drm_connector *connector = bl_get_data(bd);
>> +       struct drm_device *dev = connector->dev;
>> +       int brightness;
>> +
>> +       drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
>> +       brightness = connector->state->tv.brightness;
>> +       drm_modeset_unlock(&dev->mode_config.connection_mutex);
>> +
>> +       return brightness;
>> +}
>> +
>> +static const struct backlight_ops gud_connector_backlight_ops = {
>> +       .get_brightness = gud_connector_backlight_get_brightness,
>> +       .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,
>> +       };
>> +
>> +       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_status_request(struct drm_connector *connector)
>> +{
>> +       struct gud_connector *gconn = to_gud_connector(connector);
>> +       struct gud_device *gdrm = to_gud_device(connector->dev);
>> +       struct gud_connector_status_req req;
>> +       u16 num_modes, edid_len;
>> +       int ret;
>> +
>> +       ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_STATUS,
>> +                         connector->index, &req, sizeof(req));
>> +       if (ret)
>> +               return ret;
>> +
>> +       switch (req.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;
>> +       };
>> +
>> +       num_modes = le16_to_cpu(req.num_modes);
>> +       edid_len = le16_to_cpu(req.edid_len);
>> +
>> +       if (edid_len % EDID_LENGTH) {
>> +               drm_err(connector->dev, "%s: Invalid EDID size: %u\n", connector->name, edid_len);
>> +               edid_len = 0;
>> +       }
>> +
>> +       if (req.status & GUD_CONNECTOR_STATUS_CHANGED ||
>> +           gconn->num_modes != num_modes || gconn->edid_len != edid_len)
>> +               connector->epoch_counter += 1;
>> +
>> +       gconn->num_modes = num_modes;
>> +       gconn->edid_len = edid_len;
>> +
>> +       if (!num_modes && !edid_len && ret != connector_status_disconnected)
>> +               drm_dbg_kms(connector->dev, "%s: No modes or EDID.\n", connector->name);
>> +
>> +       return ret;
>> +}
>> +
>> +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;
>> +
>> +       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_connector_status_request(connector);
>> +       if (ret < 0)
>> +               ret = connector_status_unknown;
>> +exit:
>> +       drm_dev_exit(idx);
>> +
>> +       return ret;
>> +}
>> +
>> +struct gud_connector_get_edid_ctx {
>> +       struct gud_connector *gconn;
>> +       void *buf;
>> +};
>> +
>> +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;
>> +       struct gud_connector *gconn = ctx->gconn;
>> +       size_t start = block * EDID_LENGTH;
>> +
>> +       if (start + len > gconn->edid_len)
>> +               return -1;
>> +
>> +       if (!block) {
>> +               struct gud_device *gdrm = to_gud_device(gconn->connector.dev);
>> +               int ret;
>> +
>> +               /* Check because drm_do_get_edid() will retry on failure */
>> +               if (!ctx->buf)
>> +                       ctx->buf = kmalloc(gconn->edid_len, GFP_KERNEL);
>> +               if (!ctx->buf)
>> +                       return -1;
>> +
>> +               ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_EDID, gconn->connector.index,
>> +                                 ctx->buf, gconn->edid_len);
>> +               if (ret)
>> +                       return -1;
>> +       }
>> +
>> +       memcpy(buf, ctx->buf + start, len);
>> +
>> +       return 0;
>> +}
>> +
>> +static int gud_connector_get_modes(struct drm_connector *connector)
>> +{
>> +       struct gud_connector *gconn = to_gud_connector(connector);
>> +       struct gud_device *gdrm = to_gud_device(connector->dev);
>> +       struct gud_connector_get_edid_ctx edid_ctx = {
>> +               .gconn = gconn,
>> +       };
>> +       struct gud_display_mode_req *reqmodes = NULL;
>> +       unsigned int i, num_modes = 0;
>> +       struct edid *edid = NULL;
>> +       bool edid_override;
>> +       int idx, ret;
>> +
>> +       if (!drm_dev_enter(connector->dev, &idx))
>> +               return 0;
>> +
>> +       if (connector->force) {
>> +               ret = gud_connector_status_request(connector);
>> +               if (ret < 0)
>> +                       goto out;
>> +       }
>> +
>> +       edid = drm_do_get_edid(connector, gud_connector_get_edid_block, &edid_ctx);
>> +       edid_override = edid && !edid_ctx.buf;
>> +       kfree(edid_ctx.buf);
>> +       drm_connector_update_edid_property(connector, edid);
>> +
>> +       if (!gconn->num_modes || edid_override) {
>> +               num_modes = drm_add_edid_modes(connector, edid);
>> +               goto out;
>> +       }
>> +
>> +       reqmodes = kmalloc_array(gconn->num_modes, sizeof(*reqmodes), GFP_KERNEL);
>> +       if (!reqmodes)
>> +               goto out;
>> +
>> +       ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_MODES, connector->index,
>> +                         reqmodes, gconn->num_modes * sizeof(*reqmodes));
>> +       if (ret)
>> +               goto out;
>> +
>> +       for (i = 0; i < gconn->num_modes; i++) {
>> +               struct drm_display_mode *mode;
>> +
>> +               mode = drm_mode_create(connector->dev);
>> +               if (!mode)
>> +                       goto out;
>> +
>> +               gud_to_display_mode(mode, &reqmodes[i]);
>> +               drm_mode_probed_add(connector, mode);
>> +               num_modes++;
>> +       }
>> +out:
>> +       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);
>> +}
>> +
>> +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, u64 val)
>> +{
>> +       unsigned int i, num_modes;
>> +       const char **modes;
>> +       size_t buf_len;
>> +       char *buf;
>> +       int ret;
>> +
>> +       num_modes = val >> GUD_CONNECTOR_TV_MODE_NUM_SHIFT;
>> +
>> +       if (!num_modes)
>> +               return -EINVAL;
>> +
>> +       buf_len = num_modes * GUD_CONNECTOR_TV_MODE_NAME_LEN;
>> +       modes = kmalloc_array(num_modes, sizeof(*modes), GFP_KERNEL);
>> +       buf = kmalloc(buf_len, GFP_KERNEL);
>> +       if (!modes || !buf) {
>> +               ret = -ENOMEM;
>> +               goto free;
>> +       }
>> +
>> +       ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES,
>> +                         connector->index, buf, buf_len);
>> +       if (ret)
>> +               goto free;
>> +
>> +       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(modes);
>> +       kfree(buf);
>> +
>> +       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,
>> +                                       unsigned int num_properties)
>> +{
>> +       struct drm_device *drm = &gdrm->drm;
>> +       struct drm_connector *connector = &gconn->connector;
>> +       struct gud_property_req *properties;
>> +       unsigned int i;
>> +       int ret;
>> +
>> +       gconn->properties = kcalloc(num_properties, sizeof(*gconn->properties), GFP_KERNEL);
>> +       if (!gconn->properties)
>> +               return -ENOMEM;
>> +
>> +       properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL);
>> +       if (!properties)
>> +               return -ENOMEM;
>> +
>> +       ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_PROPERTIES, connector->index,
>> +                         properties, num_properties * sizeof(*properties));
>> +       if (ret)
>> +               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, val);
>> +                       if (ret)
>> +                               goto out;
>> +                       val = val & (BIT(GUD_CONNECTOR_TV_MODE_NUM_SHIFT) - 1);
>> +                       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, "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 *connector,
>> +                                 struct drm_connector_state *connector_state,
>> +                                 struct gud_property_req *properties)
>> +{
>> +       struct gud_connector *gconn;
>> +       unsigned int i;
>> +
>> +       gconn = to_gud_connector(connector);
>> +
>> +       /* Only interested in the count? */
>> +       if (!connector_state)
>> +               return gconn->num_properties;
>> +
>> +       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;
>> +}
>> +
>> +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;
>> +
>> +       ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR, index, &desc, sizeof(desc));
>> +       if (ret)
>> +               return ret;
>> +
>> +       gconn = kzalloc(sizeof(*gconn), GFP_KERNEL);
> 
> Would be nice to do that with drmm_, but we don't have the
> drmm_connector_alloc wrapper yet.
> 
>> +       if (!gconn)
>> +               return -ENOMEM;
>> +
>> +       gconn->initial_brightness = -ENODEV;
>> +       flags = le32_to_cpu(desc.flags);
>> +       connector = &gconn->connector;
>> +
>> +       drm_dbg(drm, "Connector: index=%u type=%u flags=0x%x num_properties=%u\n",
>> +               index, desc.connector_type, flags, desc.num_properties);
>> +
>> +       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;
>> +
>> +       if (desc.num_properties) {
>> +               ret = gud_connector_add_properties(gdrm, gconn, desc.num_properties);
>> +               if (ret) {
>> +                       dev_err(drm->dev, "Failed to add connector/%u properties\n", index);
>> +                       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);
>> +}


More information about the dri-devel mailing list