diff options
author | Lopy Cheng <lopycheng@google.com> | 2020-07-30 10:51:39 +0800 |
---|---|---|
committer | Lopy Cheng <lopycheng@google.com> | 2020-11-30 10:06:54 +0800 |
commit | 30b7b6c17ad5d82ed99e8fb6bac23b781bca4fde (patch) | |
tree | 008f2204a9576d5c5bd573fba2ebf8b9707c222d | |
parent | 61c660ffc112621cfe9ce2f861e456661c128889 (diff) | |
download | display-drivers-30b7b6c17ad5d82ed99e8fb6bac23b781bca4fde.tar.gz |
dsi: adjust ELVSS dynamically for power saving
HBM mode : Disable dynamic ELVSS.
LP mode : Disable dynamic ELVSS.
Normal mode : Enable dynamic ELVSS. Change ELVSS
power according to Backlight status.
Bug: 160283004
Test:
1. Panel on <-> Panel off test.
2. Normal <-> HBM mode test.
3. Normal <-> AOD mode test.
4. AOD mode is covered by p-sensor.
5. Quick mode switch test between Normal/LP/HBM/Off
modes.
Change-Id: I0d7f5cb0d58260c223129222c2f40ebf55598bce
Signed-off-by: Lopy Cheng <lopycheng@google.com>
-rw-r--r-- | msm/dsi/dsi_backlight.c | 231 | ||||
-rw-r--r-- | msm/dsi/dsi_panel.h | 36 |
2 files changed, 267 insertions, 0 deletions
diff --git a/msm/dsi/dsi_backlight.c b/msm/dsi/dsi_backlight.c index a43212ab..e20aafae 100644 --- a/msm/dsi/dsi_backlight.c +++ b/msm/dsi/dsi_backlight.c @@ -436,6 +436,216 @@ static u32 dsi_backlight_calculate(struct dsi_backlight_config *bl, return bl_lvl; } +static void dsi_panel_bl_elvss_clean_flag(struct dsi_backlight_config *bl) +{ + struct dynamic_elvss_data *elvss = bl->elvss; + + if (!bl || !elvss) + return; + + elvss->cur_elvss_range = DYNAMIC_ELVSS_RANGE_MAX; + elvss->cur_mode = ELVSS_MODE_INIT; + pr_debug("ELVSS: Set variables to default value\n"); +} + +static enum elvss_mode dsi_panel_get_elvss_mode(struct backlight_device *bd) +{ + struct dsi_backlight_config *bl = bl_get_data(bd); + struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); + + if ((is_on_mode(bd->props.state) && dsi_panel_get_hbm(panel)) || + (is_lp_mode(bd->props.state))) + return ELVSS_MODE_DISABLE; + else + return ELVSS_MODE_ENABLE; +} + +static void dsi_panel_bl_elvss_update(struct backlight_device *bd, + enum ctrl_elvss elvss_update) +{ + struct dsi_backlight_config *bl = bl_get_data(bd); + struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); + struct dynamic_elvss_data *elvss = bl->elvss; + int rc, i; + enum elvss_mode mode; + + if (!elvss) + return; + + mode = dsi_panel_get_elvss_mode(bd); + + if ((mode == elvss->cur_mode) && (mode != ELVSS_MODE_ENABLE)) + return; + + switch (mode) { + case ELVSS_MODE_DISABLE: + pr_debug("ELVSS: Disable Dynamic ELVSS\n"); + + rc = dsi_panel_cmd_set_transfer(panel, + &elvss->disable_dynamic_elvss_cmd); + if (rc) + pr_err("ELVSS: [%s] failed to send disable dynamic ELVSS cmd, rc=%d\n", + panel->name, rc); + + elvss->cur_elvss_range = DYNAMIC_ELVSS_RANGE_MAX; + elvss->cur_mode = ELVSS_MODE_DISABLE; + + break; + case ELVSS_MODE_ENABLE: + for (i = 0; i < elvss->num_ranges; i++) { + if (bd->props.brightness <= + elvss->nodes[i].brightness_threshold) + break; + } + + if (i == elvss->num_ranges) { + pr_warn("ELVSS: brightness is larger than brightness_threshold\n"); + return; + } + + if ((elvss_update == ELVSS_PRE_UPDATE && + elvss->cur_elvss_range < i) || + (elvss_update == ELVSS_POST_UPDATE && + elvss->cur_elvss_range > i)) { + pr_debug("ELVSS: Update: ori_range=%d, new_range=%d, ctrl_elvss_update:%d\n", + elvss->cur_elvss_range, i, elvss_update); + + rc = dsi_panel_cmd_set_transfer(panel, + &elvss->nodes[i].elvss_cmd); + if (rc) + pr_err("ELVSS: [%s] failed to send elvss_cmd, rc=%d\n", + panel->name, rc); + + elvss->cur_elvss_range = i; + elvss->cur_mode = ELVSS_MODE_ENABLE; + } + break; + default: + pr_warn("ELVSS: Invalid Mode\n"); + break; + } +} + +static int dsi_panel_bl_parse_elvss_node(struct device *parent, + struct dsi_backlight_config *bl, struct device_node *np, + struct elvss_range *node) +{ + int rc; + u32 val; + + rc = of_property_read_u32(np, + "google,dsi-elvss-range-brightness-threshold", &val); + if (rc) { + pr_err("Unable to parse dsi-elvss-range-brightness-threshold\n"); + return rc; + } + if (val > bl->brightness_max_level) { + pr_err("elvss-range-brightness-threshold exceeds max userspace brightness\n"); + return -EINVAL; + } + node->brightness_threshold = val; + + rc = dsi_panel_parse_dt_cmd_set(np, + "google,dsi-elvss-range-update-command", + "google,dsi-elvss-range-commands-state", &node->elvss_cmd); + if (rc) { + pr_warn("Unable to parse google,dsi-elvss-range-update-command\n"); + return -EINVAL; + } + + return 0; +} + +static void dsi_panel_bl_elvss_free(struct device *dev, + struct dsi_backlight_config *bl) +{ + u32 i; + struct dynamic_elvss_data *elvss = bl->elvss; + + if (!elvss) + return; + + dsi_panel_destroy_cmd_packets(&elvss->disable_dynamic_elvss_cmd); + dsi_panel_dealloc_cmd_packets(&elvss->disable_dynamic_elvss_cmd); + + for (i = 0; i < elvss->num_ranges; i++) { + dsi_panel_destroy_cmd_packets(&elvss->nodes[i].elvss_cmd); + dsi_panel_dealloc_cmd_packets(&elvss->nodes[i].elvss_cmd); + } + + devm_kfree(dev, elvss); + bl->elvss = NULL; +} + +static int dsi_panel_bl_parse_dynamic_elvss(struct device *parent, + struct dsi_backlight_config *bl, struct device_node *of_node) +{ + struct device_node *elvss_ranges_np; + struct device_node *child_np; + u32 rc, i = 0, num_ranges; + + elvss_ranges_np = of_get_child_by_name(of_node, "google,elvss-ranges"); + if (!elvss_ranges_np) { + pr_info("ELVSS modes list not found\n"); + return 0; + } + + num_ranges = of_get_child_count(elvss_ranges_np); + if (!num_ranges || (num_ranges > DYNAMIC_ELVSS_RANGE_MAX)) { + pr_err("Invalid number of ELVSS modes: %d\n", num_ranges); + return -EINVAL; + } + + bl->elvss = devm_kzalloc(parent, sizeof(struct dynamic_elvss_data), + GFP_KERNEL); + if (bl->elvss == NULL) { + pr_err("Failed to allocate memory for dynamic elvss data\n"); + return -ENOMEM; + } + + rc = dsi_panel_parse_dt_cmd_set(elvss_ranges_np, + "google,dsi-elvss-range-off-command", + "google,dsi-elvss-range-commands-state", + &bl->elvss->disable_dynamic_elvss_cmd); + if (rc) { + devm_kfree(parent, bl->elvss); + bl->elvss = NULL; + pr_err("Unable to parse dsi-elvss-range-off-command\n"); + return -EINVAL; + } + + bl->elvss->num_ranges = num_ranges; + + for_each_child_of_node(elvss_ranges_np, child_np) { + rc = dsi_panel_bl_parse_elvss_node(parent, bl, + child_np, bl->elvss->nodes + i); + if (rc) { + bl->elvss->num_ranges = i; + pr_err("Failed to parse ELVSS range %d\n", i); + goto exit_free; + } + i++; + } + + for (i = 0; i < num_ranges - 1; i++) { + /* Make sure ranges are sorted and not overlapping */ + if (bl->elvss->nodes[i].brightness_threshold >= + bl->elvss->nodes[i + 1].brightness_threshold) { + pr_err("ELVSS nodes must be sorted by elvss-brightness-threshold\n"); + rc = -EINVAL; + goto exit_free; + } + } + + dsi_panel_bl_elvss_clean_flag(bl); + + return 0; + +exit_free: + dsi_panel_bl_elvss_free(parent, bl); + return rc; +} + static int dsi_backlight_update_status(struct backlight_device *bd) { struct dsi_backlight_config *bl = bl_get_data(bd); @@ -467,11 +677,16 @@ static int dsi_backlight_update_status(struct backlight_device *bd) pr_info("req:%d bl:%d state:0x%x\n", bd->props.brightness, bl_lvl, bd->props.state); + dsi_panel_bl_elvss_update(bd, ELVSS_PRE_UPDATE); + rc = bl->update_bl(bl, bl_lvl); if (rc) { pr_err("unable to set backlight (%d)\n", rc); goto done; } + + dsi_panel_bl_elvss_update(bd, ELVSS_POST_UPDATE); + bl->bl_update_pending = false; need_notify = true; if (bl->bl_notifier && is_on_mode(bd->props.state) @@ -963,6 +1178,16 @@ int dsi_backlight_late_dpms(struct dsi_backlight_config *bl, int power_mode) FB_BLANK_UNBLANK; bd->props.state = state; + /* The dynamic elvss register will be restored to + * the default OTP's value automatically when the + * panel is power off(HW behavior). We need to set + * the variables to default value for this kind of + * case. When the device comes back from panel off + * to other modes, the dynamic elvss will be updated. + */ + if (bd->props.power == FB_BLANK_POWERDOWN) + dsi_panel_bl_elvss_clean_flag(bl); + mutex_unlock(&bl->state_lock); backlight_update_status(bd); sysfs_notify(&bd->dev.kobj, NULL, "state"); @@ -1350,6 +1575,7 @@ int dsi_panel_bl_unregister(struct dsi_panel *panel) sysfs_remove_groups(&bl->bl_device->dev.kobj, bl_device_groups); dsi_panel_bl_hbm_free(panel->parent, bl); + dsi_panel_bl_elvss_free(panel->parent, bl); dsi_panel_bl_notifier_free(panel->parent, bl); return 0; @@ -1812,6 +2038,11 @@ int dsi_panel_bl_parse_config(struct device *parent, struct dsi_backlight_config pr_err("[%s] error while parsing high brightness mode (hbm) details, rc=%d\n", panel->name, rc); + rc = dsi_panel_bl_parse_dynamic_elvss(parent, bl, utils->data); + if (rc) + pr_err("[%s] error while parsing dynamic elvss details, rc=%d\n", + panel->name, rc); + rc = dsi_panel_bl_parse_ranges(parent, bl, utils->data); if (rc) pr_debug("[%s] error while parsing backlight ranges, rc=%d\n", diff --git a/msm/dsi/dsi_panel.h b/msm/dsi/dsi_panel.h index 01be1ea5..459cdc26 100644 --- a/msm/dsi/dsi_panel.h +++ b/msm/dsi/dsi_panel.h @@ -29,6 +29,7 @@ #define DSI_MODE_MAX 32 #define HBM_RANGE_MAX 4 +#define DYNAMIC_ELVSS_RANGE_MAX 10 #define BL_STATE_STANDBY BL_CORE_FBBLANK #define BL_STATE_LP BL_CORE_LP1 @@ -136,6 +137,40 @@ struct hbm_range { u32 num_dimming_frames; }; +enum elvss_mode { + ELVSS_MODE_INIT = 0, + ELVSS_MODE_ENABLE, + ELVSS_MODE_DISABLE +}; + +enum ctrl_elvss { + ELVSS_PRE_UPDATE = 0, + ELVSS_POST_UPDATE +}; + +struct elvss_range { + /* Use brightness threshold to update the command */ + u32 brightness_threshold; + + /* Command to be sent to the panel to adjust the ELVSS power */ + struct dsi_panel_cmd_set elvss_cmd; +}; + +struct dynamic_elvss_data { + /* Record the current elvss range */ + u32 cur_elvss_range; + /* Number of elvss ranges */ + u32 num_ranges; + /* Different status*/ + enum elvss_mode cur_mode; + + /* Command to disable dynamic elvss */ + struct dsi_panel_cmd_set disable_dynamic_elvss_cmd; + + /* Store the elvss data */ + struct elvss_range nodes[DYNAMIC_ELVSS_RANGE_MAX]; +}; + struct hbm_data { /* IRC register address */ u8 irc_addr; @@ -198,6 +233,7 @@ struct dsi_backlight_config { struct mutex state_lock; struct bl_notifier_data *bl_notifier; + struct dynamic_elvss_data *elvss; struct hbm_data *hbm; int en_gpio; |