lkml.org 
[lkml]   [2022]   [Apr]   [3]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v10 2/3] drivers: ddcci: add drivers for DDCCI
    Date
    This patch adds the backlight driver that utilizes the DDCCI
    driver from patch one to add a backlight driver.

    Signed-off-by: Yusuf Khan <yusisamerican@gmail.com>
    Signed-off-by: Christoph Grenz <christophg+lkml@grenz-bonn.de>
    ---
    drivers/video/backlight/Kconfig | 11 +
    drivers/video/backlight/Makefile | 1 +
    drivers/video/backlight/ddcci-backlight.c | 411 ++++++++++++++++++++++
    3 files changed, 423 insertions(+)
    create mode 100644 drivers/video/backlight/ddcci-backlight.c

    diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
    index e32694c13da5..7a26088c3c3f 100644
    --- a/drivers/video/backlight/Kconfig
    +++ b/drivers/video/backlight/Kconfig
    @@ -289,6 +289,17 @@ config BACKLIGHT_QCOM_WLED
    If you have the Qualcomm PMIC, say Y to enable a driver for the
    WLED block. Currently it supports PM8941 and PMI8998.

    +config BACKLIGHT_DDCCI
    + tristate "DDCCI Backlight Driver"
    + depends on DDCCI
    + help
    + If you have a DDC/CI supporing monitor, say Y to enable a driver
    + to control its backlight using DDC/CI. This could be useful if
    + your monitor does not include a backlight driver. For this to be
    + useful you need to enable DDCCI support which can be found in
    + Device Drivers -> Character devices and that further depends on
    + I2C.
    +
    config BACKLIGHT_RT4831
    tristate "Richtek RT4831 Backlight Driver"
    depends on MFD_RT4831
    diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
    index cae2c83422ae..7bfb6e506b35 100644
    --- a/drivers/video/backlight/Makefile
    +++ b/drivers/video/backlight/Makefile
    @@ -58,3 +58,4 @@ obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o
    obj-$(CONFIG_BACKLIGHT_ARCXCNN) += arcxcnn_bl.o
    obj-$(CONFIG_BACKLIGHT_RAVE_SP) += rave-sp-backlight.o
    obj-$(CONFIG_BACKLIGHT_LED) += led_bl.o
    +obj-$(CONFIG_BACKLIGHT_DDCCI) += ddcci-backlight.o
    diff --git a/drivers/video/backlight/ddcci-backlight.c b/drivers/video/backlight/ddcci-backlight.c
    new file mode 100644
    index 000000000000..d37eb142311d
    --- /dev/null
    +++ b/drivers/video/backlight/ddcci-backlight.c
    @@ -0,0 +1,411 @@
    +// SPDX-License-Identifier: GPL-2.0-or-later
    +/*
    + * DDC/CI monitor backlight driver
    + *
    + * Copyright (c) 2015 Christoph Grenz
    + */
    +
    +/*
    + * This program is free software; you can redistribute it and/or modify it
    + * under the terms of the GNU General Public License as published by the Free
    + * Software Foundation; either version 2 of the License, or (at your option)
    + * any later version.
    + */
    +
    +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
    +#include <linux/backlight.h>
    +#include <linux/module.h>
    +#include <linux/fb.h>
    +#include <linux/sysfs.h>
    +
    +#include <linux/ddcci.h>
    +
    +
    +#define DDCCI_COMMAND_READ 0x01 /* read ctrl value */
    +#define DDCCI_REPLY_READ 0x02 /* read ctrl value reply */
    +#define DDCCI_COMMAND_WRITE 0x03 /* write ctrl value */
    +#define DDCCI_COMMAND_SAVE 0x0c /* save current settings */
    +
    +#define DDCCI_MONITOR_LUMINANCE 0x10
    +#define DDCCI_MONITOR_BACKLIGHT 0x13
    +#define DDCCI_MONITOR_BL_WHITE 0x6B
    +
    +static bool convenience_symlink = true;
    +
    +struct ddcci_monitor_drv_data {
    + struct ddcci_device *device;
    + struct backlight_device *bl_dev;
    + struct device *fb_dev;
    + unsigned char used_vcp;
    +};
    +
    +static int ddcci_monitor_writectrl(struct ddcci_device *device,
    + unsigned char ctrl, unsigned short value)
    +{
    + unsigned char buf[4];
    + int ret;
    +
    + buf[0] = DDCCI_COMMAND_WRITE;
    + buf[1] = ctrl;
    + buf[2] = (value >> 8);
    + buf[3] = (value & 255);
    +
    + ret = ddcci_device_write(device, true, buf, sizeof(buf));
    +
    + return ret;
    +}
    +
    +static int ddcci_monitor_readctrl(struct ddcci_device *device,
    + unsigned char ctrl, unsigned short *value,
    + unsigned short *maximum)
    +{
    + int ret;
    + unsigned char buf[10];
    +
    + buf[0] = DDCCI_COMMAND_READ;
    + buf[1] = ctrl;
    +
    + ret = ddcci_device_writeread(device, true, buf, 2, sizeof(buf));
    + if (ret < 0)
    + return ret;
    +
    + if (ret == 0)
    + return -ENOTSUPP;
    +
    + if (ret == 8 && buf[0] == DDCCI_REPLY_READ && buf[2] == ctrl) {
    + if (value)
    + *value = buf[6] * 256 + buf[7];
    +
    + if (maximum)
    + *maximum = buf[4] * 256 + buf[5];
    +
    + if (buf[1] == 1)
    + return -ENOTSUPP;
    + if (buf[1] != 0)
    + return -EIO;
    + return 0;
    + }
    +
    + return -EIO;
    +}
    +
    +static int ddcci_backlight_check_fb(struct backlight_device *bl,
    + struct fb_info *info)
    +{
    + struct ddcci_monitor_drv_data *drv_data = bl_get_data(bl);
    +
    + return drv_data->fb_dev == NULL || drv_data->fb_dev == info->dev;
    +}
    +
    +static int ddcci_backlight_update_status(struct backlight_device *bl)
    +{
    + struct ddcci_monitor_drv_data *drv_data = bl_get_data(bl);
    + int brightness = bl->props.brightness;
    + int ret;
    +
    + if (bl->props.power != FB_BLANK_UNBLANK ||
    + bl->props.state & BL_CORE_FBBLANK)
    + brightness = 0;
    +
    + ret = ddcci_monitor_writectrl(drv_data->device, drv_data->used_vcp,
    + brightness);
    + if (ret > 0)
    + ret = 0;
    + return ret;
    +}
    +
    +static int ddcci_backlight_get_brightness(struct backlight_device *bl)
    +{
    + unsigned short value = 0, maxval = 0;
    + int ret;
    + struct ddcci_monitor_drv_data *drv_data = bl_get_data(bl);
    +
    + ret = ddcci_monitor_readctrl(drv_data->device, drv_data->used_vcp,
    + &value, &maxval);
    + if (ret < 0)
    + return ret;
    +
    + bl->props.brightness = value;
    + bl->props.max_brightness = maxval;
    + ret = value;
    +
    + return ret;
    +}
    +
    +static const struct backlight_ops ddcci_backlight_ops = {
    + .options = 0,
    + .update_status = ddcci_backlight_update_status,
    + .get_brightness = ddcci_backlight_get_brightness,
    + .check_fb = ddcci_backlight_check_fb,
    +};
    +
    +static const char *ddcci_monitor_vcp_name(unsigned char vcp)
    +{
    + switch (vcp) {
    + case DDCCI_MONITOR_BL_WHITE:
    + return "backlight";
    + case DDCCI_MONITOR_LUMINANCE:
    + return "luminance";
    + default:
    + return "???";
    + }
    +}
    +
    +static const char *ddcci_monitor_next_vcp_item(const char *ptr)
    +{
    + int depth = 0;
    +
    + /* Sanity check */
    + if (ptr == NULL || ptr[0] == '\0')
    + return NULL;
    +
    + /* Find next white space outside of parentheses */
    + while ((ptr = strpbrk(ptr, " ()"))) {
    + if (!ptr || depth == INT_MAX)
    + return NULL;
    + else if (*ptr == '(')
    + depth++;
    + else if (depth > 0) {
    + if (*ptr == ')')
    + depth--;
    + } else
    + break;
    + ++ptr;
    + }
    +
    + /* Skip over whitespace */
    + ptr = skip_spaces(ptr);
    +
    + /* Check if we're now at the end of the list */
    + if (*ptr == '\0' || *ptr == ')')
    + return NULL;
    +
    + return ptr;
    +}
    +
    +static bool ddcci_monitor_find_vcp(unsigned char vcp, const char *s)
    +{
    + const char *ptr = s;
    + char vcp_hex[3];
    +
    + /* Sanity check */
    + if (s == NULL || s[0] == '\0')
    + return false;
    +
    + /* Create hex representation of VCP */
    + if (snprintf(vcp_hex, 3, "%02hhX", vcp) != 2) {
    + pr_err("snprintf failed to convert to hex. This should not happen.\n");
    + return false;
    + }
    +
    + /* Search for it */
    + do {
    + if (strncasecmp(vcp_hex, ptr, 2) == 0) {
    + if (ptr[2] == ' ' || ptr[2] == '(' || ptr[2] == ')')
    + return true;
    + }
    + } while ((ptr = ddcci_monitor_next_vcp_item(ptr)));
    +
    + return false;
    +}
    +
    +static int ddcci_backlight_create_symlink(struct ddcci_device *ddcci_dev)
    +{
    + int i, result;
    + struct device *dev = &ddcci_dev->dev;
    + struct kernfs_node *dirent;
    +
    + for (i = 0; i < 3; ++i) {
    + dev = dev->parent;
    + if (!dev) {
    + dev_dbg(&ddcci_dev->dev, "failed to create convenience symlink: ancestor device not found\n");
    + return -ENOENT;
    + }
    + }
    + dirent = sysfs_get_dirent(dev->kobj.sd, "ddcci_backlight");
    + if (dirent) {
    + sysfs_put(dirent);
    + dev_dbg(&ddcci_dev->dev, "failed to create convenience symlink: %s/ddcci_backlight already exists\n", dev_name(dev));
    + return -EEXIST;
    + }
    +
    + result = sysfs_create_link(&dev->kobj, &ddcci_dev->dev.kobj, "ddcci_backlight");
    + if (result == 0)
    + dev_dbg(&ddcci_dev->dev, "created symlink %s/ddcci_backlight\n", dev_name(dev));
    + else
    + dev_info(&ddcci_dev->dev, "failed to create convenience symlink: %d\n", result);
    + return result;
    +}
    +
    +static int ddcci_backlight_remove_symlink(struct ddcci_device *ddcci_dev)
    +{
    + int i;
    + struct device *dev = &ddcci_dev->dev;
    + struct kernfs_node *dirent;
    +
    + for (i = 0; i < 3; ++i) {
    + dev = dev->parent;
    + if (!dev)
    + return -ENOENT;
    + }
    + dirent = sysfs_get_dirent(dev->kobj.sd, "ddcci_backlight");
    + if (!dirent)
    + return -ENOENT;
    +
    + if ((dirent->flags & KERNFS_LINK) == 0) {
    + sysfs_put(dirent);
    + dev_dbg(&ddcci_dev->dev, "won't remove %s/ddcci_backlight: not a symlink\n", dev_name(dev));
    + return -EINVAL;
    + }
    +
    + if (dirent->symlink.target_kn != ddcci_dev->dev.kobj.sd) {
    + sysfs_put(dirent);
    + dev_dbg(&ddcci_dev->dev, "won't remove %s/ddcci_backlight: we are not the link target\n", dev_name(dev));
    + return -EINVAL;
    + }
    +
    + sysfs_put(dirent);
    +
    + sysfs_remove_link(&dev->kobj, "ddcci_backlight");
    + dev_dbg(&ddcci_dev->dev, "removed symlink %s/ddcci_backlight\n", dev_name(dev));
    + return 0;
    +}
    +
    +static int ddcci_monitor_probe(struct ddcci_device *dev,
    + const struct ddcci_device_id *id)
    +{
    + struct ddcci_monitor_drv_data *drv_data;
    + struct backlight_properties props;
    + struct backlight_device *bl = NULL;
    + int ret = 0;
    + bool support_luminance, support_bl_white;
    + unsigned short brightness = 0, max_brightness = 0;
    + const char *vcps;
    +
    + dev_dbg(&dev->dev, "probing monitor backlight device\n");
    +
    + /* Get VCP list */
    + vcps = ddcci_find_capstr_item(dev->capabilities, "vcp", NULL);
    + if (IS_ERR(vcps)) {
    + dev_info(&dev->dev,
    + "monitor doesn't provide a list of supported controls.\n");
    + support_bl_white = support_luminance = true;
    + } else {
    + /* Check VCP list for supported VCPs */
    + support_bl_white = ddcci_monitor_find_vcp(DDCCI_MONITOR_BL_WHITE, vcps);
    + support_luminance = ddcci_monitor_find_vcp(DDCCI_MONITOR_LUMINANCE, vcps);
    + /* Fallback to trying if no support is found */
    + if (!support_bl_white && !support_luminance) {
    + dev_info(&dev->dev,
    + "monitor doesn't announce support for backlight or luminance controls.\n");
    + support_bl_white = support_luminance = true;
    + }
    + }
    +
    + /* Initialize driver data structure */
    + drv_data = devm_kzalloc(&dev->dev, sizeof(struct ddcci_monitor_drv_data),
    + GFP_KERNEL);
    + if (!drv_data)
    + return -ENOMEM;
    + drv_data->device = dev;
    +
    + if (support_bl_white) {
    + /* Try getting backlight level */
    + dev_dbg(&dev->dev,
    + "trying to access \"backlight level white\" control\n");
    + ret = ddcci_monitor_readctrl(drv_data->device, DDCCI_MONITOR_BL_WHITE,
    + &brightness, &max_brightness);
    + if (ret < 0) {
    + if (ret == -ENOTSUPP)
    + dev_info(&dev->dev,
    + "monitor does not support reading backlight level\n");
    + else
    + goto err_free;
    + } else {
    + drv_data->used_vcp = DDCCI_MONITOR_BL_WHITE;
    + }
    + }
    +
    + if (support_luminance && !drv_data->used_vcp) {
    + /* Try getting luminance */
    + dev_dbg(&dev->dev,
    + "trying to access \"luminance\" control\n");
    + ret = ddcci_monitor_readctrl(drv_data->device, DDCCI_MONITOR_LUMINANCE,
    + &brightness, &max_brightness);
    + if (ret < 0) {
    + if (ret == -ENOTSUPP)
    + dev_info(&dev->dev,
    + "monitor does not support reading luminance\n");
    + else
    + goto err_free;
    + } else {
    + drv_data->used_vcp = DDCCI_MONITOR_LUMINANCE;
    + }
    + drv_data->used_vcp = DDCCI_MONITOR_LUMINANCE;
    + }
    +
    + if (!drv_data->used_vcp)
    + goto err_free;
    +
    + /* Create brightness device */
    + memset(&props, 0, sizeof(props));
    + props.type = BACKLIGHT_RAW;
    + props.max_brightness = max_brightness;
    + props.brightness = brightness;
    + bl = devm_backlight_device_register(&dev->dev, dev_name(&dev->dev),
    + &dev->dev, drv_data,
    + &ddcci_backlight_ops, &props);
    + drv_data->bl_dev = bl;
    + if (IS_ERR(bl)) {
    + dev_err(&dev->dev, "failed to register backlight\n");
    + return PTR_ERR(bl);
    + }
    + dev_info(&dev->dev, "registered %s as backlight device %s\n",
    + ddcci_monitor_vcp_name(drv_data->used_vcp),
    + dev_name(&dev->dev));
    +
    + if (convenience_symlink)
    + ddcci_backlight_create_symlink(dev);
    +
    + goto end;
    +err_free:
    + devm_kfree(&dev->dev, drv_data);
    +end:
    + return ret;
    +}
    +
    +static int ddcci_monitor_remove(struct ddcci_device *dev)
    +{
    + dev_dbg(&dev->dev, "removing device\n");
    + ddcci_backlight_remove_symlink(dev);
    + return 0;
    +}
    +
    +static struct ddcci_device_id ddcci_monitor_idtable[] = {
    + { "monitor", DDCCI_ANY_ID, DDCCI_ANY_ID, DDCCI_ANY_ID, DDCCI_ANY_ID, 0 },
    + {}
    +};
    +
    +static struct ddcci_driver ddcci_backlight_driver = {
    + .driver = {
    + .name = "ddcci-backlight",
    + .owner = THIS_MODULE,
    + },
    +
    + .id_table = ddcci_monitor_idtable,
    + .probe = ddcci_monitor_probe,
    + .remove = ddcci_monitor_remove,
    +};
    +
    +module_ddcci_driver(ddcci_backlight_driver);
    +
    +/* Module parameter description */
    +module_param(convenience_symlink, bool, 0644);
    +MODULE_PARM_DESC(convenience_symlink, "add convenience symlink \"ddcci_backlight\" to ancestor device in sysfs (default true)");
    +
    +MODULE_AUTHOR("Christoph Grenz <christophg+lkml@grenz-bonn.de>");
    +MODULE_DESCRIPTION("DDC/CI generic monitor backlight driver");
    +MODULE_VERSION("0.4.2");
    +MODULE_LICENSE("GPL");
    +
    +MODULE_ALIAS("ddcci:monitor-*-*-*-*");
    --
    2.25.1
    \
     
     \ /
      Last update: 2022-04-04 01:10    [W:2.725 / U:0.060 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site