731 lines
19 KiB
C
731 lines
19 KiB
C
|
// 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;
|
||
|
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;
|
||
|
}
|