Backlight for v7.3

- New Support & Features
   - Silergy SY7758: Add driver for the 6-channel high-efficiency LED driver
 
 - Improvements & Fixes
   - Awinic AW99706: Fix device tree property names to match the binding, consistently
     validate all property values, and honor the core blank state in `update_status()`
   - Kinetic KTD2801: Add missing dependency on `GPIOLIB` in Kconfig
   - Qualcomm WLED: Remove redundant `dev_err()` calls
 
 - Cleanups & Refactoring
   - Core: Use `sysfs_emit()` instead of `sprintf()` in sysfs show callbacks
   - Maintainers: Update Junjie Cao's email address for the Awinic AW99706 driver
 
 - Device Tree Binding Updates
   - Marvell 88PM860X: Add missing bracket in the example
   - Silergy SY7758: Document the 6-channel high-efficiency LED driver
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmqPE2oACgkQUa+KL4f8
 d2FmPA//dBm/TLnut7WnXl7gksJzn0HfvrMvEVAFBn1pfNOZapoXkspGkCQJpSrs
 5Y2jg+DlYehBK9SgRB/1T+nRimti5JgBRrUGB28AzXQk4WmkPcLrFREuXDweTZJZ
 HuAxMyEIBVFAfgQhvc7RiyWJtP6E4t0hqc98PQUzSG+duGLsUbb/cvhPlCTJTAxO
 WET9WOxwW9IhdA110N4rnDfio5sTD4hH4iE4hlXuQsBJFTpZzf+fsDWA8IPONmUR
 K4/9LK58QSES0SWnle9maZ4ibwePYaaPH5PQWtCIilhlddqpYAh/Xtl4N4mOFEiK
 FKdbZN/NBWeVbhj/Hpjxutyjvk5dF7mz2/DwuO2xKLpWppg1eGzfG5b4hW+fUPW2
 txmQcRN6Kazlskg0BCeIC6TOvEQeRmrR2OICh758ptRC/rfTG2pvV8rt7PzVplEp
 WPNbu/iRl+hzwrTssREwIeOQ1mLPRGhuXUnlB9nNX/AhcMVCoBgkdwA8Tf6ZPPIN
 dKTDshrJ4jjVuenBcjTA6WnLCcqywAtYpZfH8YSZafgDgK8BjtW6dapGFvP4XOLz
 Bnic9yJZpKelcuwOm2uj9cgbwenoAcrj9yr00iqgXrBPMEhVvkg9xbJUh+8D6yF0
 Vz/AjzlYcwEwpMbfHewyA2B50zu+BfjDiM6mTu0C8n50BYEx71s=
 =8Tc7
 -----END PGP SIGNATURE-----

Merge tag 'backlight-next-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/backlight

Pull backlight updates from Lee Jones:
 "New Support & Features:
   - Silergy SY7758: Add driver for the 6-channel high-efficiency LED
     driver

  Improvements & Fixes
   - Awinic AW99706: Fix device tree property names to match the
     binding, consistently validate all property values, and honor the
     core blank state in `update_status()`
   - Kinetic KTD2801: Add missing dependency on `GPIOLIB` in Kconfig
   - Qualcomm WLED: Remove redundant `dev_err()` calls

  Cleanups & Refactoring
   - Core: Use `sysfs_emit()` instead of `sprintf()` in sysfs show
     callbacks
   - Maintainers: Update Junjie Cao's email address for the Awinic
     AW99706 driver

  Device Tree Binding Updates
   - Marvell 88PM860X: Add missing bracket in the example
   - Silergy SY7758: Document the 6-channel high-efficiency LED driver"

* tag 'backlight-next-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/backlight:
  backlight: Use sysfs_emit() instead of sprintf()
  backlight: qcom-wled: Remove redundant dev_err()
  backlight: ktd2801: Fix unmet dependency on GPIOLIB
  backlight: aw99706: Honor the core blank state in update_status()
  backlight: aw99706: Validate all DT property values consistently
  backlight: aw99706: Fix DT property names to match binding
  dt-bindings: backlight: 88pm860x: Add missing bracket
  MAINTAINERS: Update my email address for the AW99706 backlight driver
  backlight: Add SY7758 6-channel High Efficiency LED Driver support
  dt-bindings: leds: backlight: Document the SY7758 6-channel High Efficiency LED Driver
This commit is contained in:
Linus Torvalds 2026-08-27 11:16:39 -07:00
commit f7e3f4d9f4
11 changed files with 350 additions and 26 deletions

View File

@ -13,3 +13,4 @@ Example:
};
backlight-2 {
};
};

View File

