diff options
| author | Adrian Betschart <adrian.betschart@cinemaone.ch> | 2026-09-11 17:13:14 +0200 |
|---|---|---|
| committer | Alex Deucher <alexander.deucher@amd.com> | 2026-10-01 17:18:05 -0400 |
| commit | 106b4a2a78b2a2f14885604891957b84bba502db (patch) | |
| tree | 5d40eef2428fb1a4791ab46a4b19091a5910eac2 /drivers/leds/led-class-flash.c | |
| download | linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.tar.gz linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.zip | |
drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validationgrafted
amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and
uses the first encoding that validates. YCbCr 4:4:4 is listed before
RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the
"color format" property is left at AUTO, even though RGB fits the same
link.
That contradicts the documented AUTO behaviour for HDMI in enum
drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when
the bandwidth is not available or the mode is 4:2:0-only), which the
amdgpu implementation of the property also describes. It also leaves
the "Broadcast RGB" property without effect on such sinks, since the
quantization range it selects only applies to RGB output.
Try RGB first. The mask still holds every encoding the sink supports,
so a mode that cannot carry RGB falls back exactly as before.
For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced
through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI
sink that advertised it.
Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation")
Suggested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
Signed-off-by: Adrian Betschart <adrian.betschart@cinemaone.ch>
Reviewed-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Tested-by: Daniel Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7)
Diffstat (limited to 'drivers/leds/led-class-flash.c')
| -rw-r--r-- | drivers/leds/led-class-flash.c | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c new file mode 100644 index 000000000..165035a88 --- /dev/null +++ b/drivers/leds/led-class-flash.c @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED Flash class interface + * + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * Author: Jacek Anaszewski <j.anaszewski@samsung.com> + */ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/led-class-flash.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/slab.h> + +#define has_flash_op(fled_cdev, op) \ + (fled_cdev && fled_cdev->ops->op) + +#define call_flash_op(fled_cdev, op, args...) \ + ((has_flash_op(fled_cdev, op)) ? \ + (fled_cdev->ops->op(fled_cdev, args)) : \ + -EINVAL) + +static const char * const led_flash_fault_names[] = { + "led-over-voltage", + "flash-timeout-exceeded", + "controller-over-temperature", + "controller-short-circuit", + "led-power-supply-over-current", + "indicator-led-fault", + "led-under-voltage", + "controller-under-voltage", + "led-over-temperature", +}; + +static ssize_t flash_brightness_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + unsigned long state; + ssize_t ret; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) { + ret = -EBUSY; + goto unlock; + } + + ret = kstrtoul(buf, 10, &state); + if (ret) + goto unlock; + + ret = led_set_flash_brightness(fled_cdev, state); + if (ret < 0) + goto unlock; + + ret = size; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; +} + +static ssize_t flash_brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + + /* no lock needed for this */ + led_update_flash_brightness(fled_cdev); + + return sprintf(buf, "%u\n", fled_cdev->brightness.val); +} +static DEVICE_ATTR_RW(flash_brightness); + +static ssize_t max_flash_brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + + return sprintf(buf, "%u\n", fled_cdev->brightness.max); +} +static DEVICE_ATTR_RO(max_flash_brightness); + +static ssize_t flash_strobe_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + unsigned long state; + ssize_t ret = -EBUSY; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) + goto unlock; + + ret = kstrtoul(buf, 10, &state); + if (ret) + goto unlock; + + if (state > 1) { + ret = -EINVAL; + goto unlock; + } + + ret = led_set_flash_strobe(fled_cdev, state); + if (ret < 0) + goto unlock; + ret = size; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; +} + +static ssize_t flash_strobe_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + bool state; + int ret; + + /* no lock needed for this */ + ret = led_get_flash_strobe(fled_cdev, &state); + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", state); +} +static DEVICE_ATTR_RW(flash_strobe); + +static ssize_t flash_timeout_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + unsigned long flash_timeout; + ssize_t ret; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) { + ret = -EBUSY; + goto unlock; + } + + ret = kstrtoul(buf, 10, &flash_timeout); + if (ret) + goto unlock; + + ret = led_set_flash_timeout(fled_cdev, flash_timeout); + if (ret < 0) + goto unlock; + + ret = size; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; +} + +static ssize_t flash_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + + return sprintf(buf, "%u\n", fled_cdev->timeout.val); +} +static DEVICE_ATTR_RW(flash_timeout); + +static ssize_t max_flash_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + + return sprintf(buf, "%u\n", fled_cdev->timeout.max); +} +static DEVICE_ATTR_RO(max_flash_timeout); + +static ssize_t flash_fault_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + u32 fault, mask = 0x1; + char *pbuf = buf; + int i, ret, buf_len; + + ret = led_get_flash_fault(fled_cdev, &fault); + if (ret < 0) + return -EINVAL; + + *buf = '\0'; + + for (i = 0; i < LED_NUM_FLASH_FAULTS; ++i) { + if (fault & mask) { + buf_len = sprintf(pbuf, "%s ", + led_flash_fault_names[i]); + pbuf += buf_len; + } + mask <<= 1; + } + + return strlen(strcat(buf, "\n")); +} +static DEVICE_ATTR_RO(flash_fault); + +static struct attribute *led_flash_strobe_attrs[] = { + &dev_attr_flash_strobe.attr, + NULL, +}; + +static struct attribute *led_flash_timeout_attrs[] = { + &dev_attr_flash_timeout.attr, + &dev_attr_max_flash_timeout.attr, + NULL, +}; + +static struct attribute *led_flash_brightness_attrs[] = { + &dev_attr_flash_brightness.attr, + &dev_attr_max_flash_brightness.attr, + NULL, +}; + +static struct attribute *led_flash_fault_attrs[] = { + &dev_attr_flash_fault.attr, + NULL, +}; + +static const struct attribute_group led_flash_strobe_group = { + .attrs = led_flash_strobe_attrs, +}; + +static const struct attribute_group led_flash_timeout_group = { + .attrs = led_flash_timeout_attrs, +}; + +static const struct attribute_group led_flash_brightness_group = { + .attrs = led_flash_brightness_attrs, +}; + +static const struct attribute_group led_flash_fault_group = { + .attrs = led_flash_fault_attrs, +}; + +static void led_flash_resume(struct led_classdev *led_cdev) +{ + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + + call_flash_op(fled_cdev, flash_brightness_set, + fled_cdev->brightness.val); + call_flash_op(fled_cdev, timeout_set, fled_cdev->timeout.val); +} + +static void led_flash_init_sysfs_groups(struct led_classdev_flash *fled_cdev) +{ + struct led_classdev *led_cdev = &fled_cdev->led_cdev; + const struct led_flash_ops *ops = fled_cdev->ops; + const struct attribute_group **flash_groups = fled_cdev->sysfs_groups; + + int num_sysfs_groups = 0; + + flash_groups[num_sysfs_groups++] = &led_flash_strobe_group; + + if (ops->flash_brightness_set) + flash_groups[num_sysfs_groups++] = &led_flash_brightness_group; + + if (ops->timeout_set) + flash_groups[num_sysfs_groups++] = &led_flash_timeout_group; + + if (ops->fault_get) + flash_groups[num_sysfs_groups++] = &led_flash_fault_group; + + led_cdev->groups = flash_groups; +} + +int led_classdev_flash_register_ext(struct device *parent, + struct led_classdev_flash *fled_cdev, + struct led_init_data *init_data) +{ + struct led_classdev *led_cdev; + const struct led_flash_ops *ops; + int ret; + + if (!fled_cdev) + return -EINVAL; + + led_cdev = &fled_cdev->led_cdev; + + if (led_cdev->flags & LED_DEV_CAP_FLASH) { + if (!led_cdev->brightness_set_blocking) + return -EINVAL; + + ops = fled_cdev->ops; + if (!ops || !ops->strobe_set) + return -EINVAL; + + led_cdev->flash_resume = led_flash_resume; + + /* Select the sysfs attributes to be created for the device */ + led_flash_init_sysfs_groups(fled_cdev); + } + + /* Register led class device */ + ret = led_classdev_register_ext(parent, led_cdev, init_data); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(led_classdev_flash_register_ext); + +void led_classdev_flash_unregister(struct led_classdev_flash *fled_cdev) +{ + if (!fled_cdev) + return; + + led_classdev_unregister(&fled_cdev->led_cdev); +} +EXPORT_SYMBOL_GPL(led_classdev_flash_unregister); + +static void devm_led_classdev_flash_release(struct device *dev, void *res) +{ + led_classdev_flash_unregister(*(struct led_classdev_flash **)res); +} + +int devm_led_classdev_flash_register_ext(struct device *parent, + struct led_classdev_flash *fled_cdev, + struct led_init_data *init_data) +{ + struct led_classdev_flash **dr; + int ret; + + dr = devres_alloc(devm_led_classdev_flash_release, sizeof(*dr), + GFP_KERNEL); + if (!dr) + return -ENOMEM; + + ret = led_classdev_flash_register_ext(parent, fled_cdev, init_data); + if (ret) { + devres_free(dr); + return ret; + } + + *dr = fled_cdev; + devres_add(parent, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_led_classdev_flash_register_ext); + +static int devm_led_classdev_flash_match(struct device *dev, + void *res, void *data) +{ + struct led_classdev_flash **p = res; + + if (WARN_ON(!p || !*p)) + return 0; + + return *p == data; +} + +void devm_led_classdev_flash_unregister(struct device *dev, + struct led_classdev_flash *fled_cdev) +{ + WARN_ON(devres_release(dev, + devm_led_classdev_flash_release, + devm_led_classdev_flash_match, fled_cdev)); +} +EXPORT_SYMBOL_GPL(devm_led_classdev_flash_unregister); + +static void led_clamp_align(struct led_flash_setting *s) +{ + u32 v, offset; + + v = s->val + s->step / 2; + v = clamp(v, s->min, s->max); + offset = v - s->min; + offset = s->step * (offset / s->step); + s->val = s->min + offset; +} + +int led_set_flash_timeout(struct led_classdev_flash *fled_cdev, u32 timeout) +{ + struct led_classdev *led_cdev = &fled_cdev->led_cdev; + struct led_flash_setting *s = &fled_cdev->timeout; + + s->val = timeout; + led_clamp_align(s); + + if (!(led_cdev->flags & LED_SUSPENDED)) + return call_flash_op(fled_cdev, timeout_set, s->val); + + return 0; +} +EXPORT_SYMBOL_GPL(led_set_flash_timeout); + +int led_get_flash_fault(struct led_classdev_flash *fled_cdev, u32 *fault) +{ + return call_flash_op(fled_cdev, fault_get, fault); +} +EXPORT_SYMBOL_GPL(led_get_flash_fault); + +int led_set_flash_brightness(struct led_classdev_flash *fled_cdev, + u32 brightness) +{ + struct led_classdev *led_cdev = &fled_cdev->led_cdev; + struct led_flash_setting *s = &fled_cdev->brightness; + + s->val = brightness; + led_clamp_align(s); + + if (!(led_cdev->flags & LED_SUSPENDED)) + return call_flash_op(fled_cdev, flash_brightness_set, s->val); + + return 0; +} +EXPORT_SYMBOL_GPL(led_set_flash_brightness); + +int led_update_flash_brightness(struct led_classdev_flash *fled_cdev) +{ + struct led_flash_setting *s = &fled_cdev->brightness; + u32 brightness; + + if (has_flash_op(fled_cdev, flash_brightness_get)) { + int ret = call_flash_op(fled_cdev, flash_brightness_get, + &brightness); + if (ret < 0) + return ret; + + s->val = brightness; + } + + return 0; +} +EXPORT_SYMBOL_GPL(led_update_flash_brightness); + +int led_set_flash_duration(struct led_classdev_flash *fled_cdev, u32 duration) +{ + struct led_classdev *led_cdev = &fled_cdev->led_cdev; + struct led_flash_setting *s = &fled_cdev->duration; + + s->val = duration; + led_clamp_align(s); + + if (!(led_cdev->flags & LED_SUSPENDED)) + return call_flash_op(fled_cdev, duration_set, s->val); + + return 0; +} +EXPORT_SYMBOL_GPL(led_set_flash_duration); + +MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>"); +MODULE_DESCRIPTION("LED Flash class interface"); +MODULE_LICENSE("GPL v2"); |
