diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/gpu/drm/gud/gud_connector.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'drivers/gpu/drm/gud/gud_connector.c')
-rw-r--r-- | drivers/gpu/drm/gud/gud_connector.c | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c new file mode 100644 index 000000000..fa636206f --- /dev/null +++ b/drivers/gpu/drm/gud/gud_connector.c @@ -0,0 +1,730 @@ +// 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_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(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; +} |