| // 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_edid.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; |
| const struct drm_edid *drm_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; |
| drm_edid = drm_edid_read_custom(connector, gud_connector_get_edid_block, &edid_ctx); |
| } |
| |
| kfree(edid_ctx.buf); |
| drm_edid_connector_update(connector, drm_edid); |
| |
| if (drm_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_edid_connector_add_modes(connector); |
| |
| kfree(reqmodes); |
| drm_edid_free(drm_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.legacy_mode != new_state->tv.legacy_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_margins_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_legacy(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->legacy_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->legacy_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_legacy(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; |
| } |