summaryrefslogtreecommitdiff
path: root/drivers/video
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video')
-rw-r--r--drivers/video/backlight/Kconfig9
-rw-r--r--drivers/video/backlight/Makefile1
-rw-r--r--drivers/video/backlight/aw99706.c471
-rw-r--r--drivers/video/backlight/led_bl.c13
-rw-r--r--drivers/video/fbdev/core/fbcon.c9
5 files changed, 503 insertions, 0 deletions
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index d9374d208cee..a1422ddd1c22 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -156,6 +156,14 @@ config BACKLIGHT_ATMEL_LCDC
If in doubt, it's safe to enable this option; it doesn't kick
in unless the board's description says it's wired that way.
+config BACKLIGHT_AW99706
+ tristate "Backlight Driver for Awinic AW99706"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you have a LCD backlight connected to the WLED output of AW99706
+ WLED output, say Y here to enable this driver.
+
config BACKLIGHT_EP93XX
tristate "Cirrus EP93xx Backlight Driver"
depends on FB_EP93XX
@@ -185,6 +193,7 @@ config BACKLIGHT_KTD253
config BACKLIGHT_KTD2801
tristate "Backlight Driver for Kinetic KTD2801"
+ depends on GPIOLIB || COMPILE_TEST
select LEDS_EXPRESSWIRE
help
Say Y to enable the backlight driver for the Kinetic KTD2801 1-wire
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index dfbb169bf6ea..a5d62b018102 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o
obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o
obj-$(CONFIG_BACKLIGHT_APPLE_DWI) += apple_dwi_bl.o
obj-$(CONFIG_BACKLIGHT_AS3711) += as3711_bl.o
+obj-$(CONFIG_BACKLIGHT_AW99706) += aw99706.o
obj-$(CONFIG_BACKLIGHT_BD6107) += bd6107.o
obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o
diff --git a/drivers/video/backlight/aw99706.c b/drivers/video/backlight/aw99706.c
new file mode 100644
index 000000000000..df5b23b2f753
--- /dev/null
+++ b/drivers/video/backlight/aw99706.c
@@ -0,0 +1,471 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * aw99706 - Backlight driver for the AWINIC AW99706
+ *
+ * Copyright (C) 2025 Junjie Cao <caojunjie650@gmail.com>
+ * Copyright (C) 2025 Pengyu Luo <mitltlatltl@gmail.com>
+ *
+ * Based on vendor driver:
+ * Copyright (c) 2023 AWINIC Technology CO., LTD
+ */
+
+#include <linux/backlight.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define AW99706_MAX_BRT_LVL 4095
+#define AW99706_REG_MAX 0x1F
+#define AW99706_ID 0x07
+
+/* registers list */
+#define AW99706_CFG0_REG 0x00
+#define AW99706_DIM_MODE_MASK GENMASK(1, 0)
+
+#define AW99706_CFG1_REG 0x01
+#define AW99706_SW_FREQ_MASK GENMASK(3, 0)
+#define AW99706_SW_ILMT_MASK GENMASK(5, 4)
+
+#define AW99706_CFG2_REG 0x02
+#define AW99706_ILED_MAX_MASK GENMASK(6, 0)
+#define AW99706_UVLOSEL_MASK BIT(7)
+
+#define AW99706_CFG3_REG 0x03
+#define AW99706_CFG4_REG 0x04
+#define AW99706_BRT_MSB_MASK GENMASK(3, 0)
+
+#define AW99706_CFG5_REG 0x05
+#define AW99706_BRT_LSB_MASK GENMASK(7, 0)
+
+#define AW99706_CFG6_REG 0x06
+#define AW99706_RAMP_CTL_MASK GENMASK(7, 6)
+
+#define AW99706_CFG7_REG 0x07
+#define AW99706_CFG8_REG 0x08
+#define AW99706_CFG9_REG 0x09
+#define AW99706_CFGA_REG 0x0A
+#define AW99706_CFGB_REG 0x0B
+#define AW99706_CFGC_REG 0x0C
+#define AW99706_CFGD_REG 0x0D
+#define AW99706_FLAG_REG 0x10
+#define AW99706_BACKLIGHT_EN_MASK BIT(7)
+
+#define AW99706_CHIPID_REG 0x11
+#define AW99706_LED_OPEN_FLAG_REG 0x12
+#define AW99706_LED_SHORT_FLAG_REG 0x13
+#define AW99706_MTPLDOSEL_REG 0x1E
+#define AW99706_MTPRUN_REG 0x1F
+
+#define RESV 0
+
+/* Boost switching frequency table, in Hz */
+static const u32 aw99706_sw_freq_tbl[] = {
+ RESV, RESV, RESV, RESV, 300000, 400000, 500000, 600000,
+ 660000, 750000, 850000, 1000000, 1200000, 1330000, 1500000, 1700000
+};
+
+/* Switching current limitation table, in uA */
+static const u32 aw99706_sw_ilmt_tbl[] = {
+ 1500000, 2000000, 2500000, 3000000
+};
+
+/* ULVO threshold table, in uV */
+static const u32 aw99706_ulvo_thres_tbl[] = {
+ 2200000, 5000000
+};
+
+struct aw99706_dt_prop {
+ const char * const name;
+ int (*lookup)(const struct aw99706_dt_prop *prop, u32 dt_val, u8 *val);
+ const u32 * const lookup_tbl;
+ u8 tbl_size;
+ u8 reg;
+ u8 mask;
+ u32 def_val;
+};
+
+static int aw99706_dt_property_lookup(const struct aw99706_dt_prop *prop,
+ u32 dt_val, u8 *val)
+{
+ int i;
+
+ if (!prop->lookup_tbl) {
+ *val = dt_val;
+ return 0;
+ }
+
+ for (i = 0; i < prop->tbl_size; i++)
+ if (prop->lookup_tbl[i] == dt_val)
+ break;
+
+ *val = i;
+
+ return i == prop->tbl_size ? -1 : 0;
+}
+
+#define MIN_ILED_MAX 5000
+#define MAX_ILED_MAX 50000
+#define STEP_ILED_MAX 500
+
+static int
+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;
+
+ *val = (dt_val - MIN_ILED_MAX) / STEP_ILED_MAX;
+
+ return (dt_val - MIN_ILED_MAX) % STEP_ILED_MAX;
+}
+
+static const struct aw99706_dt_prop aw99706_dt_props[] = {
+ {
+ "awinic,dim-mode", aw99706_dt_property_lookup,
+ NULL, 0,
+ AW99706_CFG0_REG, AW99706_DIM_MODE_MASK, 1,
+ },
+ {
+ "awinic,sw-freq", 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,
+ 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,
+ NULL, 0,
+ AW99706_CFG2_REG, AW99706_ILED_MAX_MASK, 20000,
+
+ },
+ {
+ "awinic,uvlo-thres", aw99706_dt_property_lookup,
+ aw99706_ulvo_thres_tbl, ARRAY_SIZE(aw99706_ulvo_thres_tbl),
+ AW99706_CFG2_REG, AW99706_UVLOSEL_MASK, 2200000,
+ },
+ {
+ "awinic,ramp-ctl", aw99706_dt_property_lookup,
+ NULL, 0,
+ AW99706_CFG6_REG, AW99706_RAMP_CTL_MASK, 2,
+ }
+};
+
+struct reg_init_data {
+ u8 reg;
+ u8 mask;
+ u8 val;
+};
+
+struct aw99706_device {
+ struct i2c_client *client;
+ struct device *dev;
+ struct regmap *regmap;
+ struct backlight_device *bl_dev;
+ struct gpio_desc *hwen_gpio;
+ struct reg_init_data init_tbl[ARRAY_SIZE(aw99706_dt_props)];
+ bool bl_enable;
+};
+
+enum reg_access {
+ REG_NONE_ACCESS = 0,
+ REG_RD_ACCESS = 1,
+ REG_WR_ACCESS = 2,
+};
+
+static const u8 aw99706_regs[AW99706_REG_MAX + 1] = {
+ [AW99706_CFG0_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG1_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG2_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG3_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG4_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG5_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG6_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG7_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG8_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFG9_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFGA_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFGB_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFGC_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_CFGD_REG] = REG_RD_ACCESS | REG_WR_ACCESS,
+ [AW99706_FLAG_REG] = REG_RD_ACCESS,
+ [AW99706_CHIPID_REG] = REG_RD_ACCESS,
+ [AW99706_LED_OPEN_FLAG_REG] = REG_RD_ACCESS,
+ [AW99706_LED_SHORT_FLAG_REG] = REG_RD_ACCESS,
+
+ /*
+ * Write bit is dropped here, writing BIT(0) to MTPLDOSEL will unlock
+ * Multi-time Programmable (MTP).
+ */
+ [AW99706_MTPLDOSEL_REG] = REG_RD_ACCESS,
+ [AW99706_MTPRUN_REG] = REG_NONE_ACCESS,
+};
+
+static bool aw99706_readable_reg(struct device *dev, unsigned int reg)
+{
+ return aw99706_regs[reg] & REG_RD_ACCESS;
+}
+
+static bool aw99706_writeable_reg(struct device *dev, unsigned int reg)
+{
+ return aw99706_regs[reg] & REG_WR_ACCESS;
+}
+
+static inline int aw99706_i2c_read(struct aw99706_device *aw, u8 reg,
+ unsigned int *val)
+{
+ return regmap_read(aw->regmap, reg, val);
+}
+
+static inline int aw99706_i2c_write(struct aw99706_device *aw, u8 reg, u8 val)
+{
+ return regmap_write(aw->regmap, reg, val);
+}
+
+static inline int aw99706_i2c_update_bits(struct aw99706_device *aw, u8 reg,
+ u8 mask, u8 val)
+{
+ return regmap_update_bits(aw->regmap, reg, mask, val);
+}
+
+static void aw99706_dt_parse(struct aw99706_device *aw,
+ struct backlight_properties *bl_props)
+{
+ const struct aw99706_dt_prop *prop;
+ u32 dt_val;
+ int ret, i;
+ u8 val;
+
+ for (i = 0; i < ARRAY_SIZE(aw99706_dt_props); i++) {
+ prop = &aw99706_dt_props[i];
+ ret = device_property_read_u32(aw->dev, prop->name, &dt_val);
+ if (ret < 0)
+ dt_val = prop->def_val;
+
+ if (prop->lookup(prop, dt_val, &val)) {
+ dev_warn(aw->dev, "invalid value %d for property %s, using default value %d\n",
+ dt_val, prop->name, prop->def_val);
+
+ prop->lookup(prop, prop->def_val, &val);
+ }
+
+ aw->init_tbl[i].reg = prop->reg;
+ aw->init_tbl[i].mask = prop->mask;
+ aw->init_tbl[i].val = val << __ffs(prop->mask);
+ }
+
+ bl_props->brightness = AW99706_MAX_BRT_LVL >> 1;
+ bl_props->max_brightness = AW99706_MAX_BRT_LVL;
+ device_property_read_u32(aw->dev, "default-brightness",
+ &bl_props->brightness);
+ device_property_read_u32(aw->dev, "max-brightness",
+ &bl_props->max_brightness);
+
+ if (bl_props->max_brightness > AW99706_MAX_BRT_LVL)
+ bl_props->max_brightness = AW99706_MAX_BRT_LVL;
+
+ if (bl_props->brightness > bl_props->max_brightness)
+ bl_props->brightness = bl_props->max_brightness;
+}
+
+static int aw99706_hw_init(struct aw99706_device *aw)
+{
+ int ret, i;
+
+ gpiod_set_value_cansleep(aw->hwen_gpio, 1);
+
+ for (i = 0; i < ARRAY_SIZE(aw->init_tbl); i++) {
+ ret = aw99706_i2c_update_bits(aw, aw->init_tbl[i].reg,
+ aw->init_tbl[i].mask,
+ aw->init_tbl[i].val);
+ if (ret < 0) {
+ dev_err(aw->dev, "Failed to write init data %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int aw99706_bl_enable(struct aw99706_device *aw, bool en)
+{
+ int ret;
+ u8 val;
+
+ val = FIELD_PREP(AW99706_BACKLIGHT_EN_MASK, en);
+ ret = aw99706_i2c_update_bits(aw, AW99706_CFGD_REG,
+ AW99706_BACKLIGHT_EN_MASK, val);
+ if (ret)
+ dev_err(aw->dev, "Failed to enable backlight!\n");
+
+ return ret;
+}
+
+static int aw99706_update_brightness(struct aw99706_device *aw, u32 brt_lvl)
+{
+ bool bl_enable_now = !!brt_lvl;
+ int ret;
+
+ ret = aw99706_i2c_write(aw, AW99706_CFG4_REG,
+ (brt_lvl >> 8) & AW99706_BRT_MSB_MASK);
+ if (ret < 0)
+ return ret;
+
+ ret = aw99706_i2c_write(aw, AW99706_CFG5_REG,
+ brt_lvl & AW99706_BRT_LSB_MASK);
+ if (ret < 0)
+ return ret;
+
+ if (aw->bl_enable != bl_enable_now) {
+ ret = aw99706_bl_enable(aw, bl_enable_now);
+ if (!ret)
+ aw->bl_enable = bl_enable_now;
+ }
+
+ return ret;
+}
+
+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);
+}
+
+static const struct backlight_ops aw99706_bl_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = aw99706_bl_update_status,
+};
+
+static const struct regmap_config aw99706_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = AW99706_REG_MAX,
+ .writeable_reg = aw99706_writeable_reg,
+ .readable_reg = aw99706_readable_reg,
+};
+
+static int aw99706_chip_id_read(struct aw99706_device *aw)
+{
+ int ret;
+ unsigned int val;
+
+ ret = aw99706_i2c_read(aw, AW99706_CHIPID_REG, &val);
+ if (ret < 0)
+ return ret;
+
+ return val;
+}
+
+static int aw99706_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct aw99706_device *aw;
+ struct backlight_device *bl_dev;
+ struct backlight_properties props = {};
+ int ret = 0;
+
+ aw = devm_kzalloc(dev, sizeof(*aw), GFP_KERNEL);
+ if (!aw)
+ return -ENOMEM;
+
+ aw->client = client;
+ aw->dev = dev;
+ i2c_set_clientdata(client, aw);
+
+ aw->regmap = devm_regmap_init_i2c(client, &aw99706_regmap_config);
+ if (IS_ERR(aw->regmap))
+ return dev_err_probe(dev, PTR_ERR(aw->regmap),
+ "Failed to init regmap\n");
+
+ ret = aw99706_chip_id_read(aw);
+ if (ret != AW99706_ID)
+ return dev_err_probe(dev, -ENODEV,
+ "Unknown chip id 0x%02x\n", ret);
+
+ aw99706_dt_parse(aw, &props);
+
+ aw->hwen_gpio = devm_gpiod_get(aw->dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(aw->hwen_gpio))
+ return dev_err_probe(dev, PTR_ERR(aw->hwen_gpio),
+ "Failed to get enable gpio\n");
+
+ ret = aw99706_hw_init(aw);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to initialize the chip\n");
+
+ props.type = BACKLIGHT_RAW;
+ props.scale = BACKLIGHT_SCALE_LINEAR;
+
+ bl_dev = devm_backlight_device_register(dev, "aw99706-backlight", dev,
+ aw, &aw99706_bl_ops, &props);
+ if (IS_ERR(bl_dev))
+ return dev_err_probe(dev, PTR_ERR(bl_dev),
+ "Failed to register backlight!\n");
+
+ aw->bl_dev = bl_dev;
+
+ return 0;
+}
+
+static void aw99706_remove(struct i2c_client *client)
+{
+ struct aw99706_device *aw = i2c_get_clientdata(client);
+
+ aw99706_update_brightness(aw, 0);
+
+ msleep(50);
+
+ gpiod_set_value_cansleep(aw->hwen_gpio, 0);
+}
+
+static int aw99706_suspend(struct device *dev)
+{
+ struct aw99706_device *aw = dev_get_drvdata(dev);
+
+ return aw99706_update_brightness(aw, 0);
+}
+
+static int aw99706_resume(struct device *dev)
+{
+ struct aw99706_device *aw = dev_get_drvdata(dev);
+
+ return aw99706_hw_init(aw);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(aw99706_pm_ops, aw99706_suspend, aw99706_resume);
+
+static const struct i2c_device_id aw99706_ids[] = {
+ { "aw99706" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, aw99706_ids);
+
+static const struct of_device_id aw99706_match_table[] = {
+ { .compatible = "awinic,aw99706", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, aw99706_match_table);
+
+static struct i2c_driver aw99706_i2c_driver = {
+ .probe = aw99706_probe,
+ .remove = aw99706_remove,
+ .id_table = aw99706_ids,
+ .driver = {
+ .name = "aw99706",
+ .of_match_table = aw99706_match_table,
+ .pm = pm_ptr(&aw99706_pm_ops),
+ },
+};
+
+module_i2c_driver(aw99706_i2c_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BackLight driver for aw99706");
diff --git a/drivers/video/backlight/led_bl.c b/drivers/video/backlight/led_bl.c
index efc5e380669a..f7ab9b360731 100644
--- a/drivers/video/backlight/led_bl.c
+++ b/drivers/video/backlight/led_bl.c
@@ -211,6 +211,19 @@ static int led_bl_probe(struct platform_device *pdev)
}
for (i = 0; i < priv->nb_leds; i++) {
+ struct device_link *link;
+
+ link = device_link_add(&pdev->dev, priv->leds[i]->dev->parent,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ if (!link) {
+ dev_err(&pdev->dev, "Failed to add devlink (consumer %s, supplier %s)\n",
+ dev_name(&pdev->dev), dev_name(priv->leds[i]->dev->parent));
+ backlight_device_unregister(priv->bl_dev);
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; i < priv->nb_leds; i++) {
mutex_lock(&priv->leds[i]->led_access);
led_sysfs_disable(priv->leds[i]);
mutex_unlock(&priv->leds[i]->led_access);
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 9771134beb8a..7be9e865325d 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -66,6 +66,7 @@
#include <linux/string.h>
#include <linux/kd.h>
#include <linux/panic.h>
+#include <linux/pci.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/fb.h>
@@ -78,6 +79,7 @@
#include <linux/interrupt.h>
#include <linux/crc32.h> /* For counting font checksums */
#include <linux/uaccess.h>
+#include <linux/vga_switcheroo.h>
#include <asm/irq.h>
#include "fbcon.h"
@@ -2913,6 +2915,9 @@ void fbcon_fb_unregistered(struct fb_info *info)
console_lock();
+ if (info->device && dev_is_pci(info->device))
+ vga_switcheroo_client_fb_set(to_pci_dev(info->device), NULL);
+
fbcon_registered_fb[info->node] = NULL;
fbcon_num_registered_fb--;
@@ -3046,6 +3051,10 @@ static int do_fb_registered(struct fb_info *info)
}
}
+ /* Set the fb info for vga_switcheroo clients. Does nothing otherwise. */
+ if (info->device && dev_is_pci(info->device))
+ vga_switcheroo_client_fb_set(to_pci_dev(info->device), info);
+
return ret;
}