aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video')
-rw-r--r--drivers/video/backlight/Kconfig12
-rw-r--r--drivers/video/backlight/Makefile2
-rw-r--r--drivers/video/backlight/gpio_backlight.c128
-rw-r--r--drivers/video/backlight/ipaq_micro_bl.c2
-rw-r--r--drivers/video/backlight/lm3630a_bl.c13
-rw-r--r--drivers/video/backlight/pm8941-wled.c424
-rw-r--r--drivers/video/backlight/pwm_bl.c39
-rw-r--r--drivers/video/backlight/qcom-wled.c1296
-rw-r--r--drivers/video/backlight/tosa_bl.c10
-rw-r--r--drivers/video/backlight/tosa_bl.h8
-rw-r--r--drivers/video/backlight/tosa_lcd.c28
-rw-r--r--drivers/video/console/vgacon.c6
-rw-r--r--drivers/video/fbdev/Kconfig1
-rw-r--r--drivers/video/fbdev/aty/atyfb_base.c12
-rw-r--r--drivers/video/fbdev/c2p_core.h8
-rw-r--r--drivers/video/fbdev/core/fbmem.c17
-rw-r--r--drivers/video/fbdev/hyperv_fb.c428
-rw-r--r--drivers/video/fbdev/sa1100fb.c13
-rw-r--r--drivers/video/hdmi.c8
19 files changed, 1841 insertions, 614 deletions
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 40676be2e46a..403707a3e503 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -99,7 +99,7 @@ config LCD_TOSA
config LCD_HP700
tristate "HP Jornada 700 series LCD Driver"
- depends on SA1100_JORNADA720_SSP && !PREEMPT
+ depends on SA1100_JORNADA720_SSP && !PREEMPTION
default y
help
If you have an HP Jornada 700 series handheld (710/720/728)
@@ -228,7 +228,7 @@ config BACKLIGHT_HP680
config BACKLIGHT_HP700
tristate "HP Jornada 700 series Backlight Driver"
- depends on SA1100_JORNADA720_SSP && !PREEMPT
+ depends on SA1100_JORNADA720_SSP && !PREEMPTION
default y
help
If you have an HP Jornada 700 series,
@@ -282,12 +282,12 @@ config BACKLIGHT_TOSA
If you have an Sharp SL-6000 Zaurus say Y to enable a driver
for its backlight
-config BACKLIGHT_PM8941_WLED
- tristate "Qualcomm PM8941 WLED Driver"
+config BACKLIGHT_QCOM_WLED
+ tristate "Qualcomm PMIC WLED Driver"
select REGMAP
help
- If you have the Qualcomm PM8941, say Y to enable a driver for the
- WLED block.
+ If you have the Qualcomm PMIC, say Y to enable a driver for the
+ WLED block. Currently it supports PM8941 and PMI8998.
config BACKLIGHT_SAHARA
tristate "Tabletkiosk Sahara Touch-iT Backlight Driver"
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 63c507c07437..6f8777037c37 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -48,8 +48,8 @@ obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o
obj-$(CONFIG_BACKLIGHT_OT200) += ot200_bl.o
obj-$(CONFIG_BACKLIGHT_PANDORA) += pandora_bl.o
obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o
-obj-$(CONFIG_BACKLIGHT_PM8941_WLED) += pm8941-wled.o
obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o
+obj-$(CONFIG_BACKLIGHT_QCOM_WLED) += qcom-wled.o
obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o
obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o
obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o
diff --git a/drivers/video/backlight/gpio_backlight.c b/drivers/video/backlight/gpio_backlight.c
index 18e053e4716c..75409ddfba3e 100644
--- a/drivers/video/backlight/gpio_backlight.c
+++ b/drivers/video/backlight/gpio_backlight.c
@@ -6,29 +6,23 @@
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/fb.h>
-#include <linux/gpio.h> /* Only for legacy support */
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_gpio.h>
#include <linux/platform_data/gpio_backlight.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
struct gpio_backlight {
- struct device *dev;
struct device *fbdev;
-
struct gpio_desc *gpiod;
- int def_value;
};
-static int gpio_backlight_update_status(struct backlight_device *bl)
+static int gpio_backlight_get_next_brightness(struct backlight_device *bl)
{
- struct gpio_backlight *gbl = bl_get_data(bl);
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK ||
@@ -36,6 +30,14 @@ static int gpio_backlight_update_status(struct backlight_device *bl)
bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
brightness = 0;
+ return brightness;
+}
+
+static int gpio_backlight_update_status(struct backlight_device *bl)
+{
+ struct gpio_backlight *gbl = bl_get_data(bl);
+ int brightness = gpio_backlight_get_next_brightness(bl);
+
gpiod_set_value_cansleep(gbl->gpiod, brightness);
return 0;
@@ -55,105 +57,63 @@ static const struct backlight_ops gpio_backlight_ops = {
.check_fb = gpio_backlight_check_fb,
};
-static int gpio_backlight_probe_dt(struct platform_device *pdev,
- struct gpio_backlight *gbl)
-{
- struct device *dev = &pdev->dev;
- int ret;
-
- gbl->def_value = device_property_read_bool(dev, "default-on");
-
- gbl->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
- if (IS_ERR(gbl->gpiod)) {
- ret = PTR_ERR(gbl->gpiod);
-
- if (ret != -EPROBE_DEFER) {
- dev_err(dev,
- "Error: The gpios parameter is missing or invalid.\n");
- }
- return ret;
- }
-
- return 0;
-}
-
-static int gpio_backlight_initial_power_state(struct gpio_backlight *gbl)
-{
- struct device_node *node = gbl->dev->of_node;
-
- /* Not booted with device tree or no phandle link to the node */
- if (!node || !node->phandle)
- return gbl->def_value ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
-
- /* if the enable GPIO is disabled, do not enable the backlight */
- if (gpiod_get_value_cansleep(gbl->gpiod) == 0)
- return FB_BLANK_POWERDOWN;
-
- return FB_BLANK_UNBLANK;
-}
-
-
static int gpio_backlight_probe(struct platform_device *pdev)
{
- struct gpio_backlight_platform_data *pdata =
- dev_get_platdata(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct gpio_backlight_platform_data *pdata = dev_get_platdata(dev);
+ struct device_node *of_node = dev->of_node;
struct backlight_properties props;
struct backlight_device *bl;
struct gpio_backlight *gbl;
- int ret;
+ int ret, init_brightness, def_value;
- gbl = devm_kzalloc(&pdev->dev, sizeof(*gbl), GFP_KERNEL);
+ gbl = devm_kzalloc(dev, sizeof(*gbl), GFP_KERNEL);
if (gbl == NULL)
return -ENOMEM;
- gbl->dev = &pdev->dev;
+ if (pdata)
+ gbl->fbdev = pdata->fbdev;
- if (pdev->dev.fwnode) {
- ret = gpio_backlight_probe_dt(pdev, gbl);
- if (ret)
- return ret;
- } else if (pdata) {
- /*
- * Legacy platform data GPIO retrieveal. Do not expand
- * the use of this code path, currently only used by one
- * SH board.
- */
- unsigned long flags = GPIOF_DIR_OUT;
+ def_value = device_property_read_bool(dev, "default-on");
- gbl->fbdev = pdata->fbdev;
- gbl->def_value = pdata->def_value;
- flags |= gbl->def_value ? GPIOF_INIT_HIGH : GPIOF_INIT_LOW;
-
- ret = devm_gpio_request_one(gbl->dev, pdata->gpio, flags,
- pdata ? pdata->name : "backlight");
- if (ret < 0) {
- dev_err(&pdev->dev, "unable to request GPIO\n");
- return ret;
- }
- gbl->gpiod = gpio_to_desc(pdata->gpio);
- if (!gbl->gpiod)
- return -EINVAL;
- } else {
- dev_err(&pdev->dev,
- "failed to find platform data or device tree node.\n");
- return -ENODEV;
+ gbl->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
+ if (IS_ERR(gbl->gpiod)) {
+ ret = PTR_ERR(gbl->gpiod);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev,
+ "Error: The gpios parameter is missing or invalid.\n");
+ return ret;
}
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = 1;
- bl = devm_backlight_device_register(&pdev->dev, dev_name(&pdev->dev),
- &pdev->dev, gbl, &gpio_backlight_ops,
- &props);
+ bl = devm_backlight_device_register(dev, dev_name(dev), dev, gbl,
+ &gpio_backlight_ops, &props);
if (IS_ERR(bl)) {
- dev_err(&pdev->dev, "failed to register backlight\n");
+ dev_err(dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
- bl->props.power = gpio_backlight_initial_power_state(gbl);
+ /* Set the initial power state */
+ if (!of_node || !of_node->phandle)
+ /* Not booted with device tree or no phandle link to the node */
+ bl->props.power = def_value ? FB_BLANK_UNBLANK
+ : FB_BLANK_POWERDOWN;
+ else if (gpiod_get_direction(gbl->gpiod) == 0 &&
+ gpiod_get_value_cansleep(gbl->gpiod) == 0)
+ bl->props.power = FB_BLANK_POWERDOWN;
+ else
+ bl->props.power = FB_BLANK_UNBLANK;
+
bl->props.brightness = 1;
- backlight_update_status(bl);
+ init_brightness = gpio_backlight_get_next_brightness(bl);
+ ret = gpiod_direction_output(gbl->gpiod, init_brightness);
+ if (ret) {
+ dev_err(dev, "failed to set initial brightness\n");
+ return ret;
+ }
platform_set_drvdata(pdev, bl);
return 0;
diff --git a/drivers/video/backlight/ipaq_micro_bl.c b/drivers/video/backlight/ipaq_micro_bl.c
index 1123f67c12b3..85b16cc82878 100644
--- a/drivers/video/backlight/ipaq_micro_bl.c
+++ b/drivers/video/backlight/ipaq_micro_bl.c
@@ -44,7 +44,7 @@ static const struct backlight_ops micro_bl_ops = {
.update_status = micro_bl_update_status,
};
-static struct backlight_properties micro_bl_props = {
+static const struct backlight_properties micro_bl_props = {
.type = BACKLIGHT_RAW,
.max_brightness = 255,
.power = FB_BLANK_UNBLANK,
diff --git a/drivers/video/backlight/lm3630a_bl.c b/drivers/video/backlight/lm3630a_bl.c
index 2d8e8192e4e2..ee320883b710 100644
--- a/drivers/video/backlight/lm3630a_bl.c
+++ b/drivers/video/backlight/lm3630a_bl.c
@@ -12,6 +12,7 @@
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/regmap.h>
+#include <linux/gpio/consumer.h>
#include <linux/pwm.h>
#include <linux/platform_data/lm3630a_bl.h>
@@ -48,6 +49,7 @@ struct lm3630a_chip {
struct lm3630a_platform_data *pdata;
struct backlight_device *bleda;
struct backlight_device *bledb;
+ struct gpio_desc *enable_gpio;
struct regmap *regmap;
struct pwm_device *pwmd;
};
@@ -534,6 +536,13 @@ static int lm3630a_probe(struct i2c_client *client,
}
pchip->pdata = pdata;
+ pchip->enable_gpio = devm_gpiod_get_optional(&client->dev, "enable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(pchip->enable_gpio)) {
+ rval = PTR_ERR(pchip->enable_gpio);
+ return rval;
+ }
+
/* chip initialize */
rval = lm3630a_chip_init(pchip);
if (rval < 0) {
@@ -598,12 +607,14 @@ static const struct i2c_device_id lm3630a_id[] = {
{}
};
+MODULE_DEVICE_TABLE(i2c, lm3630a_id);
+
static const struct of_device_id lm3630a_match_table[] = {
{ .compatible = "ti,lm3630a", },
{ },
};
-MODULE_DEVICE_TABLE(i2c, lm3630a_id);
+MODULE_DEVICE_TABLE(of, lm3630a_match_table);
static struct i2c_driver lm3630a_i2c_driver = {
.driver = {
diff --git a/drivers/video/backlight/pm8941-wled.c b/drivers/video/backlight/pm8941-wled.c
deleted file mode 100644
index 82b85725a22a..000000000000
--- a/drivers/video/backlight/pm8941-wled.c
+++ /dev/null
@@ -1,424 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/* Copyright (c) 2015, Sony Mobile Communications, AB.
- */
-
-#include <linux/kernel.h>
-#include <linux/backlight.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/of_device.h>
-#include <linux/regmap.h>
-
-/* From DT binding */
-#define PM8941_WLED_DEFAULT_BRIGHTNESS 2048
-
-#define PM8941_WLED_REG_VAL_BASE 0x40
-#define PM8941_WLED_REG_VAL_MAX 0xFFF
-
-#define PM8941_WLED_REG_MOD_EN 0x46
-#define PM8941_WLED_REG_MOD_EN_BIT BIT(7)
-#define PM8941_WLED_REG_MOD_EN_MASK BIT(7)
-
-#define PM8941_WLED_REG_SYNC 0x47
-#define PM8941_WLED_REG_SYNC_MASK 0x07
-#define PM8941_WLED_REG_SYNC_LED1 BIT(0)
-#define PM8941_WLED_REG_SYNC_LED2 BIT(1)
-#define PM8941_WLED_REG_SYNC_LED3 BIT(2)
-#define PM8941_WLED_REG_SYNC_ALL 0x07
-#define PM8941_WLED_REG_SYNC_CLEAR 0x00
-
-#define PM8941_WLED_REG_FREQ 0x4c
-#define PM8941_WLED_REG_FREQ_MASK 0x0f
-
-#define PM8941_WLED_REG_OVP 0x4d
-#define PM8941_WLED_REG_OVP_MASK 0x03
-
-#define PM8941_WLED_REG_BOOST 0x4e
-#define PM8941_WLED_REG_BOOST_MASK 0x07
-
-#define PM8941_WLED_REG_SINK 0x4f
-#define PM8941_WLED_REG_SINK_MASK 0xe0
-#define PM8941_WLED_REG_SINK_SHFT 0x05
-
-/* Per-'string' registers below */
-#define PM8941_WLED_REG_STR_OFFSET 0x10
-
-#define PM8941_WLED_REG_STR_MOD_EN_BASE 0x60
-#define PM8941_WLED_REG_STR_MOD_MASK BIT(7)
-#define PM8941_WLED_REG_STR_MOD_EN BIT(7)
-
-#define PM8941_WLED_REG_STR_SCALE_BASE 0x62
-#define PM8941_WLED_REG_STR_SCALE_MASK 0x1f
-
-#define PM8941_WLED_REG_STR_MOD_SRC_BASE 0x63
-#define PM8941_WLED_REG_STR_MOD_SRC_MASK 0x01
-#define PM8941_WLED_REG_STR_MOD_SRC_INT 0x00
-#define PM8941_WLED_REG_STR_MOD_SRC_EXT 0x01
-
-#define PM8941_WLED_REG_STR_CABC_BASE 0x66
-#define PM8941_WLED_REG_STR_CABC_MASK BIT(7)
-#define PM8941_WLED_REG_STR_CABC_EN BIT(7)
-
-struct pm8941_wled_config {
- u32 i_boost_limit;
- u32 ovp;
- u32 switch_freq;
- u32 num_strings;
- u32 i_limit;
- bool cs_out_en;
- bool ext_gen;
- bool cabc_en;
-};
-
-struct pm8941_wled {
- const char *name;
- struct regmap *regmap;
- u16 addr;
-
- struct pm8941_wled_config cfg;
-};
-
-static int pm8941_wled_update_status(struct backlight_device *bl)
-{
- struct pm8941_wled *wled = bl_get_data(bl);
- u16 val = bl->props.brightness;
- u8 ctrl = 0;
- int rc;
- int i;
-
- if (bl->props.power != FB_BLANK_UNBLANK ||
- bl->props.fb_blank != FB_BLANK_UNBLANK ||
- bl->props.state & BL_CORE_FBBLANK)
- val = 0;
-
- if (val != 0)
- ctrl = PM8941_WLED_REG_MOD_EN_BIT;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_MOD_EN,
- PM8941_WLED_REG_MOD_EN_MASK, ctrl);
- if (rc)
- return rc;
-
- for (i = 0; i < wled->cfg.num_strings; ++i) {
- u8 v[2] = { val & 0xff, (val >> 8) & 0xf };
-
- rc = regmap_bulk_write(wled->regmap,
- wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i,
- v, 2);
- if (rc)
- return rc;
- }
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_SYNC,
- PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL);
- if (rc)
- return rc;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_SYNC,
- PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR);
- return rc;
-}
-
-static int pm8941_wled_setup(struct pm8941_wled *wled)
-{
- int rc;
- int i;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_OVP,
- PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp);
- if (rc)
- return rc;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_BOOST,
- PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit);
- if (rc)
- return rc;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_FREQ,
- PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq);
- if (rc)
- return rc;
-
- if (wled->cfg.cs_out_en) {
- u8 all = (BIT(wled->cfg.num_strings) - 1)
- << PM8941_WLED_REG_SINK_SHFT;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_SINK,
- PM8941_WLED_REG_SINK_MASK, all);
- if (rc)
- return rc;
- }
-
- for (i = 0; i < wled->cfg.num_strings; ++i) {
- u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i;
-
- rc = regmap_update_bits(wled->regmap,
- addr + PM8941_WLED_REG_STR_MOD_EN_BASE,
- PM8941_WLED_REG_STR_MOD_MASK,
- PM8941_WLED_REG_STR_MOD_EN);
- if (rc)
- return rc;
-
- if (wled->cfg.ext_gen) {
- rc = regmap_update_bits(wled->regmap,
- addr + PM8941_WLED_REG_STR_MOD_SRC_BASE,
- PM8941_WLED_REG_STR_MOD_SRC_MASK,
- PM8941_WLED_REG_STR_MOD_SRC_EXT);
- if (rc)
- return rc;
- }
-
- rc = regmap_update_bits(wled->regmap,
- addr + PM8941_WLED_REG_STR_SCALE_BASE,
- PM8941_WLED_REG_STR_SCALE_MASK,
- wled->cfg.i_limit);
- if (rc)
- return rc;
-
- rc = regmap_update_bits(wled->regmap,
- addr + PM8941_WLED_REG_STR_CABC_BASE,
- PM8941_WLED_REG_STR_CABC_MASK,
- wled->cfg.cabc_en ?
- PM8941_WLED_REG_STR_CABC_EN : 0);
- if (rc)
- return rc;
- }
-
- return 0;
-}
-
-static const struct pm8941_wled_config pm8941_wled_config_defaults = {
- .i_boost_limit = 3,
- .i_limit = 20,
- .ovp = 2,
- .switch_freq = 5,
- .num_strings = 0,
- .cs_out_en = false,
- .ext_gen = false,
- .cabc_en = false,
-};
-
-struct pm8941_wled_var_cfg {
- const u32 *values;
- u32 (*fn)(u32);
- int size;
-};
-
-static const u32 pm8941_wled_i_boost_limit_values[] = {
- 105, 385, 525, 805, 980, 1260, 1400, 1680,
-};
-
-static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = {
- .values = pm8941_wled_i_boost_limit_values,
- .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values),
-};
-
-static const u32 pm8941_wled_ovp_values[] = {
- 35, 32, 29, 27,
-};
-
-static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = {
- .values = pm8941_wled_ovp_values,
- .size = ARRAY_SIZE(pm8941_wled_ovp_values),
-};
-
-static u32 pm8941_wled_num_strings_values_fn(u32 idx)
-{
- return idx + 1;
-}
-
-static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
- .fn = pm8941_wled_num_strings_values_fn,
- .size = 3,
-};
-
-static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
-{
- return 19200 / (2 * (1 + idx));
-}
-
-static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
- .fn = pm8941_wled_switch_freq_values_fn,
- .size = 16,
-};
-
-static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
- .size = 26,
-};
-
-static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx)
-{
- if (idx >= cfg->size)
- return UINT_MAX;
- if (cfg->fn)
- return cfg->fn(idx);
- if (cfg->values)
- return cfg->values[idx];
- return idx;
-}
-
-static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev)
-{
- struct pm8941_wled_config *cfg = &wled->cfg;
- u32 val;
- int rc;
- u32 c;
- int i;
- int j;
-
- const struct {
- const char *name;
- u32 *val_ptr;
- const struct pm8941_wled_var_cfg *cfg;
- } u32_opts[] = {
- {
- "qcom,current-boost-limit",
- &cfg->i_boost_limit,
- .cfg = &pm8941_wled_i_boost_limit_cfg,
- },
- {
- "qcom,current-limit",
- &cfg->i_limit,
- .cfg = &pm8941_wled_i_limit_cfg,
- },
- {
- "qcom,ovp",
- &cfg->ovp,
- .cfg = &pm8941_wled_ovp_cfg,
- },
- {
- "qcom,switching-freq",
- &cfg->switch_freq,
- .cfg = &pm8941_wled_switch_freq_cfg,
- },
- {
- "qcom,num-strings",
- &cfg->num_strings,
- .cfg = &pm8941_wled_num_strings_cfg,
- },
- };
- const struct {
- const char *name;
- bool *val_ptr;
- } bool_opts[] = {
- { "qcom,cs-out", &cfg->cs_out_en, },
- { "qcom,ext-gen", &cfg->ext_gen, },
- { "qcom,cabc", &cfg->cabc_en, },
- };
-
- rc = of_property_read_u32(dev->of_node, "reg", &val);
- if (rc || val > 0xffff) {
- dev_err(dev, "invalid IO resources\n");
- return rc ? rc : -EINVAL;
- }
- wled->addr = val;
-
- rc = of_property_read_string(dev->of_node, "label", &wled->name);
- if (rc)
- wled->name = devm_kasprintf(dev, GFP_KERNEL, "%pOFn", dev->of_node);
-
- *cfg = pm8941_wled_config_defaults;
- for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) {
- rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
- if (rc == -EINVAL) {
- continue;
- } else if (rc) {
- dev_err(dev, "error reading '%s'\n", u32_opts[i].name);
- return rc;
- }
-
- c = UINT_MAX;
- for (j = 0; c != val; j++) {
- c = pm8941_wled_values(u32_opts[i].cfg, j);
- if (c == UINT_MAX) {
- dev_err(dev, "invalid value for '%s'\n",
- u32_opts[i].name);
- return -EINVAL;
- }
- }
-
- dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c);
- *u32_opts[i].val_ptr = j;
- }
-
- for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
- if (of_property_read_bool(dev->of_node, bool_opts[i].name))
- *bool_opts[i].val_ptr = true;
- }
-
- cfg->num_strings = cfg->num_strings + 1;
-
- return 0;
-}
-
-static const struct backlight_ops pm8941_wled_ops = {
- .update_status = pm8941_wled_update_status,
-};
-
-static int pm8941_wled_probe(struct platform_device *pdev)
-{
- struct backlight_properties props;
- struct backlight_device *bl;
- struct pm8941_wled *wled;
- struct regmap *regmap;
- u32 val;
- int rc;
-
- regmap = dev_get_regmap(pdev->dev.parent, NULL);
- if (!regmap) {
- dev_err(&pdev->dev, "Unable to get regmap\n");
- return -EINVAL;
- }
-
- wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
- if (!wled)
- return -ENOMEM;
-
- wled->regmap = regmap;
-
- rc = pm8941_wled_configure(wled, &pdev->dev);
- if (rc)
- return rc;
-
- rc = pm8941_wled_setup(wled);
- if (rc)
- return rc;
-
- val = PM8941_WLED_DEFAULT_BRIGHTNESS;
- of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
-
- memset(&props, 0, sizeof(struct backlight_properties));
- props.type = BACKLIGHT_RAW;
- props.brightness = val;
- props.max_brightness = PM8941_WLED_REG_VAL_MAX;
- bl = devm_backlight_device_register(&pdev->dev, wled->name,
- &pdev->dev, wled,
- &pm8941_wled_ops, &props);
- return PTR_ERR_OR_ZERO(bl);
-};
-
-static const struct of_device_id pm8941_wled_match_table[] = {
- { .compatible = "qcom,pm8941-wled" },
- {}
-};
-MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
-
-static struct platform_driver pm8941_wled_driver = {
- .probe = pm8941_wled_probe,
- .driver = {
- .name = "pm8941-wled",
- .of_match_table = pm8941_wled_match_table,
- },
-};
-
-module_platform_driver(pm8941_wled_driver);
-
-MODULE_DESCRIPTION("pm8941 wled driver");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c
index 746eebc411df..efb4efc2a13d 100644
--- a/drivers/video/backlight/pwm_bl.c
+++ b/drivers/video/backlight/pwm_bl.c
@@ -125,8 +125,9 @@ static int pwm_backlight_update_status(struct backlight_device *bl)
state.duty_cycle = compute_duty_cycle(pb, brightness);
pwm_apply_state(pb->pwm, &state);
pwm_backlight_power_on(pb);
- } else
+ } else {
pwm_backlight_power_off(pb);
+ }
if (pb->notify_after)
pb->notify_after(pb->dev, brightness);
@@ -148,15 +149,16 @@ static const struct backlight_ops pwm_backlight_ops = {
};
#ifdef CONFIG_OF
-#define PWM_LUMINANCE_SCALE 10000 /* luminance scale */
+#define PWM_LUMINANCE_SHIFT 16
+#define PWM_LUMINANCE_SCALE (1 << PWM_LUMINANCE_SHIFT) /* luminance scale */
/*
* CIE lightness to PWM conversion.
*
* The CIE 1931 lightness formula is what actually describes how we perceive
* light:
- * Y = (L* / 902.3) if L* ≤ 0.08856
- * Y = ((L* + 16) / 116)^3 if L* > 0.08856
+ * Y = (L* / 903.3) if L* ≤ 8
+ * Y = ((L* + 16) / 116)^3 if L* > 8
*
* Where Y is the luminance, the amount of light coming out of the screen, and
* is a number between 0.0 and 1.0; and L* is the lightness, how bright a human
@@ -165,16 +167,25 @@ static const struct backlight_ops pwm_backlight_ops = {
* The following function does the fixed point maths needed to implement the
* above formula.
*/
-static u64 cie1931(unsigned int lightness, unsigned int scale)
+static u64 cie1931(unsigned int lightness)
{
u64 retval;
+ /*
+ * @lightness is given as a number between 0 and 1, expressed
+ * as a fixed-point number in scale
+ * PWM_LUMINANCE_SCALE. Convert to a percentage, still
+ * expressed as a fixed-point number, so the above formulas
+ * can be applied.
+ */
lightness *= 100;
- if (lightness <= (8 * scale)) {
- retval = DIV_ROUND_CLOSEST_ULL(lightness * 10, 9023);
+ if (lightness <= (8 * PWM_LUMINANCE_SCALE)) {
+ retval = DIV_ROUND_CLOSEST(lightness * 10, 9033);
} else {
- retval = int_pow((lightness + (16 * scale)) / 116, 3);
- retval = DIV_ROUND_CLOSEST_ULL(retval, (scale * scale));
+ retval = (lightness + (16 * PWM_LUMINANCE_SCALE)) / 116;
+ retval *= retval * retval;
+ retval += 1ULL << (2*PWM_LUMINANCE_SHIFT - 1);
+ retval >>= 2*PWM_LUMINANCE_SHIFT;
}
return retval;
@@ -208,8 +219,7 @@ int pwm_backlight_brightness_default(struct device *dev,
/* Fill the table using the cie1931 algorithm */
for (i = 0; i < data->max_brightness; i++) {
retval = cie1931((i * PWM_LUMINANCE_SCALE) /
- data->max_brightness, PWM_LUMINANCE_SCALE) *
- period;
+ data->max_brightness) * period;
retval = DIV_ROUND_CLOSEST_ULL(retval, PWM_LUMINANCE_SCALE);
if (retval > UINT_MAX)
return -EINVAL;
@@ -564,18 +574,17 @@ static int pwm_backlight_probe(struct platform_device *pdev)
memset(&props, 0, sizeof(struct backlight_properties));
if (data->levels) {
+ pb->levels = data->levels;
+
/*
* For the DT case, only when brightness levels is defined
* data->levels is filled. For the non-DT case, data->levels
* can come from platform data, however is not usual.
*/
- for (i = 0; i <= data->max_brightness; i++) {
+ for (i = 0; i <= data->max_brightness; i++)
if (data->levels[i] > pb->scale)
pb->scale = data->levels[i];
- pb->levels = data->levels;
- }
-
if (pwm_backlight_is_linear(data))
props.scale = BACKLIGHT_SCALE_LINEAR;
else
diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
new file mode 100644
index 000000000000..d46052d8ff41
--- /dev/null
+++ b/drivers/video/backlight/qcom-wled.c
@@ -0,0 +1,1296 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2015, Sony Mobile Communications, AB.
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/ktime.h>
+#include <linux/kernel.h>
+#include <linux/backlight.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+
+/* From DT binding */
+#define WLED_MAX_STRINGS 4
+
+#define WLED_DEFAULT_BRIGHTNESS 2048
+#define WLED_SOFT_START_DLY_US 10000
+#define WLED3_SINK_REG_BRIGHT_MAX 0xFFF
+
+/* WLED3/WLED4 control registers */
+#define WLED3_CTRL_REG_FAULT_STATUS 0x08
+#define WLED3_CTRL_REG_ILIM_FAULT_BIT BIT(0)
+#define WLED3_CTRL_REG_OVP_FAULT_BIT BIT(1)
+#define WLED4_CTRL_REG_SC_FAULT_BIT BIT(2)
+
+#define WLED3_CTRL_REG_INT_RT_STS 0x10
+#define WLED3_CTRL_REG_OVP_FAULT_STATUS BIT(1)
+
+#define WLED3_CTRL_REG_MOD_EN 0x46
+#define WLED3_CTRL_REG_MOD_EN_MASK BIT(7)
+#define WLED3_CTRL_REG_MOD_EN_SHIFT 7
+
+#define WLED3_CTRL_REG_FEEDBACK_CONTROL 0x48
+
+#define WLED3_CTRL_REG_FREQ 0x4c
+#define WLED3_CTRL_REG_FREQ_MASK GENMASK(3, 0)
+
+#define WLED3_CTRL_REG_OVP 0x4d
+#define WLED3_CTRL_REG_OVP_MASK GENMASK(1, 0)
+
+#define WLED3_CTRL_REG_ILIMIT 0x4e
+#define WLED3_CTRL_REG_ILIMIT_MASK GENMASK(2, 0)
+
+/* WLED3/WLED4 sink registers */
+#define WLED3_SINK_REG_SYNC 0x47
+#define WLED3_SINK_REG_SYNC_CLEAR 0x00
+
+#define WLED3_SINK_REG_CURR_SINK 0x4f
+#define WLED3_SINK_REG_CURR_SINK_MASK GENMASK(7, 5)
+#define WLED3_SINK_REG_CURR_SINK_SHFT 5
+
+/* WLED3 specific per-'string' registers below */
+#define WLED3_SINK_REG_BRIGHT(n) (0x40 + n)
+
+#define WLED3_SINK_REG_STR_MOD_EN(n) (0x60 + (n * 0x10))
+#define WLED3_SINK_REG_STR_MOD_MASK BIT(7)
+
+#define WLED3_SINK_REG_STR_FULL_SCALE_CURR(n) (0x62 + (n * 0x10))
+#define WLED3_SINK_REG_STR_FULL_SCALE_CURR_MASK GENMASK(4, 0)
+
+#define WLED3_SINK_REG_STR_MOD_SRC(n) (0x63 + (n * 0x10))
+#define WLED3_SINK_REG_STR_MOD_SRC_MASK BIT(0)
+#define WLED3_SINK_REG_STR_MOD_SRC_INT 0x00
+#define WLED3_SINK_REG_STR_MOD_SRC_EXT 0x01
+
+#define WLED3_SINK_REG_STR_CABC(n) (0x66 + (n * 0x10))
+#define WLED3_SINK_REG_STR_CABC_MASK BIT(7)
+
+/* WLED4 specific control registers */
+#define WLED4_CTRL_REG_SHORT_PROTECT 0x5e
+#define WLED4_CTRL_REG_SHORT_EN_MASK BIT(7)
+
+#define WLED4_CTRL_REG_SEC_ACCESS 0xd0
+#define WLED4_CTRL_REG_SEC_UNLOCK 0xa5
+
+#define WLED4_CTRL_REG_TEST1 0xe2
+#define WLED4_CTRL_REG_TEST1_EXT_FET_DTEST2 0x09
+
+/* WLED4 specific sink registers */
+#define WLED4_SINK_REG_CURR_SINK 0x46
+#define WLED4_SINK_REG_CURR_SINK_MASK GENMASK(7, 4)
+#define WLED4_SINK_REG_CURR_SINK_SHFT 4
+
+/* WLED4 specific per-'string' registers below */
+#define WLED4_SINK_REG_STR_MOD_EN(n) (0x50 + (n * 0x10))
+#define WLED4_SINK_REG_STR_MOD_MASK BIT(7)
+
+#define WLED4_SINK_REG_STR_FULL_SCALE_CURR(n) (0x52 + (n * 0x10))
+#define WLED4_SINK_REG_STR_FULL_SCALE_CURR_MASK GENMASK(3, 0)
+
+#define WLED4_SINK_REG_STR_MOD_SRC(n) (0x53 + (n * 0x10))
+#define WLED4_SINK_REG_STR_MOD_SRC_MASK BIT(0)
+#define WLED4_SINK_REG_STR_MOD_SRC_INT 0x00
+#define WLED4_SINK_REG_STR_MOD_SRC_EXT 0x01
+
+#define WLED4_SINK_REG_STR_CABC(n) (0x56 + (n * 0x10))
+#define WLED4_SINK_REG_STR_CABC_MASK BIT(7)
+
+#define WLED4_SINK_REG_BRIGHT(n) (0x57 + (n * 0x10))
+
+struct wled_var_cfg {
+ const u32 *values;
+ u32 (*fn)(u32);
+ int size;
+};
+
+struct wled_u32_opts {
+ const char *name;
+ u32 *val_ptr;
+ const struct wled_var_cfg *cfg;
+};
+
+struct wled_bool_opts {
+ const char *name;
+ bool *val_ptr;
+};
+
+struct wled_config {
+ u32 boost_i_limit;
+ u32 ovp;
+ u32 switch_freq;
+ u32 num_strings;
+ u32 string_i_limit;
+ u32 enabled_strings[WLED_MAX_STRINGS];
+ bool cs_out_en;
+ bool ext_gen;
+ bool cabc;
+ bool external_pfet;
+ bool auto_detection_enabled;
+};
+
+struct wled {
+ const char *name;
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock; /* Lock to avoid race from thread irq handler */
+ ktime_t last_short_event;
+ ktime_t start_ovp_fault_time;
+ u16 ctrl_addr;
+ u16 sink_addr;
+ u16 max_string_count;
+ u16 auto_detection_ovp_count;
+ u32 brightness;
+ u32 max_brightness;
+ u32 short_count;
+ u32 auto_detect_count;
+ bool disabled_by_short;
+ bool has_short_detect;
+ int short_irq;
+ int ovp_irq;
+
+ struct wled_config cfg;
+ struct delayed_work ovp_work;
+ int (*wled_set_brightness)(struct wled *wled, u16 brightness);
+};
+
+static int wled3_set_brightness(struct wled *wled, u16 brightness)
+{
+ int rc, i;
+ u8 v[2];
+
+ v[0] = brightness & 0xff;
+ v[1] = (brightness >> 8) & 0xf;
+
+ for (i = 0; i < wled->cfg.num_strings; ++i) {
+ rc = regmap_bulk_write(wled->regmap, wled->ctrl_addr +
+ WLED3_SINK_REG_BRIGHT(i), v, 2);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int wled4_set_brightness(struct wled *wled, u16 brightness)
+{
+ int rc, i;
+ u16 low_limit = wled->max_brightness * 4 / 1000;
+ u8 v[2];
+
+ /* WLED4's lower limit of operation is 0.4% */
+ if (brightness > 0 && brightness < low_limit)
+ brightness = low_limit;
+
+ v[0] = brightness & 0xff;
+ v[1] = (brightness >> 8) & 0xf;
+
+ for (i = 0; i < wled->cfg.num_strings; ++i) {
+ rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
+ WLED4_SINK_REG_BRIGHT(i), v, 2);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static void wled_ovp_work(struct work_struct *work)
+{
+ struct wled *wled = container_of(work,
+ struct wled, ovp_work.work);
+ enable_irq(wled->ovp_irq);
+}
+
+static int wled_module_enable(struct wled *wled, int val)
+{
+ int rc;
+
+ if (wled->disabled_by_short)
+ return -ENXIO;
+
+ rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
+ WLED3_CTRL_REG_MOD_EN,
+ WLED3_CTRL_REG_MOD_EN_MASK,
+ val << WLED3_CTRL_REG_MOD_EN_SHIFT);
+ if (rc < 0)
+ return rc;
+
+ if (wled->ovp_irq > 0) {
+ if (val) {
+ /*
+ * The hardware generates a storm of spurious OVP
+ * interrupts during soft start operations. So defer
+ * enabling the IRQ for 10ms to ensure that the
+ * soft start is complete.
+ */
+ schedule_delayed_work(&wled->ovp_work, HZ / 100);
+ } else {
+ if (!cancel_delayed_work_sync(&wled->ovp_work))
+ disable_irq(wled->ovp_irq);
+ }
+ }
+
+ return 0;
+}
+
+static int wled_sync_toggle(struct wled *wled)
+{
+ int rc;
+ unsigned int mask = GENMASK(wled->max_string_count - 1, 0);
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_SINK_REG_SYNC,
+ mask, mask);
+ if (rc < 0)
+ return rc;
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_SINK_REG_SYNC,
+ mask, WLED3_SINK_REG_SYNC_CLEAR);
+
+ return rc;
+}
+
+static int wled_update_status(struct backlight_device *bl)
+{
+ struct wled *wled = bl_get_data(bl);
+ u16 brightness = bl->props.brightness;
+ int rc = 0;
+
+ if (bl->props.power != FB_BLANK_UNBLANK ||
+ bl->props.fb_blank != FB_BLANK_UNBLANK ||
+ bl->props.state & BL_CORE_FBBLANK)
+ brightness = 0;
+
+ mutex_lock(&wled->lock);
+ if (brightness) {
+ rc = wled->wled_set_brightness(wled, brightness);
+ if (rc < 0) {
+ dev_err(wled->dev, "wled failed to set brightness rc:%d\n",
+ rc);
+ goto unlock_mutex;
+ }
+
+ rc = wled_sync_toggle(wled);
+ if (rc < 0) {
+ dev_err(wled->dev, "wled sync failed rc:%d\n", rc);
+ goto unlock_mutex;
+ }
+ }
+
+ if (!!brightness != !!wled->brightness) {
+ rc = wled_module_enable(wled, !!brightness);
+ if (rc < 0) {
+ dev_err(wled->dev, "wled enable failed rc:%d\n", rc);
+ goto unlock_mutex;
+ }
+ }
+
+ wled->brightness = brightness;
+
+unlock_mutex:
+ mutex_unlock(&wled->lock);
+
+ return rc;
+}
+
+#define WLED_SHORT_DLY_MS 20
+#define WLED_SHORT_CNT_MAX 5
+#define WLED_SHORT_RESET_CNT_DLY_US USEC_PER_SEC
+
+static irqreturn_t wled_short_irq_handler(int irq, void *_wled)
+{
+ struct wled *wled = _wled;
+ int rc;
+ s64 elapsed_time;
+
+ wled->short_count++;
+ mutex_lock(&wled->lock);
+ rc = wled_module_enable(wled, false);
+ if (rc < 0) {
+ dev_err(wled->dev, "wled disable failed rc:%d\n", rc);
+ goto unlock_mutex;
+ }
+
+ elapsed_time = ktime_us_delta(ktime_get(),
+ wled->last_short_event);
+ if (elapsed_time > WLED_SHORT_RESET_CNT_DLY_US)
+ wled->short_count = 1;
+
+ if (wled->short_count > WLED_SHORT_CNT_MAX) {
+ dev_err(wled->dev, "Short triggered %d times, disabling WLED forever!\n",
+ wled->short_count);
+ wled->disabled_by_short = true;
+ goto unlock_mutex;
+ }
+
+ wled->last_short_event = ktime_get();
+
+ msleep(WLED_SHORT_DLY_MS);
+ rc = wled_module_enable(wled, true);
+ if (rc < 0)
+ dev_err(wled->dev, "wled enable failed rc:%d\n", rc);
+
+unlock_mutex:
+ mutex_unlock(&wled->lock);
+
+ return IRQ_HANDLED;
+}
+
+#define AUTO_DETECT_BRIGHTNESS 200
+
+static void wled_auto_string_detection(struct wled *wled)
+{
+ int rc = 0, i;
+ u32 sink_config = 0, int_sts;
+ u8 sink_test = 0, sink_valid = 0, val;
+
+ /* Read configured sink configuration */
+ rc = regmap_read(wled->regmap, wled->sink_addr +
+ WLED4_SINK_REG_CURR_SINK, &sink_config);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to read SINK configuration rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+
+ /* Disable the module before starting detection */
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
+ WLED3_CTRL_REG_MOD_EN_MASK, 0);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to disable WLED module rc=%d\n", rc);
+ goto failed_detect;
+ }
+
+ /* Set low brightness across all sinks */
+ rc = wled4_set_brightness(wled, AUTO_DETECT_BRIGHTNESS);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to set brightness for auto detection rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+
+ if (wled->cfg.cabc) {
+ for (i = 0; i < wled->cfg.num_strings; i++) {
+ rc = regmap_update_bits(wled->regmap, wled->sink_addr +
+ WLED4_SINK_REG_STR_CABC(i),
+ WLED4_SINK_REG_STR_CABC_MASK,
+ 0);
+ if (rc < 0)
+ goto failed_detect;
+ }
+ }
+
+ /* Disable all sinks */
+ rc = regmap_write(wled->regmap,
+ wled->sink_addr + WLED4_SINK_REG_CURR_SINK, 0);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to disable all sinks rc=%d\n", rc);
+ goto failed_detect;
+ }
+
+ /* Iterate through the strings one by one */
+ for (i = 0; i < wled->cfg.num_strings; i++) {
+ sink_test = BIT((WLED4_SINK_REG_CURR_SINK_SHFT + i));
+
+ /* Enable feedback control */
+ rc = regmap_write(wled->regmap, wled->ctrl_addr +
+ WLED3_CTRL_REG_FEEDBACK_CONTROL, i + 1);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to enable feedback for SINK %d rc = %d\n",
+ i + 1, rc);
+ goto failed_detect;
+ }
+
+ /* Enable the sink */
+ rc = regmap_write(wled->regmap, wled->sink_addr +
+ WLED4_SINK_REG_CURR_SINK, sink_test);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to configure SINK %d rc=%d\n",
+ i + 1, rc);
+ goto failed_detect;
+ }
+
+ /* Enable the module */
+ rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
+ WLED3_CTRL_REG_MOD_EN,
+ WLED3_CTRL_REG_MOD_EN_MASK,
+ WLED3_CTRL_REG_MOD_EN_MASK);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to enable WLED module rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+
+ usleep_range(WLED_SOFT_START_DLY_US,
+ WLED_SOFT_START_DLY_US + 1000);
+
+ rc = regmap_read(wled->regmap, wled->ctrl_addr +
+ WLED3_CTRL_REG_INT_RT_STS, &int_sts);
+ if (rc < 0) {
+ dev_err(wled->dev, "Error in reading WLED3_CTRL_INT_RT_STS rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+
+ if (int_sts & WLED3_CTRL_REG_OVP_FAULT_STATUS)
+ dev_dbg(wled->dev, "WLED OVP fault detected with SINK %d\n",
+ i + 1);
+ else
+ sink_valid |= sink_test;
+
+ /* Disable the module */
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
+ WLED3_CTRL_REG_MOD_EN_MASK, 0);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to disable WLED module rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+ }
+
+ if (!sink_valid) {
+ dev_err(wled->dev, "No valid WLED sinks found\n");
+ wled->disabled_by_short = true;
+ goto failed_detect;
+ }
+
+ if (sink_valid != sink_config) {
+ dev_warn(wled->dev, "%x is not a valid sink configuration - using %x instead\n",
+ sink_config, sink_valid);
+ sink_config = sink_valid;
+ }
+
+ /* Write the new sink configuration */
+ rc = regmap_write(wled->regmap,
+ wled->sink_addr + WLED4_SINK_REG_CURR_SINK,
+ sink_config);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to reconfigure the default sink rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+
+ /* Enable valid sinks */
+ for (i = 0; i < wled->cfg.num_strings; i++) {
+ if (wled->cfg.cabc) {
+ rc = regmap_update_bits(wled->regmap, wled->sink_addr +
+ WLED4_SINK_REG_STR_CABC(i),
+ WLED4_SINK_REG_STR_CABC_MASK,
+ WLED4_SINK_REG_STR_CABC_MASK);
+ if (rc < 0)
+ goto failed_detect;
+ }
+
+ if (sink_config & BIT(WLED4_SINK_REG_CURR_SINK_SHFT + i))
+ val = WLED4_SINK_REG_STR_MOD_MASK;
+ else
+ val = 0x0; /* Disable modulator_en for unused sink */
+
+ rc = regmap_write(wled->regmap, wled->sink_addr +
+ WLED4_SINK_REG_STR_MOD_EN(i), val);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to configure MODULATOR_EN rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+ }
+
+ /* Restore the feedback setting */
+ rc = regmap_write(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_FEEDBACK_CONTROL, 0);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to restore feedback setting rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+
+ /* Restore brightness */
+ rc = wled4_set_brightness(wled, wled->brightness);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to set brightness after auto detection rc=%d\n",
+ rc);
+ goto failed_detect;
+ }
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
+ WLED3_CTRL_REG_MOD_EN_MASK,
+ WLED3_CTRL_REG_MOD_EN_MASK);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to enable WLED module rc=%d\n", rc);
+ goto failed_detect;
+ }
+
+failed_detect:
+ return;
+}
+
+#define WLED_AUTO_DETECT_OVP_COUNT 5
+#define WLED_AUTO_DETECT_CNT_DLY_US USEC_PER_SEC
+static bool wled_auto_detection_required(struct wled *wled)
+{
+ s64 elapsed_time_us;
+
+ if (!wled->cfg.auto_detection_enabled)
+ return false;
+
+ /*
+ * Check if the OVP fault was an occasional one
+ * or if it's firing continuously, the latter qualifies
+ * for an auto-detection check.
+ */
+ if (!wled->auto_detection_ovp_count) {
+ wled->start_ovp_fault_time = ktime_get();
+ wled->auto_detection_ovp_count++;
+ } else {
+ elapsed_time_us = ktime_us_delta(ktime_get(),
+ wled->start_ovp_fault_time);
+ if (elapsed_time_us > WLED_AUTO_DETECT_CNT_DLY_US)
+ wled->auto_detection_ovp_count = 0;
+ else
+ wled->auto_detection_ovp_count++;
+
+ if (wled->auto_detection_ovp_count >=
+ WLED_AUTO_DETECT_OVP_COUNT) {
+ wled->auto_detection_ovp_count = 0;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int wled_auto_detection_at_init(struct wled *wled)
+{
+ int rc;
+ u32 fault_status, rt_status;
+
+ if (!wled->cfg.auto_detection_enabled)
+ return 0;
+
+ rc = regmap_read(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_INT_RT_STS,
+ &rt_status);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to read RT status rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = regmap_read(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_FAULT_STATUS,
+ &fault_status);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to read fault status rc=%d\n", rc);
+ return rc;
+ }
+
+ if ((rt_status & WLED3_CTRL_REG_OVP_FAULT_STATUS) ||
+ (fault_status & WLED3_CTRL_REG_OVP_FAULT_BIT)) {
+ mutex_lock(&wled->lock);
+ wled_auto_string_detection(wled);
+ mutex_unlock(&wled->lock);
+ }
+
+ return rc;
+}
+
+static irqreturn_t wled_ovp_irq_handler(int irq, void *_wled)
+{
+ struct wled *wled = _wled;
+ int rc;
+ u32 int_sts, fault_sts;
+
+ rc = regmap_read(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_INT_RT_STS, &int_sts);
+ if (rc < 0) {
+ dev_err(wled->dev, "Error in reading WLED3_INT_RT_STS rc=%d\n",
+ rc);
+ return IRQ_HANDLED;
+ }
+
+ rc = regmap_read(wled->regmap, wled->ctrl_addr +
+ WLED3_CTRL_REG_FAULT_STATUS, &fault_sts);
+ if (rc < 0) {
+ dev_err(wled->dev, "Error in reading WLED_FAULT_STATUS rc=%d\n",
+ rc);
+ return IRQ_HANDLED;
+ }
+
+ if (fault_sts & (WLED3_CTRL_REG_OVP_FAULT_BIT |
+ WLED3_CTRL_REG_ILIM_FAULT_BIT))
+ dev_dbg(wled->dev, "WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
+ int_sts, fault_sts);
+
+ if (fault_sts & WLED3_CTRL_REG_OVP_FAULT_BIT) {
+ if (wled_auto_detection_required(wled)) {
+ mutex_lock(&wled->lock);
+ wled_auto_string_detection(wled);
+ mutex_unlock(&wled->lock);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int wled3_setup(struct wled *wled)
+{
+ u16 addr;
+ u8 sink_en = 0;
+ int rc, i, j;
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_OVP,
+ WLED3_CTRL_REG_OVP_MASK, wled->cfg.ovp);
+ if (rc)
+ return rc;
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_ILIMIT,
+ WLED3_CTRL_REG_ILIMIT_MASK,
+ wled->cfg.boost_i_limit);
+ if (rc)
+ return rc;
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_FREQ,
+ WLED3_CTRL_REG_FREQ_MASK,
+ wled->cfg.switch_freq);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < wled->cfg.num_strings; ++i) {
+ j = wled->cfg.enabled_strings[i];
+ addr = wled->ctrl_addr + WLED3_SINK_REG_STR_MOD_EN(j);
+ rc = regmap_update_bits(wled->regmap, addr,
+ WLED3_SINK_REG_STR_MOD_MASK,
+ WLED3_SINK_REG_STR_MOD_MASK);
+ if (rc)
+ return rc;
+
+ if (wled->cfg.ext_gen) {
+ addr = wled->ctrl_addr + WLED3_SINK_REG_STR_MOD_SRC(j);
+ rc = regmap_update_bits(wled->regmap, addr,
+ WLED3_SINK_REG_STR_MOD_SRC_MASK,
+ WLED3_SINK_REG_STR_MOD_SRC_EXT);
+ if (rc)
+ return rc;
+ }
+
+ addr = wled->ctrl_addr + WLED3_SINK_REG_STR_FULL_SCALE_CURR(j);
+ rc = regmap_update_bits(wled->regmap, addr,
+ WLED3_SINK_REG_STR_FULL_SCALE_CURR_MASK,
+ wled->cfg.string_i_limit);
+ if (rc)
+ return rc;
+
+ addr = wled->ctrl_addr + WLED3_SINK_REG_STR_CABC(j);
+ rc = regmap_update_bits(wled->regmap, addr,
+ WLED3_SINK_REG_STR_CABC_MASK,
+ wled->cfg.cabc ?
+ WLED3_SINK_REG_STR_CABC_MASK : 0);
+ if (rc)
+ return rc;
+
+ sink_en |= BIT(j + WLED3_SINK_REG_CURR_SINK_SHFT);
+ }
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_SINK_REG_CURR_SINK,
+ WLED3_SINK_REG_CURR_SINK_MASK, sink_en);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static const struct wled_config wled3_config_defaults = {
+ .boost_i_limit = 3,
+ .string_i_limit = 20,
+ .ovp = 2,
+ .num_strings = 3,
+ .switch_freq = 5,
+ .cs_out_en = false,
+ .ext_gen = false,
+ .cabc = false,
+ .enabled_strings = {0, 1, 2, 3},
+};
+
+static int wled4_setup(struct wled *wled)
+{
+ int rc, temp, i, j;
+ u16 addr;
+ u8 sink_en = 0;
+ u32 sink_cfg;
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_OVP,
+ WLED3_CTRL_REG_OVP_MASK, wled->cfg.ovp);
+ if (rc < 0)
+ return rc;
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_ILIMIT,
+ WLED3_CTRL_REG_ILIMIT_MASK,
+ wled->cfg.boost_i_limit);
+ if (rc < 0)
+ return rc;
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->ctrl_addr + WLED3_CTRL_REG_FREQ,
+ WLED3_CTRL_REG_FREQ_MASK,
+ wled->cfg.switch_freq);
+ if (rc < 0)
+ return rc;
+
+ if (wled->cfg.external_pfet) {
+ /* Unlock the secure register access */
+ rc = regmap_write(wled->regmap, wled->ctrl_addr +
+ WLED4_CTRL_REG_SEC_ACCESS,
+ WLED4_CTRL_REG_SEC_UNLOCK);
+ if (rc < 0)
+ return rc;
+
+ rc = regmap_write(wled->regmap,
+ wled->ctrl_addr + WLED4_CTRL_REG_TEST1,
+ WLED4_CTRL_REG_TEST1_EXT_FET_DTEST2);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = regmap_read(wled->regmap, wled->sink_addr +
+ WLED4_SINK_REG_CURR_SINK, &sink_cfg);
+ if (rc < 0)
+ return rc;
+
+ for (i = 0; i < wled->cfg.num_strings; i++) {
+ j = wled->cfg.enabled_strings[i];
+ temp = j + WLED4_SINK_REG_CURR_SINK_SHFT;
+ sink_en |= 1 << temp;
+ }
+
+ if (sink_cfg == sink_en) {
+ rc = wled_auto_detection_at_init(wled);
+ return rc;
+ }
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->sink_addr + WLED4_SINK_REG_CURR_SINK,
+ WLED4_SINK_REG_CURR_SINK_MASK, 0);
+ if (rc < 0)
+ return rc;
+
+ rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
+ WLED3_CTRL_REG_MOD_EN,
+ WLED3_CTRL_REG_MOD_EN_MASK, 0);
+ if (rc < 0)
+ return rc;
+
+ /* Per sink/string configuration */
+ for (i = 0; i < wled->cfg.num_strings; i++) {
+ j = wled->cfg.enabled_strings[i];
+
+ addr = wled->sink_addr +
+ WLED4_SINK_REG_STR_MOD_EN(j);
+ rc = regmap_update_bits(wled->regmap, addr,
+ WLED4_SINK_REG_STR_MOD_MASK,
+ WLED4_SINK_REG_STR_MOD_MASK);
+ if (rc < 0)
+ return rc;
+
+ addr = wled->sink_addr +
+ WLED4_SINK_REG_STR_FULL_SCALE_CURR(j);
+ rc = regmap_update_bits(wled->regmap, addr,
+ WLED4_SINK_REG_STR_FULL_SCALE_CURR_MASK,
+ wled->cfg.string_i_limit);
+ if (rc < 0)
+ return rc;
+
+ addr = wled->sink_addr +
+ WLED4_SINK_REG_STR_CABC(j);
+ rc = regmap_update_bits(wled->regmap, addr,
+ WLED4_SINK_REG_STR_CABC_MASK,
+ wled->cfg.cabc ?
+ WLED4_SINK_REG_STR_CABC_MASK : 0);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
+ WLED3_CTRL_REG_MOD_EN,
+ WLED3_CTRL_REG_MOD_EN_MASK,
+ WLED3_CTRL_REG_MOD_EN_MASK);
+ if (rc < 0)
+ return rc;
+
+ rc = regmap_update_bits(wled->regmap,
+ wled->sink_addr + WLED4_SINK_REG_CURR_SINK,
+ WLED4_SINK_REG_CURR_SINK_MASK, sink_en);
+ if (rc < 0)
+ return rc;
+
+ rc = wled_sync_toggle(wled);
+ if (rc < 0) {
+ dev_err(wled->dev, "Failed to toggle sync reg rc:%d\n", rc);
+ return rc;
+ }
+
+ rc = wled_auto_detection_at_init(wled);
+
+ return rc;
+}
+
+static const struct wled_config wled4_config_defaults = {
+ .boost_i_limit = 4,
+ .string_i_limit = 10,
+ .ovp = 1,
+ .num_strings = 4,
+ .switch_freq = 11,
+ .cabc = false,
+ .external_pfet = false,
+ .auto_detection_enabled = false,
+};
+
+static const u32 wled3_boost_i_limit_values[] = {
+ 105, 385, 525, 805, 980, 1260, 1400, 1680,
+};
+
+static const struct wled_var_cfg wled3_boost_i_limit_cfg = {
+ .values = wled3_boost_i_limit_values,
+ .size = ARRAY_SIZE(wled3_boost_i_limit_values),
+};
+
+static const u32 wled4_boost_i_limit_values[] = {
+ 105, 280, 450, 620, 970, 1150, 1300, 1500,
+};
+
+static const struct wled_var_cfg wled4_boost_i_limit_cfg = {
+ .values = wled4_boost_i_limit_values,
+ .size = ARRAY_SIZE(wled4_boost_i_limit_values),
+};
+
+static const u32 wled3_ovp_values[] = {
+ 35, 32, 29, 27,
+};
+
+static const struct wled_var_cfg wled3_ovp_cfg = {
+ .values = wled3_ovp_values,
+ .size = ARRAY_SIZE(wled3_ovp_values),
+};
+
+static const u32 wled4_ovp_values[] = {
+ 31100, 29600, 19600, 18100,
+};
+
+static const struct wled_var_cfg wled4_ovp_cfg = {
+ .values = wled4_ovp_values,
+ .size = ARRAY_SIZE(wled4_ovp_values),
+};
+
+static u32 wled3_num_strings_values_fn(u32 idx)
+{
+ return idx + 1;
+}
+
+static const struct wled_var_cfg wled3_num_strings_cfg = {
+ .fn = wled3_num_strings_values_fn,
+ .size = 3,
+};
+
+static const struct wled_var_cfg wled4_num_strings_cfg = {
+ .fn = wled3_num_strings_values_fn,
+ .size = 4,
+};
+
+static u32 wled3_switch_freq_values_fn(u32 idx)
+{
+ return 19200 / (2 * (1 + idx));
+}
+
+static const struct wled_var_cfg wled3_switch_freq_cfg = {
+ .fn = wled3_switch_freq_values_fn,
+ .size = 16,
+};
+
+static const struct wled_var_cfg wled3_string_i_limit_cfg = {
+ .size = 26,
+};
+
+static const u32 wled4_string_i_limit_values[] = {
+ 0, 2500, 5000, 7500, 10000, 12500, 15000, 17500, 20000,
+ 22500, 25000, 27500, 30000,
+};
+
+static const struct wled_var_cfg wled4_string_i_limit_cfg = {
+ .values = wled4_string_i_limit_values,
+ .size = ARRAY_SIZE(wled4_string_i_limit_values),
+};
+
+static const struct wled_var_cfg wled3_string_cfg = {
+ .size = 8,
+};
+
+static const struct wled_var_cfg wled4_string_cfg = {
+ .size = 16,
+};
+
+static u32 wled_values(const struct wled_var_cfg *cfg, u32 idx)
+{
+ if (idx >= cfg->size)
+ return UINT_MAX;
+ if (cfg->fn)
+ return cfg->fn(idx);
+ if (cfg->values)
+ return cfg->values[idx];
+ return idx;
+}
+
+static int wled_configure(struct wled *wled, int version)
+{
+ struct wled_config *cfg = &wled->cfg;
+ struct device *dev = wled->dev;
+ const __be32 *prop_addr;
+ u32 size, val, c, string_len;
+ int rc, i, j;
+
+ const struct wled_u32_opts *u32_opts = NULL;
+ const struct wled_u32_opts wled3_opts[] = {
+ {
+ .name = "qcom,current-boost-limit",
+ .val_ptr = &cfg->boost_i_limit,
+ .cfg = &wled3_boost_i_limit_cfg,
+ },
+ {
+ .name = "qcom,current-limit",
+ .val_ptr = &cfg->string_i_limit,
+ .cfg = &wled3_string_i_limit_cfg,
+ },
+ {
+ .name = "qcom,ovp",
+ .val_ptr = &cfg->ovp,
+ .cfg = &wled3_ovp_cfg,
+ },
+ {
+ .name = "qcom,switching-freq",
+ .val_ptr = &cfg->switch_freq,
+ .cfg = &wled3_switch_freq_cfg,
+ },
+ {
+ .name = "qcom,num-strings",
+ .val_ptr = &cfg->num_strings,
+ .cfg = &wled3_num_strings_cfg,
+ },
+ };
+
+ const struct wled_u32_opts wled4_opts[] = {
+ {
+ .name = "qcom,current-boost-limit",
+ .val_ptr = &cfg->boost_i_limit,
+ .cfg = &wled4_boost_i_limit_cfg,
+ },
+ {
+ .name = "qcom,current-limit-microamp",
+ .val_ptr = &cfg->string_i_limit,
+ .cfg = &wled4_string_i_limit_cfg,
+ },
+ {
+ .name = "qcom,ovp-millivolt",
+ .val_ptr = &cfg->ovp,
+ .cfg = &wled4_ovp_cfg,
+ },
+ {
+ .name = "qcom,switching-freq",
+ .val_ptr = &cfg->switch_freq,
+ .cfg = &wled3_switch_freq_cfg,
+ },
+ {
+ .name = "qcom,num-strings",
+ .val_ptr = &cfg->num_strings,
+ .cfg = &wled4_num_strings_cfg,
+ },
+ };
+
+ const struct wled_bool_opts bool_opts[] = {
+ { "qcom,cs-out", &cfg->cs_out_en, },
+ { "qcom,ext-gen", &cfg->ext_gen, },
+ { "qcom,cabc", &cfg->cabc, },
+ { "qcom,external-pfet", &cfg->external_pfet, },
+ { "qcom,auto-string-detection", &cfg->auto_detection_enabled, },
+ };
+
+ prop_addr = of_get_address(dev->of_node, 0, NULL, NULL);
+ if (!prop_addr) {
+ dev_err(wled->dev, "invalid IO resources\n");
+ return -EINVAL;
+ }
+ wled->ctrl_addr = be32_to_cpu(*prop_addr);
+
+ rc = of_property_read_string(dev->of_node, "label", &wled->name);
+ if (rc)
+ wled->name = devm_kasprintf(dev, GFP_KERNEL, "%pOFn", dev->of_node);
+
+ switch (version) {
+ case 3:
+ u32_opts = wled3_opts;
+ size = ARRAY_SIZE(wled3_opts);
+ *cfg = wled3_config_defaults;
+ wled->wled_set_brightness = wled3_set_brightness;
+ wled->max_string_count = 3;
+ wled->sink_addr = wled->ctrl_addr;
+ break;
+
+ case 4:
+ u32_opts = wled4_opts;
+ size = ARRAY_SIZE(wled4_opts);
+ *cfg = wled4_config_defaults;
+ wled->wled_set_brightness = wled4_set_brightness;
+ wled->max_string_count = 4;
+
+ prop_addr = of_get_address(dev->of_node, 1, NULL, NULL);
+ if (!prop_addr) {
+ dev_err(wled->dev, "invalid IO resources\n");
+ return -EINVAL;
+ }
+ wled->sink_addr = be32_to_cpu(*prop_addr);
+ break;
+
+ default:
+ dev_err(wled->dev, "Invalid WLED version\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < size; ++i) {
+ rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
+ if (rc == -EINVAL) {
+ continue;
+ } else if (rc) {
+ dev_err(dev, "error reading '%s'\n", u32_opts[i].name);
+ return rc;
+ }
+
+ c = UINT_MAX;
+ for (j = 0; c != val; j++) {
+ c = wled_values(u32_opts[i].cfg, j);
+ if (c == UINT_MAX) {
+ dev_err(dev, "invalid value for '%s'\n",
+ u32_opts[i].name);
+ return -EINVAL;
+ }
+
+ if (c == val)
+ break;
+ }
+
+ dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c);
+ *u32_opts[i].val_ptr = j;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
+ if (of_property_read_bool(dev->of_node, bool_opts[i].name))
+ *bool_opts[i].val_ptr = true;
+ }
+
+ cfg->num_strings = cfg->num_strings + 1;
+
+ string_len = of_property_count_elems_of_size(dev->of_node,
+ "qcom,enabled-strings",
+ sizeof(u32));
+ if (string_len > 0)
+ of_property_read_u32_array(dev->of_node,
+ "qcom,enabled-strings",
+ wled->cfg.enabled_strings,
+ sizeof(u32));
+
+ return 0;
+}
+
+static int wled_configure_short_irq(struct wled *wled,
+ struct platform_device *pdev)
+{
+ int rc;
+
+ if (!wled->has_short_detect)
+ return 0;
+
+ rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
+ WLED4_CTRL_REG_SHORT_PROTECT,
+ WLED4_CTRL_REG_SHORT_EN_MASK,
+ WLED4_CTRL_REG_SHORT_EN_MASK);
+ if (rc < 0)
+ return rc;
+
+ wled->short_irq = platform_get_irq_byname(pdev, "short");
+ if (wled->short_irq < 0) {
+ dev_dbg(&pdev->dev, "short irq is not used\n");
+ return 0;
+ }
+
+ rc = devm_request_threaded_irq(wled->dev, wled->short_irq,
+ NULL, wled_short_irq_handler,
+ IRQF_ONESHOT,
+ "wled_short_irq", wled);
+ if (rc < 0)
+ dev_err(wled->dev, "Unable to request short_irq (err:%d)\n",
+ rc);
+
+ return rc;
+}
+
+static int wled_configure_ovp_irq(struct wled *wled,
+ struct platform_device *pdev)
+{
+ int rc;
+ u32 val;
+
+ wled->ovp_irq = platform_get_irq_byname(pdev, "ovp");
+ if (wled->ovp_irq < 0) {
+ dev_dbg(&pdev->dev, "OVP IRQ not found - disabling automatic string detection\n");
+ return 0;
+ }
+
+ rc = devm_request_threaded_irq(wled->dev, wled->ovp_irq, NULL,
+ 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;
+ }
+
+ rc = regmap_read(wled->regmap, wled->ctrl_addr +
+ WLED3_CTRL_REG_MOD_EN, &val);
+ if (rc < 0)
+ return rc;
+
+ /* Keep OVP irq disabled until module is enabled */
+ if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
+ disable_irq(wled->ovp_irq);
+
+ return 0;
+}
+
+static const struct backlight_ops wled_ops = {
+ .update_status = wled_update_status,
+};
+
+static int wled_probe(struct platform_device *pdev)
+{
+ struct backlight_properties props;
+ struct backlight_device *bl;
+ struct wled *wled;
+ struct regmap *regmap;
+ int version;
+ u32 val;
+ int rc;
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap) {
+ dev_err(&pdev->dev, "Unable to get regmap\n");
+ return -EINVAL;
+ }
+
+ wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
+ if (!wled)
+ return -ENOMEM;
+
+ wled->regmap = regmap;
+ wled->dev = &pdev->dev;
+
+ version = (uintptr_t)of_device_get_match_data(&pdev->dev);
+ if (!version) {
+ dev_err(&pdev->dev, "Unknown device version\n");
+ return -ENODEV;
+ }
+
+ mutex_init(&wled->lock);
+ rc = wled_configure(wled, version);
+ if (rc)
+ return rc;
+
+ switch (version) {
+ case 3:
+ wled->cfg.auto_detection_enabled = false;
+ rc = wled3_setup(wled);
+ if (rc) {
+ dev_err(&pdev->dev, "wled3_setup failed\n");
+ return rc;
+ }
+ break;
+
+ case 4:
+ wled->has_short_detect = true;
+ rc = wled4_setup(wled);
+ if (rc) {
+ dev_err(&pdev->dev, "wled4_setup failed\n");
+ return rc;
+ }
+ break;
+
+ default:
+ dev_err(wled->dev, "Invalid WLED version\n");
+ break;
+ }
+
+ INIT_DELAYED_WORK(&wled->ovp_work, wled_ovp_work);
+
+ rc = wled_configure_short_irq(wled, pdev);
+ if (rc < 0)
+ return rc;
+
+ rc = wled_configure_ovp_irq(wled, pdev);
+ if (rc < 0)
+ return rc;
+
+ val = WLED_DEFAULT_BRIGHTNESS;
+ of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_RAW;
+ props.brightness = val;
+ props.max_brightness = WLED3_SINK_REG_BRIGHT_MAX;
+ bl = devm_backlight_device_register(&pdev->dev, wled->name,
+ &pdev->dev, wled,
+ &wled_ops, &props);
+ return PTR_ERR_OR_ZERO(bl);
+};
+
+static int wled_remove(struct platform_device *pdev)
+{
+ struct wled *wled = dev_get_drvdata(&pdev->dev);
+
+ mutex_destroy(&wled->lock);
+ cancel_delayed_work_sync(&wled->ovp_work);
+ disable_irq(wled->short_irq);
+ disable_irq(wled->ovp_irq);
+
+ return 0;
+}
+
+static const struct of_device_id wled_match_table[] = {
+ { .compatible = "qcom,pm8941-wled", .data = (void *)3 },
+ { .compatible = "qcom,pmi8998-wled", .data = (void *)4 },
+ { .compatible = "qcom,pm660l-wled", .data = (void *)4 },
+ {}
+};
+MODULE_DEVICE_TABLE(of, wled_match_table);
+
+static struct platform_driver wled_driver = {
+ .probe = wled_probe,
+ .remove = wled_remove,
+ .driver = {
+ .name = "qcom,wled",
+ .of_match_table = wled_match_table,
+ },
+};
+
+module_platform_driver(wled_driver);
+
+MODULE_DESCRIPTION("Qualcomm WLED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/video/backlight/tosa_bl.c b/drivers/video/backlight/tosa_bl.c
index 1275e815bd86..cff5e96fd988 100644
--- a/drivers/video/backlight/tosa_bl.c
+++ b/drivers/video/backlight/tosa_bl.c
@@ -18,7 +18,7 @@
#include <asm/mach/sharpsl_param.h>
-#include <mach/tosa.h>
+#include "tosa_bl.h"
#define COMADJ_DEFAULT 97
@@ -28,6 +28,7 @@
struct tosa_bl_data {
struct i2c_client *i2c;
struct backlight_device *bl;
+ struct gpio_desc *gpio;
int comadj;
};
@@ -42,7 +43,7 @@ static void tosa_bl_set_backlight(struct tosa_bl_data *data, int brightness)
i2c_smbus_write_byte_data(data->i2c, DAC_CH2, (u8)(brightness & 0xff));
/* SetBacklightVR */
- gpio_set_value(TOSA_GPIO_BL_C20MA, brightness & 0x100);
+ gpiod_set_value(data->gpio, brightness & 0x100);
tosa_bl_enable(spi, brightness);
}
@@ -87,9 +88,8 @@ static int tosa_bl_probe(struct i2c_client *client,
return -ENOMEM;
data->comadj = sharpsl_param.comadj == -1 ? COMADJ_DEFAULT : sharpsl_param.comadj;
-
- ret = devm_gpio_request_one(&client->dev, TOSA_GPIO_BL_C20MA,
- GPIOF_OUT_INIT_LOW, "backlight");
+ data->gpio = devm_gpiod_get(&client->dev, "backlight", GPIOD_OUT_LOW);
+ ret = PTR_ERR_OR_ZERO(data->gpio);
if (ret) {
dev_dbg(&data->bl->dev, "Unable to request gpio!\n");
return ret;
diff --git a/drivers/video/backlight/tosa_bl.h b/drivers/video/backlight/tosa_bl.h
new file mode 100644
index 000000000000..589e17e6fdb2
--- /dev/null
+++ b/drivers/video/backlight/tosa_bl.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _TOSA_BL_H
+#define _TOSA_BL_H
+
+struct spi_device;
+extern int tosa_bl_enable(struct spi_device *spi, int enable);
+
+#endif
diff --git a/drivers/video/backlight/tosa_lcd.c b/drivers/video/backlight/tosa_lcd.c
index 29af8e27b6e5..e8ab583e5098 100644
--- a/drivers/video/backlight/tosa_lcd.c
+++ b/drivers/video/backlight/tosa_lcd.c
@@ -19,7 +19,7 @@
#include <asm/mach/sharpsl_param.h>
-#include <mach/tosa.h>
+#include "tosa_bl.h"
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
@@ -28,12 +28,26 @@
#define TG_REG0_UD 0x0004
#define TG_REG0_LR 0x0008
+/*
+ * Timing Generator
+ */
+#define TG_PNLCTL 0x00
+#define TG_TPOSCTL 0x01
+#define TG_DUTYCTL 0x02
+#define TG_GPOSR 0x03
+#define TG_GPODR1 0x04
+#define TG_GPODR2 0x05
+#define TG_PINICTL 0x06
+#define TG_HPOSCTL 0x07
+
+
#define DAC_BASE 0x4e
struct tosa_lcd_data {
struct spi_device *spi;
struct lcd_device *lcd;
struct i2c_client *i2c;
+ struct gpio_desc *gpiod_tg;
int lcd_power;
bool is_vga;
@@ -66,7 +80,7 @@ EXPORT_SYMBOL(tosa_bl_enable);
static void tosa_lcd_tg_init(struct tosa_lcd_data *data)
{
/* TG on */
- gpio_set_value(TOSA_GPIO_TG_ON, 0);
+ gpiod_set_value(data->gpiod_tg, 0);
mdelay(60);
@@ -100,6 +114,7 @@ static void tosa_lcd_tg_on(struct tosa_lcd_data *data)
*/
struct i2c_adapter *adap = i2c_get_adapter(0);
struct i2c_board_info info = {
+ .dev_name = "tosa-bl",
.type = "tosa-bl",
.addr = DAC_BASE,
.platform_data = data->spi,
@@ -121,7 +136,7 @@ static void tosa_lcd_tg_off(struct tosa_lcd_data *data)
mdelay(50);
/* TG Off */
- gpio_set_value(TOSA_GPIO_TG_ON, 1);
+ gpiod_set_value(data->gpiod_tg, 1);
mdelay(100);
}
@@ -191,10 +206,9 @@ static int tosa_lcd_probe(struct spi_device *spi)
data->spi = spi;
spi_set_drvdata(spi, data);
- ret = devm_gpio_request_one(&spi->dev, TOSA_GPIO_TG_ON,
- GPIOF_OUT_INIT_LOW, "tg #pwr");
- if (ret < 0)
- return ret;
+ data->gpiod_tg = devm_gpiod_get(&spi->dev, "tg #pwr", GPIOD_OUT_LOW);
+ if (IS_ERR(data->gpiod_tg))
+ return PTR_ERR(data->gpiod_tg);
mdelay(60);
diff --git a/drivers/video/console/vgacon.c b/drivers/video/console/vgacon.c
index c6b3bdbbdbc9..de7b8382aba9 100644
--- a/drivers/video/console/vgacon.c
+++ b/drivers/video/console/vgacon.c
@@ -113,9 +113,9 @@ static int __init text_mode(char *str)
{
vgacon_text_mode_force = true;
- pr_warning("You have booted with nomodeset. This means your GPU drivers are DISABLED\n");
- pr_warning("Any video related functionality will be severely degraded, and you may not even be able to suspend the system properly\n");
- pr_warning("Unless you actually understand what nomodeset does, you should reboot without enabling it\n");
+ pr_warn("You have booted with nomodeset. This means your GPU drivers are DISABLED\n");
+ pr_warn("Any video related functionality will be severely degraded, and you may not even be able to suspend the system properly\n");
+ pr_warn("Unless you actually understand what nomodeset does, you should reboot without enabling it\n");
return 1;
}
diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
index 1e70e838530e..aa9541bf964b 100644
--- a/drivers/video/fbdev/Kconfig
+++ b/drivers/video/fbdev/Kconfig
@@ -2214,6 +2214,7 @@ config FB_HYPERV
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
+ select FB_DEFERRED_IO
help
This framebuffer driver supports Microsoft Hyper-V Synthetic Video.
diff --git a/drivers/video/fbdev/aty/atyfb_base.c b/drivers/video/fbdev/aty/atyfb_base.c
index 6dda5d885a03..79d548746efd 100644
--- a/drivers/video/fbdev/aty/atyfb_base.c
+++ b/drivers/video/fbdev/aty/atyfb_base.c
@@ -48,7 +48,7 @@
******************************************************************************/
-
+#include <linux/compat.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
@@ -235,6 +235,13 @@ static int atyfb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info);
static int atyfb_blank(int blank, struct fb_info *info);
static int atyfb_ioctl(struct fb_info *info, u_int cmd, u_long arg);
+#ifdef CONFIG_COMPAT
+static int atyfb_compat_ioctl(struct fb_info *info, u_int cmd, u_long arg)
+{
+ return atyfb_ioctl(info, cmd, (u_long)compat_ptr(arg));
+}
+#endif
+
#ifdef __sparc__
static int atyfb_mmap(struct fb_info *info, struct vm_area_struct *vma);
#endif
@@ -290,6 +297,9 @@ static struct fb_ops atyfb_ops = {
.fb_pan_display = atyfb_pan_display,
.fb_blank = atyfb_blank,
.fb_ioctl = atyfb_ioctl,
+#ifdef CONFIG_COMPAT
+ .fb_compat_ioctl = atyfb_compat_ioctl,
+#endif
.fb_fillrect = atyfb_fillrect,
.fb_copyarea = atyfb_copyarea,
.fb_imageblit = atyfb_imageblit,
diff --git a/drivers/video/fbdev/c2p_core.h b/drivers/video/fbdev/c2p_core.h
index e1035a865fb9..45a6d895a7d7 100644
--- a/drivers/video/fbdev/c2p_core.h
+++ b/drivers/video/fbdev/c2p_core.h
@@ -29,7 +29,7 @@ static inline void _transp(u32 d[], unsigned int i1, unsigned int i2,
extern void c2p_unsupported(void);
-static inline u32 get_mask(unsigned int n)
+static __always_inline u32 get_mask(unsigned int n)
{
switch (n) {
case 1:
@@ -57,7 +57,7 @@ static inline u32 get_mask(unsigned int n)
* Transpose operations on 8 32-bit words
*/
-static inline void transp8(u32 d[], unsigned int n, unsigned int m)
+static __always_inline void transp8(u32 d[], unsigned int n, unsigned int m)
{
u32 mask = get_mask(n);
@@ -99,7 +99,7 @@ static inline void transp8(u32 d[], unsigned int n, unsigned int m)
* Transpose operations on 4 32-bit words
*/
-static inline void transp4(u32 d[], unsigned int n, unsigned int m)
+static __always_inline void transp4(u32 d[], unsigned int n, unsigned int m)
{
u32 mask = get_mask(n);
@@ -126,7 +126,7 @@ static inline void transp4(u32 d[], unsigned int n, unsigned int m)
* Transpose operations on 4 32-bit words (reverse order)
*/
-static inline void transp4x(u32 d[], unsigned int n, unsigned int m)
+static __always_inline void transp4x(u32 d[], unsigned int n, unsigned int m)
{
u32 mask = get_mask(n);
diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c
index e6a1c805064f..95c32952fa8a 100644
--- a/drivers/video/fbdev/core/fbmem.c
+++ b/drivers/video/fbdev/core/fbmem.c
@@ -1758,21 +1758,19 @@ EXPORT_SYMBOL(remove_conflicting_framebuffers);
/**
* remove_conflicting_pci_framebuffers - remove firmware-configured framebuffers for PCI devices
* @pdev: PCI device
- * @res_id: index of PCI BAR configuring framebuffer memory
* @name: requesting driver name
*
* This function removes framebuffer devices (eg. initialized by firmware)
- * using memory range configured for @pdev's BAR @res_id.
+ * using memory range configured for any of @pdev's memory bars.
*
* The function assumes that PCI device with shadowed ROM drives a primary
* display and so kicks out vga16fb.
*/
-int remove_conflicting_pci_framebuffers(struct pci_dev *pdev, int res_id, const char *name)
+int remove_conflicting_pci_framebuffers(struct pci_dev *pdev, const char *name)
{
struct apertures_struct *ap;
bool primary = false;
int err, idx, bar;
- bool res_id_found = false;
for (idx = 0, bar = 0; bar < PCI_ROM_RESOURCE; bar++) {
if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM))
@@ -1789,16 +1787,11 @@ int remove_conflicting_pci_framebuffers(struct pci_dev *pdev, int res_id, const
continue;
ap->ranges[idx].base = pci_resource_start(pdev, bar);
ap->ranges[idx].size = pci_resource_len(pdev, bar);
- pci_info(pdev, "%s: bar %d: 0x%lx -> 0x%lx\n", __func__, bar,
- (unsigned long)pci_resource_start(pdev, bar),
- (unsigned long)pci_resource_end(pdev, bar));
+ pci_dbg(pdev, "%s: bar %d: 0x%lx -> 0x%lx\n", __func__, bar,
+ (unsigned long)pci_resource_start(pdev, bar),
+ (unsigned long)pci_resource_end(pdev, bar));
idx++;
- if (res_id == bar)
- res_id_found = true;
}
- if (!res_id_found)
- pci_warn(pdev, "%s: passed res_id (%d) is not a memory bar\n",
- __func__, res_id);
#ifdef CONFIG_X86
primary = pdev->resource[PCI_ROM_RESOURCE].flags &
diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c
index 2dcb7c58b31e..4cd27e5172a1 100644
--- a/drivers/video/fbdev/hyperv_fb.c
+++ b/drivers/video/fbdev/hyperv_fb.c
@@ -23,6 +23,14 @@
*
* Portrait orientation is also supported:
* For example: video=hyperv_fb:864x1152
+ *
+ * When a Windows 10 RS5+ host is used, the virtual machine screen
+ * resolution is obtained from the host. The "video=hyperv_fb" option is
+ * not needed, but still can be used to overwrite what the host specifies.
+ * The VM resolution on the host could be set by executing the powershell
+ * "set-vmvideo" command. For example
+ * set-vmvideo -vmname name -horizontalresolution:1920 \
+ * -verticalresolution:1200 -resolutiontype single
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -34,6 +42,7 @@
#include <linux/fb.h>
#include <linux/pci.h>
#include <linux/efi.h>
+#include <linux/console.h>
#include <linux/hyperv.h>
@@ -44,6 +53,10 @@
#define SYNTHVID_VERSION(major, minor) ((minor) << 16 | (major))
#define SYNTHVID_VERSION_WIN7 SYNTHVID_VERSION(3, 0)
#define SYNTHVID_VERSION_WIN8 SYNTHVID_VERSION(3, 2)
+#define SYNTHVID_VERSION_WIN10 SYNTHVID_VERSION(3, 5)
+
+#define SYNTHVID_VER_GET_MAJOR(ver) (ver & 0x0000ffff)
+#define SYNTHVID_VER_GET_MINOR(ver) ((ver & 0xffff0000) >> 16)
#define SYNTHVID_DEPTH_WIN7 16
#define SYNTHVID_DEPTH_WIN8 32
@@ -82,16 +95,25 @@ enum synthvid_msg_type {
SYNTHVID_POINTER_SHAPE = 8,
SYNTHVID_FEATURE_CHANGE = 9,
SYNTHVID_DIRT = 10,
+ SYNTHVID_RESOLUTION_REQUEST = 13,
+ SYNTHVID_RESOLUTION_RESPONSE = 14,
- SYNTHVID_MAX = 11
+ SYNTHVID_MAX = 15
};
+#define SYNTHVID_EDID_BLOCK_SIZE 128
+#define SYNTHVID_MAX_RESOLUTION_COUNT 64
+
+struct hvd_screen_info {
+ u16 width;
+ u16 height;
+} __packed;
+
struct synthvid_msg_hdr {
u32 type;
u32 size; /* size of this header + payload after this field*/
} __packed;
-
struct synthvid_version_req {
u32 version;
} __packed;
@@ -102,6 +124,19 @@ struct synthvid_version_resp {
u8 max_video_outputs;
} __packed;
+struct synthvid_supported_resolution_req {
+ u8 maximum_resolution_count;
+} __packed;
+
+struct synthvid_supported_resolution_resp {
+ u8 edid_block[SYNTHVID_EDID_BLOCK_SIZE];
+ u8 resolution_count;
+ u8 default_resolution_index;
+ u8 is_standard;
+ struct hvd_screen_info
+ supported_resolution[SYNTHVID_MAX_RESOLUTION_COUNT];
+} __packed;
+
struct synthvid_vram_location {
u64 user_ctx;
u8 is_vram_gpa_specified;
@@ -187,6 +222,8 @@ struct synthvid_msg {
struct synthvid_pointer_shape ptr_shape;
struct synthvid_feature_change feature_chg;
struct synthvid_dirt dirt;
+ struct synthvid_supported_resolution_req resolution_req;
+ struct synthvid_supported_resolution_resp resolution_resp;
};
} __packed;
@@ -201,6 +238,7 @@ struct synthvid_msg {
#define RING_BUFSIZE (256 * 1024)
#define VSP_TIMEOUT (10 * HZ)
#define HVFB_UPDATE_DELAY (HZ / 20)
+#define HVFB_ONDEMAND_THROTTLE (HZ / 20)
struct hvfb_par {
struct fb_info *info;
@@ -211,6 +249,7 @@ struct hvfb_par {
struct delayed_work dwork;
bool update;
+ bool update_saved; /* The value of 'update' before hibernation */
u32 pseudo_palette[16];
u8 init_buf[MAX_VMBUS_PKT_SIZE];
@@ -220,12 +259,25 @@ struct hvfb_par {
bool synchronous_fb;
struct notifier_block hvfb_panic_nb;
+
+ /* Memory for deferred IO and frame buffer itself */
+ unsigned char *dio_vp;
+ unsigned char *mmio_vp;
+ unsigned long mmio_pp;
+
+ /* Dirty rectangle, protected by delayed_refresh_lock */
+ int x1, y1, x2, y2;
+ bool delayed_refresh;
+ spinlock_t delayed_refresh_lock;
};
static uint screen_width = HVFB_WIDTH;
static uint screen_height = HVFB_HEIGHT;
+static uint screen_width_max = HVFB_WIDTH;
+static uint screen_height_max = HVFB_HEIGHT;
static uint screen_depth;
static uint screen_fb_size;
+static uint dio_fb_size; /* FB size for deferred IO */
/* Send message to Hyper-V host */
static inline int synthvid_send(struct hv_device *hdev,
@@ -312,28 +364,88 @@ static int synthvid_send_ptr(struct hv_device *hdev)
}
/* Send updated screen area (dirty rectangle) location to host */
-static int synthvid_update(struct fb_info *info)
+static int
+synthvid_update(struct fb_info *info, int x1, int y1, int x2, int y2)
{
struct hv_device *hdev = device_to_hv_device(info->device);
struct synthvid_msg msg;
memset(&msg, 0, sizeof(struct synthvid_msg));
+ if (x2 == INT_MAX)
+ x2 = info->var.xres;
+ if (y2 == INT_MAX)
+ y2 = info->var.yres;
msg.vid_hdr.type = SYNTHVID_DIRT;
msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
sizeof(struct synthvid_dirt);
msg.dirt.video_output = 0;
msg.dirt.dirt_count = 1;
- msg.dirt.rect[0].x1 = 0;
- msg.dirt.rect[0].y1 = 0;
- msg.dirt.rect[0].x2 = info->var.xres;
- msg.dirt.rect[0].y2 = info->var.yres;
+ msg.dirt.rect[0].x1 = (x1 > x2) ? 0 : x1;
+ msg.dirt.rect[0].y1 = (y1 > y2) ? 0 : y1;
+ msg.dirt.rect[0].x2 =
+ (x2 < x1 || x2 > info->var.xres) ? info->var.xres : x2;
+ msg.dirt.rect[0].y2 =
+ (y2 < y1 || y2 > info->var.yres) ? info->var.yres : y2;
synthvid_send(hdev, &msg);
return 0;
}
+static void hvfb_docopy(struct hvfb_par *par,
+ unsigned long offset,
+ unsigned long size)
+{
+ if (!par || !par->mmio_vp || !par->dio_vp || !par->fb_ready ||
+ size == 0 || offset >= dio_fb_size)
+ return;
+
+ if (offset + size > dio_fb_size)
+ size = dio_fb_size - offset;
+
+ memcpy(par->mmio_vp + offset, par->dio_vp + offset, size);
+}
+
+/* Deferred IO callback */
+static void synthvid_deferred_io(struct fb_info *p,
+ struct list_head *pagelist)
+{
+ struct hvfb_par *par = p->par;
+ struct page *page;
+ unsigned long start, end;
+ int y1, y2, miny, maxy;
+
+ miny = INT_MAX;
+ maxy = 0;
+
+ /*
+ * Merge dirty pages. It is possible that last page cross
+ * over the end of frame buffer row yres. This is taken care of
+ * in synthvid_update function by clamping the y2
+ * value to yres.
+ */
+ list_for_each_entry(page, pagelist, lru) {
+ start = page->index << PAGE_SHIFT;
+ end = start + PAGE_SIZE - 1;
+ y1 = start / p->fix.line_length;
+ y2 = end / p->fix.line_length;
+ miny = min_t(int, miny, y1);
+ maxy = max_t(int, maxy, y2);
+
+ /* Copy from dio space to mmio address */
+ if (par->fb_ready)
+ hvfb_docopy(par, start, PAGE_SIZE);
+ }
+
+ if (par->fb_ready && par->update)
+ synthvid_update(p, 0, miny, p->var.xres, maxy + 1);
+}
+
+static struct fb_deferred_io synthvid_defio = {
+ .delay = HZ / 20,
+ .deferred_io = synthvid_deferred_io,
+};
/*
* Actions on received messages from host:
@@ -354,6 +466,7 @@ static void synthvid_recv_sub(struct hv_device *hdev)
/* Complete the wait event */
if (msg->vid_hdr.type == SYNTHVID_VERSION_RESPONSE ||
+ msg->vid_hdr.type == SYNTHVID_RESOLUTION_RESPONSE ||
msg->vid_hdr.type == SYNTHVID_VRAM_LOCATION_ACK) {
memcpy(par->init_buf, msg, MAX_VMBUS_PKT_SIZE);
complete(&par->wait);
@@ -400,6 +513,17 @@ static void synthvid_receive(void *ctx)
} while (bytes_recvd > 0 && ret == 0);
}
+/* Check if the ver1 version is equal or greater than ver2 */
+static inline bool synthvid_ver_ge(u32 ver1, u32 ver2)
+{
+ if (SYNTHVID_VER_GET_MAJOR(ver1) > SYNTHVID_VER_GET_MAJOR(ver2) ||
+ (SYNTHVID_VER_GET_MAJOR(ver1) == SYNTHVID_VER_GET_MAJOR(ver2) &&
+ SYNTHVID_VER_GET_MINOR(ver1) >= SYNTHVID_VER_GET_MINOR(ver2)))
+ return true;
+
+ return false;
+}
+
/* Check synthetic video protocol version with the host */
static int synthvid_negotiate_ver(struct hv_device *hdev, u32 ver)
{
@@ -428,6 +552,64 @@ static int synthvid_negotiate_ver(struct hv_device *hdev, u32 ver)
}
par->synthvid_version = ver;
+ pr_info("Synthvid Version major %d, minor %d\n",
+ SYNTHVID_VER_GET_MAJOR(ver), SYNTHVID_VER_GET_MINOR(ver));
+
+out:
+ return ret;
+}
+
+/* Get current resolution from the host */
+static int synthvid_get_supported_resolution(struct hv_device *hdev)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par = info->par;
+ struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf;
+ int ret = 0;
+ unsigned long t;
+ u8 index;
+ int i;
+
+ memset(msg, 0, sizeof(struct synthvid_msg));
+ msg->vid_hdr.type = SYNTHVID_RESOLUTION_REQUEST;
+ msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
+ sizeof(struct synthvid_supported_resolution_req);
+
+ msg->resolution_req.maximum_resolution_count =
+ SYNTHVID_MAX_RESOLUTION_COUNT;
+ synthvid_send(hdev, msg);
+
+ t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT);
+ if (!t) {
+ pr_err("Time out on waiting resolution response\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ if (msg->resolution_resp.resolution_count == 0) {
+ pr_err("No supported resolutions\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ index = msg->resolution_resp.default_resolution_index;
+ if (index >= msg->resolution_resp.resolution_count) {
+ pr_err("Invalid resolution index: %d\n", index);
+ ret = -ENODEV;
+ goto out;
+ }
+
+ for (i = 0; i < msg->resolution_resp.resolution_count; i++) {
+ screen_width_max = max_t(unsigned int, screen_width_max,
+ msg->resolution_resp.supported_resolution[i].width);
+ screen_height_max = max_t(unsigned int, screen_height_max,
+ msg->resolution_resp.supported_resolution[i].height);
+ }
+
+ screen_width =
+ msg->resolution_resp.supported_resolution[index].width;
+ screen_height =
+ msg->resolution_resp.supported_resolution[index].height;
out:
return ret;
@@ -448,11 +630,27 @@ static int synthvid_connect_vsp(struct hv_device *hdev)
}
/* Negotiate the protocol version with host */
- if (vmbus_proto_version == VERSION_WS2008 ||
- vmbus_proto_version == VERSION_WIN7)
- ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN7);
- else
+ switch (vmbus_proto_version) {
+ case VERSION_WIN10:
+ case VERSION_WIN10_V5:
+ ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN10);
+ if (!ret)
+ break;
+ /* Fallthrough */
+ case VERSION_WIN8:
+ case VERSION_WIN8_1:
ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN8);
+ if (!ret)
+ break;
+ /* Fallthrough */
+ case VERSION_WS2008:
+ case VERSION_WIN7:
+ ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN7);
+ break;
+ default:
+ ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN10);
+ break;
+ }
if (ret) {
pr_err("Synthetic video device version not accepted\n");
@@ -464,6 +662,12 @@ static int synthvid_connect_vsp(struct hv_device *hdev)
else
screen_depth = SYNTHVID_DEPTH_WIN8;
+ if (synthvid_ver_ge(par->synthvid_version, SYNTHVID_VERSION_WIN10)) {
+ ret = synthvid_get_supported_resolution(hdev);
+ if (ret)
+ pr_info("Failed to get supported resolution from host, use default\n");
+ }
+
screen_fb_size = hdev->channel->offermsg.offer.
mmio_megabytes * 1024 * 1024;
@@ -488,7 +692,7 @@ static int synthvid_send_config(struct hv_device *hdev)
msg->vid_hdr.type = SYNTHVID_VRAM_LOCATION;
msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
sizeof(struct synthvid_vram_location);
- msg->vram.user_ctx = msg->vram.vram_gpa = info->fix.smem_start;
+ msg->vram.user_ctx = msg->vram.vram_gpa = par->mmio_pp;
msg->vram.is_vram_gpa_specified = 1;
synthvid_send(hdev, msg);
@@ -498,7 +702,7 @@ static int synthvid_send_config(struct hv_device *hdev)
ret = -ETIMEDOUT;
goto out;
}
- if (msg->vram_ack.user_ctx != info->fix.smem_start) {
+ if (msg->vram_ack.user_ctx != par->mmio_pp) {
pr_err("Unable to set VRAM location\n");
ret = -ENODEV;
goto out;
@@ -515,19 +719,77 @@ out:
/*
* Delayed work callback:
- * It is called at HVFB_UPDATE_DELAY or longer time interval to process
- * screen updates. It is re-scheduled if further update is necessary.
+ * It is scheduled to call whenever update request is received and it has
+ * not been called in last HVFB_ONDEMAND_THROTTLE time interval.
*/
static void hvfb_update_work(struct work_struct *w)
{
struct hvfb_par *par = container_of(w, struct hvfb_par, dwork.work);
struct fb_info *info = par->info;
+ unsigned long flags;
+ int x1, x2, y1, y2;
+ int j;
+
+ spin_lock_irqsave(&par->delayed_refresh_lock, flags);
+ /* Reset the request flag */
+ par->delayed_refresh = false;
+
+ /* Store the dirty rectangle to local variables */
+ x1 = par->x1;
+ x2 = par->x2;
+ y1 = par->y1;
+ y2 = par->y2;
+
+ /* Clear dirty rectangle */
+ par->x1 = par->y1 = INT_MAX;
+ par->x2 = par->y2 = 0;
+
+ spin_unlock_irqrestore(&par->delayed_refresh_lock, flags);
+
+ if (x1 > info->var.xres || x2 > info->var.xres ||
+ y1 > info->var.yres || y2 > info->var.yres || x2 <= x1)
+ return;
+
+ /* Copy the dirty rectangle to frame buffer memory */
+ for (j = y1; j < y2; j++) {
+ hvfb_docopy(par,
+ j * info->fix.line_length +
+ (x1 * screen_depth / 8),
+ (x2 - x1) * screen_depth / 8);
+ }
+
+ /* Refresh */
+ if (par->fb_ready && par->update)
+ synthvid_update(info, x1, y1, x2, y2);
+}
- if (par->fb_ready)
- synthvid_update(info);
+/*
+ * Control the on-demand refresh frequency. It schedules a delayed
+ * screen update if it has not yet.
+ */
+static void hvfb_ondemand_refresh_throttle(struct hvfb_par *par,
+ int x1, int y1, int w, int h)
+{
+ unsigned long flags;
+ int x2 = x1 + w;
+ int y2 = y1 + h;
+
+ spin_lock_irqsave(&par->delayed_refresh_lock, flags);
+
+ /* Merge dirty rectangle */
+ par->x1 = min_t(int, par->x1, x1);
+ par->y1 = min_t(int, par->y1, y1);
+ par->x2 = max_t(int, par->x2, x2);
+ par->y2 = max_t(int, par->y2, y2);
+
+ /* Schedule a delayed screen update if not yet */
+ if (par->delayed_refresh == false) {
+ schedule_delayed_work(&par->dwork,
+ HVFB_ONDEMAND_THROTTLE);
+ par->delayed_refresh = true;
+ }
- if (par->update)
- schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY);
+ spin_unlock_irqrestore(&par->delayed_refresh_lock, flags);
}
static int hvfb_on_panic(struct notifier_block *nb,
@@ -539,7 +801,8 @@ static int hvfb_on_panic(struct notifier_block *nb,
par = container_of(nb, struct hvfb_par, hvfb_panic_nb);
par->synchronous_fb = true;
info = par->info;
- synthvid_update(info);
+ hvfb_docopy(par, 0, dio_fb_size);
+ synthvid_update(info, 0, 0, INT_MAX, INT_MAX);
return NOTIFY_DONE;
}
@@ -600,7 +863,10 @@ static void hvfb_cfb_fillrect(struct fb_info *p,
cfb_fillrect(p, rect);
if (par->synchronous_fb)
- synthvid_update(p);
+ synthvid_update(p, 0, 0, INT_MAX, INT_MAX);
+ else
+ hvfb_ondemand_refresh_throttle(par, rect->dx, rect->dy,
+ rect->width, rect->height);
}
static void hvfb_cfb_copyarea(struct fb_info *p,
@@ -610,7 +876,10 @@ static void hvfb_cfb_copyarea(struct fb_info *p,
cfb_copyarea(p, area);
if (par->synchronous_fb)
- synthvid_update(p);
+ synthvid_update(p, 0, 0, INT_MAX, INT_MAX);
+ else
+ hvfb_ondemand_refresh_throttle(par, area->dx, area->dy,
+ area->width, area->height);
}
static void hvfb_cfb_imageblit(struct fb_info *p,
@@ -620,7 +889,10 @@ static void hvfb_cfb_imageblit(struct fb_info *p,
cfb_imageblit(p, image);
if (par->synchronous_fb)
- synthvid_update(p);
+ synthvid_update(p, 0, 0, INT_MAX, INT_MAX);
+ else
+ hvfb_ondemand_refresh_throttle(par, image->dx, image->dy,
+ image->width, image->height);
}
static struct fb_ops hvfb_ops = {
@@ -653,6 +925,8 @@ static void hvfb_get_option(struct fb_info *info)
}
if (x < HVFB_WIDTH_MIN || y < HVFB_HEIGHT_MIN ||
+ (synthvid_ver_ge(par->synthvid_version, SYNTHVID_VERSION_WIN10) &&
+ (x > screen_width_max || y > screen_height_max)) ||
(par->synthvid_version == SYNTHVID_VERSION_WIN8 &&
x * y * screen_depth / 8 > SYNTHVID_FB_SIZE_WIN8) ||
(par->synthvid_version == SYNTHVID_VERSION_WIN7 &&
@@ -677,6 +951,9 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
resource_size_t pot_start, pot_end;
int ret;
+ dio_fb_size =
+ screen_width * screen_height * screen_depth / 8;
+
if (gen2vm) {
pot_start = 0;
pot_end = -1;
@@ -689,8 +966,12 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
}
if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM) ||
- pci_resource_len(pdev, 0) < screen_fb_size)
+ pci_resource_len(pdev, 0) < screen_fb_size) {
+ pr_err("Resource not available or (0x%lx < 0x%lx)\n",
+ (unsigned long) pci_resource_len(pdev, 0),
+ (unsigned long) screen_fb_size);
goto err1;
+ }
pot_end = pci_resource_end(pdev, 0);
pot_start = pot_end - screen_fb_size + 1;
@@ -707,9 +988,14 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
if (!fb_virt)
goto err2;
+ /* Allocate memory for deferred IO */
+ par->dio_vp = vzalloc(round_up(dio_fb_size, PAGE_SIZE));
+ if (par->dio_vp == NULL)
+ goto err3;
+
info->apertures = alloc_apertures(1);
if (!info->apertures)
- goto err3;
+ goto err4;
if (gen2vm) {
info->apertures->ranges[0].base = screen_info.lfb_base;
@@ -721,16 +1007,23 @@ static int hvfb_getmem(struct hv_device *hdev, struct fb_info *info)
info->apertures->ranges[0].size = pci_resource_len(pdev, 0);
}
+ /* Physical address of FB device */
+ par->mmio_pp = par->mem->start;
+ /* Virtual address of FB device */
+ par->mmio_vp = (unsigned char *) fb_virt;
+
info->fix.smem_start = par->mem->start;
- info->fix.smem_len = screen_fb_size;
- info->screen_base = fb_virt;
- info->screen_size = screen_fb_size;
+ info->fix.smem_len = dio_fb_size;
+ info->screen_base = par->dio_vp;
+ info->screen_size = dio_fb_size;
if (!gen2vm)
pci_dev_put(pdev);
return 0;
+err4:
+ vfree(par->dio_vp);
err3:
iounmap(fb_virt);
err2:
@@ -748,6 +1041,7 @@ static void hvfb_putmem(struct fb_info *info)
{
struct hvfb_par *par = info->par;
+ vfree(par->dio_vp);
iounmap(info->screen_base);
vmbus_free_mmio(par->mem->start, screen_fb_size);
par->mem = NULL;
@@ -771,6 +1065,11 @@ static int hvfb_probe(struct hv_device *hdev,
init_completion(&par->wait);
INIT_DELAYED_WORK(&par->dwork, hvfb_update_work);
+ par->delayed_refresh = false;
+ spin_lock_init(&par->delayed_refresh_lock);
+ par->x1 = par->y1 = INT_MAX;
+ par->x2 = par->y2 = 0;
+
/* Connect to VSP */
hv_set_drvdata(hdev, info);
ret = synthvid_connect_vsp(hdev);
@@ -779,17 +1078,16 @@ static int hvfb_probe(struct hv_device *hdev,
goto error1;
}
+ hvfb_get_option(info);
+ pr_info("Screen resolution: %dx%d, Color depth: %d\n",
+ screen_width, screen_height, screen_depth);
+
ret = hvfb_getmem(hdev, info);
if (ret) {
pr_err("No memory for framebuffer\n");
goto error2;
}
- hvfb_get_option(info);
- pr_info("Screen resolution: %dx%d, Color depth: %d\n",
- screen_width, screen_height, screen_depth);
-
-
/* Set up fb_info */
info->flags = FBINFO_DEFAULT;
@@ -823,6 +1121,10 @@ static int hvfb_probe(struct hv_device *hdev,
info->fbops = &hvfb_ops;
info->pseudo_palette = par->pseudo_palette;
+ /* Initialize deferred IO */
+ info->fbdefio = &synthvid_defio;
+ fb_deferred_io_init(info);
+
/* Send config to host */
ret = synthvid_send_config(hdev);
if (ret)
@@ -844,6 +1146,7 @@ static int hvfb_probe(struct hv_device *hdev,
return 0;
error:
+ fb_deferred_io_cleanup(info);
hvfb_putmem(info);
error2:
vmbus_close(hdev->channel);
@@ -866,6 +1169,8 @@ static int hvfb_remove(struct hv_device *hdev)
par->update = false;
par->fb_ready = false;
+ fb_deferred_io_cleanup(info);
+
unregister_framebuffer(info);
cancel_delayed_work_sync(&par->dwork);
@@ -878,6 +1183,61 @@ static int hvfb_remove(struct hv_device *hdev)
return 0;
}
+static int hvfb_suspend(struct hv_device *hdev)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par = info->par;
+
+ console_lock();
+
+ /* 1 means do suspend */
+ fb_set_suspend(info, 1);
+
+ cancel_delayed_work_sync(&par->dwork);
+
+ par->update_saved = par->update;
+ par->update = false;
+ par->fb_ready = false;
+
+ vmbus_close(hdev->channel);
+
+ console_unlock();
+
+ return 0;
+}
+
+static int hvfb_resume(struct hv_device *hdev)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par = info->par;
+ int ret;
+
+ console_lock();
+
+ ret = synthvid_connect_vsp(hdev);
+ if (ret != 0)
+ goto out;
+
+ ret = synthvid_send_config(hdev);
+ if (ret != 0) {
+ vmbus_close(hdev->channel);
+ goto out;
+ }
+
+ par->fb_ready = true;
+ par->update = par->update_saved;
+
+ schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY);
+
+ /* 0 means do resume */
+ fb_set_suspend(info, 0);
+
+out:
+ console_unlock();
+
+ return ret;
+}
+
static const struct pci_device_id pci_stub_id_table[] = {
{
@@ -901,6 +1261,8 @@ static struct hv_driver hvfb_drv = {
.id_table = id_table,
.probe = hvfb_probe,
.remove = hvfb_remove,
+ .suspend = hvfb_suspend,
+ .resume = hvfb_resume,
.driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
diff --git a/drivers/video/fbdev/sa1100fb.c b/drivers/video/fbdev/sa1100fb.c
index ae2bcfee338a..81ad3aa1ca06 100644
--- a/drivers/video/fbdev/sa1100fb.c
+++ b/drivers/video/fbdev/sa1100fb.c
@@ -968,19 +968,6 @@ static void sa1100fb_task(struct work_struct *w)
#ifdef CONFIG_CPU_FREQ
/*
- * Calculate the minimum DMA period over all displays that we own.
- * This, together with the SDRAM bandwidth defines the slowest CPU
- * frequency that can be selected.
- */
-static unsigned int sa1100fb_min_dma_period(struct sa1100fb_info *fbi)
-{
- /*
- * FIXME: we need to verify _all_ consoles.
- */
- return sa1100fb_display_dma_period(&fbi->fb.var);
-}
-
-/*
* CPU clock speed change handler. We need to adjust the LCD timing
* parameters when the CPU clock is adjusted by the power management
* subsystem.
diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
index b939bc28d886..9c82e2a0a411 100644
--- a/drivers/video/hdmi.c
+++ b/drivers/video/hdmi.c
@@ -1576,12 +1576,12 @@ static int hdmi_avi_infoframe_unpack(struct hdmi_avi_infoframe *frame,
if (ptr[0] & 0x10)
frame->active_aspect = ptr[1] & 0xf;
if (ptr[0] & 0x8) {
- frame->top_bar = (ptr[5] << 8) + ptr[6];
- frame->bottom_bar = (ptr[7] << 8) + ptr[8];
+ frame->top_bar = (ptr[6] << 8) | ptr[5];
+ frame->bottom_bar = (ptr[8] << 8) | ptr[7];
}
if (ptr[0] & 0x4) {
- frame->left_bar = (ptr[9] << 8) + ptr[10];
- frame->right_bar = (ptr[11] << 8) + ptr[12];
+ frame->left_bar = (ptr[10] << 8) | ptr[9];
+ frame->right_bar = (ptr[12] << 8) | ptr[11];
}
frame->scan_mode = ptr[0] & 0x3;