[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