@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
title: Awinic AW99706 6-channel WLED Backlight Driver
maintainers:
- Junjie Cao <caojunjie650@gmail.com>
- Junjie Cao <junjie.cao@linux.dev>
allOf:
- $ref: common.yaml#

View File

@ -0,0 +1,52 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/backlight/silergy,sy7758.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Silergy SY7758 6-channel High Efficiency LED Driver
maintainers:
- Neil Armstrong <neil.armstrong@linaro.org>
description:
Silergy SY7758 is a high efficiency 6-channels LED backlight
driver with I2C brightness control.
allOf:
- $ref: common.yaml#
properties:
compatible:
const: silergy,sy7758
reg:
maxItems: 1
vdd-supply: true
enable-gpios:
maxItems: 1
required:
- compatible
- reg
- enable-gpios
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
backlight@11 {
compatible = "silergy,sy7758";
reg = <0x11>;
vdd-supply = <&bl_vdd>;
enable-gpios = <&gpio 16 GPIO_ACTIVE_HIGH>;
};
};

View File

@ -4420,7 +4420,7 @@ S: Maintained
F: drivers/media/pci/hws/
AWINIC AW99706 WLED BACKLIGHT DRIVER
M: Junjie Cao <caojunjie650@gmail.com>
M: Junjie Cao <junjie.cao@linux.dev>
S: Maintained
F: Documentation/devicetree/bindings/leds/backlight/awinic,aw99706.yaml
F: drivers/video/backlight/aw99706.c

View File

@ -193,7 +193,7 @@ config BACKLIGHT_KTD253
config BACKLIGHT_KTD2801
tristate "Backlight Driver for Kinetic KTD2801"
depends on GPIOLIB || COMPILE_TEST
depends on GPIOLIB
select LEDS_EXPRESSWIRE
help
Say Y to enable the backlight driver for the Kinetic KTD2801 1-wire
@ -207,6 +207,14 @@ config BACKLIGHT_KTZ8866
Say Y to enable the backlight driver for the Kinetic KTZ8866
found in Xiaomi Mi Pad 5 series.
config BACKLIGHT_SY7758
tristate "Backlight Driver for Silergy SY7758"
depends on I2C
select REGMAP_I2C
help
Say Y to enable the backlight driver for the Silergy SY7758
backlight controller found in Ayaneo Pocket S2.
config BACKLIGHT_LM3533
tristate "Backlight Driver for LM3533"
depends on MFD_LM3533

View File

@ -57,6 +57,7 @@ obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o
obj-$(CONFIG_BACKLIGHT_QCOM_WLED) += qcom-wled.o
obj-$(CONFIG_BACKLIGHT_RT4831) += rt4831-backlight.o
obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o
obj-$(CONFIG_BACKLIGHT_SY7758) += sy7758.o
obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o
obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o
obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o

View File

@ -60,7 +60,7 @@
#define AW99706_MTPLDOSEL_REG 0x1E
#define AW99706_MTPRUN_REG 0x1F
#define RESV 0
#define RESV U32_MAX
/* Boost switching frequency table, in Hz */
static const u32 aw99706_sw_freq_tbl[] = {
@ -94,17 +94,19 @@ static int aw99706_dt_property_lookup(const struct aw99706_dt_prop *prop,
int i;
if (!prop->lookup_tbl) {
if (dt_val > (prop->mask >> __ffs(prop->mask)))
return -EINVAL;
*val = dt_val;
return 0;
}
for (i = 0; i < prop->tbl_size; i++)
if (prop->lookup_tbl[i] == dt_val)
if (prop->lookup_tbl[i] != RESV && prop->lookup_tbl[i] == dt_val)
break;
*val = i;
return i == prop->tbl_size ? -1 : 0;
return i == prop->tbl_size ? -EINVAL : 0;
}
#define MIN_ILED_MAX 5000
@ -116,11 +118,14 @@ aw99706_dt_property_iled_max_convert(const struct aw99706_dt_prop *prop,
u32 dt_val, u8 *val)
{
if (dt_val > MAX_ILED_MAX || dt_val < MIN_ILED_MAX)
return -1;
return -EINVAL;
if ((dt_val - MIN_ILED_MAX) % STEP_ILED_MAX)
return -EINVAL;
*val = (dt_val - MIN_ILED_MAX) / STEP_ILED_MAX;
return (dt_val - MIN_ILED_MAX) % STEP_ILED_MAX;
return 0;
}
static const struct aw99706_dt_prop aw99706_dt_props[] = {
@ -130,23 +135,23 @@ static const struct aw99706_dt_prop aw99706_dt_props[] = {
AW99706_CFG0_REG, AW99706_DIM_MODE_MASK, 1,
},
{
"awinic,sw-freq", aw99706_dt_property_lookup,
"awinic,sw-freq-hz", aw99706_dt_property_lookup,
aw99706_sw_freq_tbl, ARRAY_SIZE(aw99706_sw_freq_tbl),
AW99706_CFG1_REG, AW99706_SW_FREQ_MASK, 750000,
},
{
"awinic,sw-ilmt", aw99706_dt_property_lookup,
"awinic,sw-ilmt-microamp", aw99706_dt_property_lookup,
aw99706_sw_ilmt_tbl, ARRAY_SIZE(aw99706_sw_ilmt_tbl),
AW99706_CFG1_REG, AW99706_SW_ILMT_MASK, 3000000,
},
{
"awinic,iled-max", aw99706_dt_property_iled_max_convert,
"awinic,iled-max-microamp", aw99706_dt_property_iled_max_convert,
NULL, 0,
AW99706_CFG2_REG, AW99706_ILED_MAX_MASK, 20000,
},
{
"awinic,uvlo-thres", aw99706_dt_property_lookup,
"awinic,uvlo-thres-microvolt", aw99706_dt_property_lookup,
aw99706_ulvo_thres_tbl, ARRAY_SIZE(aw99706_ulvo_thres_tbl),
AW99706_CFG2_REG, AW99706_UVLOSEL_MASK, 2200000,
},
@ -335,7 +340,7 @@ static int aw99706_bl_update_status(struct backlight_device *bl)
{
struct aw99706_device *aw = bl_get_data(bl);
return aw99706_update_brightness(aw, bl->props.brightness);
return aw99706_update_brightness(aw, backlight_get_brightness(bl));
}
static const struct backlight_ops aw99706_bl_ops = {

View File

@ -139,7 +139,7 @@ static ssize_t bl_power_show(struct device *dev, struct device_attribute *attr,
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.power);
return sysfs_emit(buf, "%d\n", bd->props.power);
}
static ssize_t bl_power_store(struct device *dev, struct device_attribute *attr,
@ -180,7 +180,7 @@ static ssize_t brightness_show(struct device *dev,
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.brightness);
return sysfs_emit(buf, "%d\n", bd->props.brightness);
}
int backlight_device_set_brightness(struct backlight_device *bd,
@ -228,7 +228,7 @@ static ssize_t type_show(struct device *dev, struct device_attribute *attr,
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%s\n", backlight_types[bd->props.type]);
return sysfs_emit(buf, "%s\n", backlight_types[bd->props.type]);
}
static DEVICE_ATTR_RO(type);
@ -237,7 +237,7 @@ static ssize_t max_brightness_show(struct device *dev,
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.max_brightness);
return sysfs_emit(buf, "%d\n", bd->props.max_brightness);
}
static DEVICE_ATTR_RO(max_brightness);
@ -251,9 +251,9 @@ static ssize_t actual_brightness_show(struct device *dev,
if (bd->ops && bd->ops->get_brightness) {
rc = bd->ops->get_brightness(bd);
if (rc >= 0)
rc = sprintf(buf, "%d\n", rc);
rc = sysfs_emit(buf, "%d\n", rc);
} else {
rc = sprintf(buf, "%d\n", bd->props.brightness);
rc = sysfs_emit(buf, "%d\n", bd->props.brightness);
}
mutex_unlock(&bd->ops_lock);
@ -267,9 +267,9 @@ static ssize_t scale_show(struct device *dev,
struct backlight_device *bd = to_backlight_device(dev);
if (WARN_ON(bd->props.scale > BACKLIGHT_SCALE_NON_LINEAR))
return sprintf(buf, "unknown\n");
return sysfs_emit(buf, "unknown\n");
return sprintf(buf, "%s\n", backlight_scale_types[bd->props.scale]);
return sysfs_emit(buf, "%s\n", backlight_scale_types[bd->props.scale]);
}
static DEVICE_ATTR_RO(scale);

View File

@ -77,7 +77,7 @@ static ssize_t lcd_power_show(struct device *dev, struct device_attribute *attr,
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->get_power)
rc = sprintf(buf, "%d\n", ld->ops->get_power(ld));
rc = sysfs_emit(buf, "%d\n", ld->ops->get_power(ld));
else
rc = -ENXIO;
mutex_unlock(&ld->ops_lock);
@ -118,7 +118,7 @@ static ssize_t contrast_show(struct device *dev,
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->get_contrast)
rc = sprintf(buf, "%d\n", ld->ops->get_contrast(ld));
rc = sysfs_emit(buf, "%d\n", ld->ops->get_contrast(ld));
mutex_unlock(&ld->ops_lock);
return rc;
@ -154,7 +154,7 @@ static ssize_t max_contrast_show(struct device *dev,
{
struct lcd_device *ld = to_lcd_device(dev);
return sprintf(buf, "%d\n", ld->props.max_contrast);
return sysfs_emit(buf, "%d\n", ld->props.max_contrast);
}
static DEVICE_ATTR_RO(max_contrast);

View File

@ -1639,8 +1639,6 @@ static int wled_configure_ovp_irq(struct wled *wled,
wled_ovp_irq_handler, IRQF_ONESHOT,
"wled_ovp_irq", wled);
if (rc < 0) {
dev_err(wled->dev, "Unable to request ovp_irq (err:%d)\n",
rc);
wled->ovp_irq = 0;
return 0;
}

View File

@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Silergy SY7758 6-channel High Efficiency LED Driver
*
* Copyright (C) 2025 Kancy Joe <kancy2333@outlook.com>
* Copyright (C) 2026 Linaro Limited
* Author: Neil Armstrong <neil.armstrong@linaro.org>
*/
#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/bitfield.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#define DEFAULT_BRIGHTNESS 1024
#define MAX_BRIGHTNESS 4080
#define REG_MAX 0xAE
/* Registers */
#define REG_DEV_CTL 0x01
#define REG_DEV_ID 0x03
#define REG_BRT_12BIT_L 0x10
#define REG_BRT_12BIT_H 0x11
/* OTP memory */
#define REG_OTP_CFG0 0xA0
#define REG_OTP_CFG1 0xA1
#define REG_OTP_CFG2 0xA2
#define REG_OTP_CFG5 0xA5
#define REG_OTP_CFG9 0xA9
/* Fields */
#define BIT_DEV_CTL_FAST BIT(7)
#define MSK_DEV_CTL_BRT_MODE GENMASK(2, 1)
#define BIT_DEV_CTL_BL_CTLB BIT(0)
#define MSK_BRT_12BIT_L GENMASK(7, 0)
#define MSK_BRT_12BIT_H GENMASK(3, 0)
#define MSK_CFG0_CURRENT_LOW GENMASK(7, 0)
#define BIT_CFG1_PDET_STDBY BIT(7)
#define MSK_CFG1_CURRENT_MAX GENMASK(6, 4)
#define MSK_CFG1_CURRENT_HIGH GENMASK(3, 0)
#define BIT_CFG2_UVLO_EN BIT(5)
#define BIT_CFG2_UVLO_TH BIT(4)
#define BIT_CFG2_BL_ON BIT(3)
#define BIT_CFG2_ISET_EN BIT(2)
#define BIT_CFG2_BST_ESET_EN BIT(1)
#define BIT_CFG5_PWM_DIRECT BIT(7)
#define MSK_CFG5_PS_MODE GENMASK(6, 4)
#define MSK_CFG5_PWM_FREQ GENMASK(3, 0)
#define MSK_CFG9_VBST_MAX GENMASK(7, 5)
#define BIT_CFG9_JUMP_EN BIT(4)
#define MSK_CFG9_JUMP_TH GENMASK(3, 2)
#define MSK_CFG9_JUMP_VOLTAGE GENMASK(1, 0)
struct sy7758 {
struct i2c_client *client;
struct regmap *regmap;
struct gpio_desc *gpio;
struct backlight_device *bl;
};
static const struct regmap_config sy7758_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = REG_MAX,
};
static int sy7758_backlight_update_status(struct backlight_device *backlight_dev)
{
struct sy7758 *sydev = bl_get_data(backlight_dev);
unsigned int brightness = backlight_get_brightness(backlight_dev);
int ret;
ret = regmap_write(sydev->regmap, REG_BRT_12BIT_L,
FIELD_PREP(MSK_BRT_12BIT_L,
brightness & 0xff));
if (ret)
return ret;
ret = regmap_write(sydev->regmap, REG_BRT_12BIT_H,
FIELD_PREP(MSK_BRT_12BIT_H,
(brightness >> 8) & 0xf));
if (ret)
return ret;
return 0;
}
static const struct backlight_ops sy7758_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = sy7758_backlight_update_status,
};
static int sy7758_init(struct sy7758 *sydev)
{
int ret = 0;
ret = regmap_write(sydev->regmap, REG_DEV_CTL,
BIT_DEV_CTL_FAST | BIT_DEV_CTL_BL_CTLB |
FIELD_PREP(MSK_DEV_CTL_BRT_MODE, 2));
if (ret)
return ret;
ret = regmap_write(sydev->regmap, REG_BRT_12BIT_L,
FIELD_PREP(MSK_BRT_12BIT_L,
DEFAULT_BRIGHTNESS & 0xff));
if (ret)
return ret;
ret = regmap_write(sydev->regmap, REG_BRT_12BIT_H,
FIELD_PREP(MSK_BRT_12BIT_H,
(DEFAULT_BRIGHTNESS >> 8)));
if (ret)
return ret;
ret = regmap_write(sydev->regmap, REG_OTP_CFG5,
FIELD_PREP(MSK_CFG5_PS_MODE, 6) |
FIELD_PREP(MSK_CFG5_PWM_FREQ, 4));
if (ret)
return ret;
ret = regmap_write(sydev->regmap, REG_OTP_CFG0,
FIELD_PREP(MSK_CFG0_CURRENT_LOW, 85));
if (ret)
return ret;
ret = regmap_write(sydev->regmap, REG_OTP_CFG1,
BIT_CFG1_PDET_STDBY |
FIELD_PREP(MSK_CFG1_CURRENT_MAX, 1) |
FIELD_PREP(MSK_CFG1_CURRENT_HIGH, 10));
if (ret)
return ret;
ret = regmap_write(sydev->regmap, REG_OTP_CFG9,
FIELD_PREP(MSK_CFG9_VBST_MAX, 4));
if (ret)
return ret;
ret = regmap_write(sydev->regmap, REG_OTP_CFG2,
BIT_CFG2_BL_ON | BIT_CFG2_UVLO_EN);
if (ret)
return ret;
return 0;
}
static int sy7758_probe(struct i2c_client *client)
{
struct backlight_properties props = { };
struct device *dev = &client->dev;
struct sy7758 *sydev;
unsigned int dev_id;
int ret;
sydev = devm_kzalloc(dev, sizeof(*sydev), GFP_KERNEL);
if (!sydev)
return -ENOMEM;
i2c_set_clientdata(client, sydev);
/* Initialize regmap */
sydev->client = client;
sydev->regmap = devm_regmap_init_i2c(client, &sy7758_regmap_config);
if (IS_ERR(sydev->regmap))
return dev_err_probe(dev, PTR_ERR(sydev->regmap),
"failed to init regmap\n");
/* Get and enable regulator */
ret = devm_regulator_get_enable(dev, "vdd");
if (ret)
return dev_err_probe(dev, ret, "failed to get regulator\n");
fsleep(100);
/* Get enable GPIO and set to high */
sydev->gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);
if (IS_ERR(sydev->gpio))
return dev_err_probe(dev, PTR_ERR(sydev->gpio),
"failed to get enable GPIO\n");
/* Let some time for HW to settle */
fsleep(10000);
/* try read and check device id */
ret = regmap_read(sydev->regmap, REG_DEV_ID, &dev_id);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to read device id\n");
if (dev_id != 0x63) {
dev_err(dev, "unexpected device id: 0x%02x\n", dev_id);
return -ENODEV;
}
/* Initialize and set default brightness */
ret = sy7758_init(sydev);
if (ret)
return ret;
props.type = BACKLIGHT_RAW;
props.max_brightness = MAX_BRIGHTNESS;
props.brightness = DEFAULT_BRIGHTNESS;
props.scale = BACKLIGHT_SCALE_LINEAR;
sydev->bl = devm_backlight_device_register(dev, "sy7758-backlight",
dev, sydev, &sy7758_backlight_ops,
&props);
if (IS_ERR(sydev->bl))
return dev_err_probe(dev, PTR_ERR(sydev->bl),
"failed to register backlight device\n");
return backlight_update_status(sydev->bl);
}
static void sy7758_remove(struct i2c_client *client)
{
struct sy7758 *sydev = i2c_get_clientdata(client);
backlight_disable(sydev->bl);
}
static const struct i2c_device_id sy7758_ids[] = {
{ "sy7758" },
{ }
};
MODULE_DEVICE_TABLE(i2c, sy7758_ids);
static const struct of_device_id sy7758_match_table[] = {
{ .compatible = "silergy,sy7758", },
{ },
};
MODULE_DEVICE_TABLE(of, sy7758_match_table);
static struct i2c_driver sy7758_driver = {
.driver = {
.name = "sy7758",
.of_match_table = sy7758_match_table,
},
.probe = sy7758_probe,
.remove = sy7758_remove,
.id_table = sy7758_ids,
};
module_i2c_driver(sy7758_driver);
MODULE_DESCRIPTION("Silergy SY7758 Backlight Driver");
MODULE_AUTHOR("Kancy Joe <kancy2333@outlook.com>");
MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>");
MODULE_LICENSE("GPL");