The Kinetic KTD253 backlight driver is controlled with a single GPIO line, but still supports a range of brightness settings by sending fast pulses on the line. This is based off the source code release for the Samsung GT-S7710 mobile phone. Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxx> --- MAINTAINERS | 6 + drivers/video/backlight/Kconfig | 8 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/ktd253-backlight.c | 254 +++++++++++++++++++++ 4 files changed, 269 insertions(+) create mode 100644 drivers/video/backlight/ktd253-backlight.c diff --git a/MAINTAINERS b/MAINTAINERS index b4a43a9e7fbc..ea6fcc5bb79e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9610,6 +9610,12 @@ F: Documentation/admin-guide/auxdisplay/ks0108.rst F: drivers/auxdisplay/ks0108.c F: include/linux/ks0108.h +KTD253 BACKLIGHT DRIVER +M: Linus Walleij <linus.walleij@xxxxxxxxxx> +S: Maintained +F: Documentation/devicetree/bindings/leds/backlight/kinetic,ktd253.yaml +F: drivers/video/backlight/ktd253-backlight.c + L3MDEV M: David Ahern <dsahern@xxxxxxxxxx> L: netdev@xxxxxxxxxxxxxxx diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 7d22d7377606..6a74c60707b4 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -190,6 +190,14 @@ config BACKLIGHT_IPAQ_MICRO computers. Say yes if you have one of the h3100/h3600/h3700 machines. +config BACKLIGHT_KTD253 + tristate "Backlight Driver for Kinetic KTD253" + depends on GPIOLIB || COMPILE_TEST + help + Say y to enabled the backlight driver for the Kinetic KTD253 + which is a 1-wire GPIO-controlled backlight found in some mobile + phones. + config BACKLIGHT_LM3533 tristate "Backlight Driver for LM3533" depends on MFD_LM3533 diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 0c1a1524627a..d50cd12574ae 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_BACKLIGHT_GPIO) += gpio_backlight.o obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o obj-$(CONFIG_BACKLIGHT_HP700) += jornada720_bl.o obj-$(CONFIG_BACKLIGHT_IPAQ_MICRO) += ipaq_micro_bl.o +obj-$(CONFIG_BACKLIGHT_KTD253) += ktd253-backlight.o obj-$(CONFIG_BACKLIGHT_LM3533) += lm3533_bl.o obj-$(CONFIG_BACKLIGHT_LM3630A) += lm3630a_bl.o obj-$(CONFIG_BACKLIGHT_LM3639) += lm3639_bl.o diff --git a/drivers/video/backlight/ktd253-backlight.c b/drivers/video/backlight/ktd253-backlight.c new file mode 100644 index 000000000000..d460d1fef329 --- /dev/null +++ b/drivers/video/backlight/ktd253-backlight.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Backlight driver for the Kinetic KTD253 + * Based on code and know-how from the Samsung GT-S7710 + * Gareth Phillips <gareth.phillips@xxxxxxxxxxx> + */ +#include <linux/backlight.h> +#include <linux/err.h> +#include <linux/fb.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/limits.h> + +/* Current ratio is n/32 from 1/32 to 32/32 */ +#define KTD253_MIN_RATIO 1 +#define KTD253_MAX_RATIO 32 +#define KTD253_DEFAULT_RATIO 13 + +/* With the table we use this is 24/32 current ratio actually */ +#define KTD253_MAX_BRIGHTNESS 255 +#define KTD253_DEFAULT_BRIGHTNESS 160 + +#define KTD253_T_LOW_NS (200 + 10) /* Additional 10ns as safety factor */ +#define KTD253_T_HIGH_NS (200 + 10) /* Additional 10ns as safety factor */ +#define KTD253_T_OFF_MS 3 + +struct ktd253_backlight { + struct device *dev; + struct gpio_desc *gpiod; + u16 ratio; + unsigned int brightness; +}; + +/* + * The following table is used to convert brightness level to the LED + * Current Ratio expressed as (full current) /(n * 32). + * i.e. 1 = 1/32 full current. Zero indicates LED is powered off. + * The table is intended to allow the brightness level to be "tuned" + * to compensate for non-linearity of brightness relative to current. + */ +static const u16 ktd253_brightness_to_current_ratio[] = { + 0, /* (0/32) KTD253_BACKLIGHT_OFF */ + 39, /* (1/32) KTD253_MIN_RATIO */ + 58, /* (2/32) */ + 67, /* (3/32) */ + 76, /* (4/32) */ + 85, /* (5/32) */ + 94, /* (6/32) */ + 104, /* (7/32) */ + 113, /* (8/32) */ + 122, /* (9/32) */ + 131, /* (10/32) */ + 145, /* (11/32) */ + 159, /* (12/32) */ + 169, /* (13/32) */ + 179, /* (14/32) */ + 189, /* (15/32) */ + 196, /* (16/32) */ + 203, /* (17/32) */ + 210, /* (18/32) */ + 217, /* (19/32) */ + 224, /* (20/32) */ + 231, /* (21/32) */ + 238, /* (22/32) */ + 245, /* (23/32) */ + 255, /* (24/32) */ + 300, /* (25/32) */ + 300, /* (26/32) */ + 300, /* (27/32) */ + 300, /* (28/32) */ + 300, /* (29/32) */ + 300, /* (30/32) */ + 300, /* (31/32) */ + 300 /* (32/32) KTD253_MAX_RATIO */ + +}; + +/* Inspired by gpio_bl.c */ +static int ktd253_backlight_get_next_brightness(struct backlight_device *bl) +{ + int brightness = bl->props.brightness; + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.fb_blank != FB_BLANK_UNBLANK || + bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) + brightness = 0; + + return brightness; +} + +static int ktd253_backlight_update_status(struct backlight_device *bl) +{ + struct ktd253_backlight *ktd253 = bl_get_data(bl); + int brightness = ktd253_backlight_get_next_brightness(bl); + u16 target_ratio; + u16 current_ratio = ktd253->ratio; + unsigned long flags; + + dev_dbg(ktd253->dev, "new brightness: %d\n", brightness); + + /* Look up the current ratio */ + for (target_ratio = KTD253_MAX_RATIO; target_ratio > 0; target_ratio--) { + if (brightness > ktd253_brightness_to_current_ratio[target_ratio - 1]) + break; + } + + dev_dbg(ktd253->dev, "new ratio: %d/32\n", target_ratio); + + if (target_ratio == current_ratio) + /* This is already right */ + return 0; + + if (target_ratio == 0) { + gpiod_set_value_cansleep(ktd253->gpiod, 0); + /* + * We need to keep the GPIO low for at least this long + * to actually switch the KTD253 off. + */ + msleep(KTD253_T_OFF_MS); + ktd253->ratio = 0; + return 0; + } + + if (current_ratio == 0) { + gpiod_set_value_cansleep(ktd253->gpiod, 1); + ndelay(KTD253_T_HIGH_NS); + /* We always fall back to this when we power on */ + current_ratio = KTD253_MAX_RATIO; + } + + /* + * WARNING: + * The loop to set the correct current level is performed + * with interrupts disabled as it is timing critical. + * The maximum number of cycles of the loop is 32 + * so the time taken will be (T_LOW_NS + T_HIGH_NS + loop_time) * 32, + */ + local_irq_save(flags); + while (current_ratio != target_ratio) { + /* + * These GPIO operations absolutely can NOT sleep so no + * _cansleep suffixes, and no using GPIO expanders on + * slow buses for this! + */ + gpiod_set_value(ktd253->gpiod, 0); + ndelay(KTD253_T_LOW_NS); + gpiod_set_value(ktd253->gpiod, 1); + ndelay(KTD253_T_HIGH_NS); + /* After 1/32 we loop back to 32/32 */ + if (current_ratio == KTD253_MIN_RATIO) + current_ratio = KTD253_MAX_RATIO; + else + current_ratio--; + } + local_irq_restore(flags); + ktd253->ratio = current_ratio; + + dev_dbg(ktd253->dev, "new ratio set to %d/32\n", target_ratio); + + return 0; +} + +static const struct backlight_ops ktd253_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = ktd253_backlight_update_status, +}; + +static int ktd253_backlight_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct backlight_device *bl; + struct ktd253_backlight *ktd253; + u32 max_brightness; + u32 brightness; + int ret; + + ktd253 = devm_kzalloc(dev, sizeof(*ktd253), GFP_KERNEL); + if (!ktd253) + return -ENOMEM; + ktd253->dev = dev; + + ret = device_property_read_u32(dev, "max-brightness", &max_brightness); + if (ret) + max_brightness = KTD253_MAX_BRIGHTNESS; + + ret = device_property_read_u32(dev, "default-brightness", &brightness); + if (ret) + brightness = KTD253_DEFAULT_BRIGHTNESS; + + if (brightness) + /* This will be the default ratio when the KTD253 is enabled */ + ktd253->ratio = KTD253_MAX_RATIO; + else + ktd253->ratio = 0; + + ktd253->gpiod = devm_gpiod_get(dev, NULL, + brightness ? GPIOD_OUT_HIGH : + GPIOD_OUT_LOW); + if (IS_ERR(ktd253->gpiod)) { + ret = PTR_ERR(ktd253->gpiod); + if (ret != -EPROBE_DEFER) + dev_err(dev, "gpio line missing or invalid.\n"); + return ret; + } + gpiod_set_consumer_name(ktd253->gpiod, dev_name(dev)); + + bl = devm_backlight_device_register(dev, dev_name(dev), dev, ktd253, + &ktd253_backlight_ops, NULL); + if (IS_ERR(bl)) { + dev_err(dev, "failed to register backlight\n"); + return PTR_ERR(bl); + } + bl->props.max_brightness = max_brightness; + /* When we just enable the GPIO line we set max brightness */ + if (brightness) { + bl->props.brightness = brightness; + bl->props.power = FB_BLANK_UNBLANK; + ktd253_backlight_update_status(bl); + } else { + bl->props.brightness = 0; + bl->props.power = FB_BLANK_POWERDOWN; + } + + platform_set_drvdata(pdev, bl); + + return 0; +} + +static const struct of_device_id ktd253_backlight_of_match[] = { + { .compatible = "kinetic,ktd253" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ktd253_backlight_of_match); + +static struct platform_driver ktd253_backlight_driver = { + .driver = { + .name = "ktd253-backlight", + .of_match_table = ktd253_backlight_of_match, + }, + .probe = ktd253_backlight_probe, +}; +module_platform_driver(ktd253_backlight_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Kinetic KTD253 Backlight Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ktd253-backlight"); -- 2.26.2 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel