| /* |
| * Copyright (c) 2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #define pr_fmt(fmt) "%s:%d: " fmt, __func__, __LINE__ |
| #include <linux/backlight.h> |
| #include <linux/debugfs.h> |
| #include <linux/of_gpio.h> |
| #include <linux/pwm.h> |
| #include <linux/sysfs.h> |
| #include <linux/list.h> |
| #include <linux/list_sort.h> |
| #include <video/mipi_display.h> |
| |
| #include <dsi_drm.h> |
| #include <sde_crtc.h> |
| #include <sde_encoder.h> |
| |
| #include "dsi_display.h" |
| #include "dsi_panel.h" |
| #include "sde_connector.h" |
| |
| #define BL_NODE_NAME_SIZE 32 |
| #define BL_BRIGHTNESS_BUF_SIZE 2 |
| |
| struct dsi_backlight_pwm_config { |
| struct pwm_device *pwm_bl; |
| bool pwm_enabled; |
| u32 pwm_period_usecs; |
| }; |
| |
| static void dsi_panel_bl_hbm_free(struct device *dev, |
| struct dsi_backlight_config *bl); |
| |
| static void dsi_panel_bl_notifier_free(struct device *dev, |
| struct dsi_backlight_config *bl); |
| |
| static int dsi_panel_bl_find_range(struct dsi_backlight_config *bl, |
| int brightness, u32 *range); |
| |
| static inline bool is_lp_mode(unsigned long state) |
| { |
| return (state & (BL_STATE_LP | BL_STATE_LP2)) != 0; |
| } |
| |
| static inline bool is_on_mode(unsigned long state) |
| { |
| return (!is_lp_mode(state) && !is_standby_mode(state)); |
| } |
| |
| static inline unsigned int regulator_mode_from_state(unsigned long state) |
| { |
| if (is_standby_mode(state)) |
| return REGULATOR_MODE_STANDBY; |
| else if (is_lp_mode(state)) |
| return REGULATOR_MODE_IDLE; |
| else |
| return REGULATOR_MODE_NORMAL; |
| } |
| |
| static int dsi_panel_pwm_bl_register(struct dsi_backlight_config *bl); |
| |
| static void dsi_panel_bl_free_unregister(struct dsi_backlight_config *bl) |
| { |
| kfree(bl->priv); |
| } |
| |
| static int dsi_backlight_update_dcs(struct dsi_backlight_config *bl, u32 bl_lvl) |
| { |
| int rc = 0; |
| struct dsi_panel *panel; |
| struct mipi_dsi_device *dsi; |
| size_t num_params; |
| const u32 hbyte = bl->high_byte_offset; |
| |
| if (!bl || (bl_lvl > 0xffff)) { |
| pr_err("invalid params\n"); |
| return -EINVAL; |
| } |
| |
| panel = container_of(bl, struct dsi_panel, bl_config); |
| /* if no change in backlight, abort */ |
| if (bl_lvl == bl->bl_actual) |
| return 0; |
| |
| dsi = &panel->mipi_device; |
| |
| num_params = bl->bl_max_level >= BIT(hbyte) ? 2 : 1; |
| if (num_params == 2) { |
| u8 payload[2] = { bl_lvl >> hbyte, (BIT(hbyte) - 1) & bl_lvl }; |
| |
| rc = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, |
| &payload, sizeof(payload)); |
| } else { |
| u8 payload = bl_lvl; |
| |
| rc = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, |
| &payload, sizeof(payload)); |
| } |
| |
| if (rc < 0) |
| pr_err("failed to update dcs backlight:%d\n", bl_lvl); |
| |
| return rc; |
| } |
| |
| /* Linearly interpolate value x from range [x1, x2] to determine the |
| * corresponding value in range [y1, y2]. |
| */ |
| static int dsi_backlight_lerp(u16 x1, u16 x2, u16 y1, u16 y2, u16 x, u32 *y) |
| { |
| if ((x2 < x1) || (y2 < y1)) |
| return -EINVAL; |
| |
| if (((x2 - x1) == 0) || (x <= x1)) |
| *y = y1; |
| else if (x >= x2) |
| *y = y2; |
| else |
| *y = DIV_ROUND_CLOSEST((x - x1) * (y2 - y1), x2 - x1) + y1; |
| |
| return 0; |
| } |
| |
| static u32 dsi_backlight_calculate_normal(struct dsi_backlight_config *bl, |
| int brightness) |
| { |
| u32 bl_lvl = 0; |
| int rc = 0; |
| |
| if (bl->lut) { |
| /* |
| * look up panel brightness; the first entry in the LUT |
| corresponds to userspace brightness level 1 |
| */ |
| if (WARN_ON(brightness > bl->brightness_max_level)) |
| bl_lvl = bl->lut[bl->brightness_max_level]; |
| else |
| bl_lvl = bl->lut[brightness]; |
| } else { |
| /* map UI brightness into driver backlight level rounding it */ |
| rc = dsi_backlight_lerp( |
| 1, bl->brightness_max_level, |
| bl->bl_min_level ? : 1, bl->bl_max_level, |
| brightness, &bl_lvl); |
| if (unlikely(rc)) |
| pr_err("failed to linearly interpolate, brightness unmodified\n"); |
| } |
| |
| pr_debug("normal bl: bl_lut %sused\n", bl->lut ? "" : "un"); |
| |
| return bl_lvl; |
| } |
| int dsi_panel_switch_update_hbm(struct dsi_panel *panel) |
| { |
| if (!panel || !panel->funcs || !panel->funcs->update_hbm) |
| return -EOPNOTSUPP; |
| |
| return panel->funcs->update_hbm(panel); |
| } |
| |
| int dsi_backlight_hbm_dimming_start(struct dsi_backlight_config *bl, |
| u32 num_frames, struct dsi_panel_cmd_set *stop_cmd) |
| { |
| struct hbm_data *hbm = bl->hbm; |
| |
| if (!hbm || !num_frames) |
| return 0; |
| |
| if (unlikely(!hbm->dimming_workq)) { |
| pr_err("hbm: tried to start dimming, but missing worker thread\n"); |
| return -EINVAL; |
| } |
| |
| if (!hbm->dimming_active) { |
| struct dsi_display *display = |
| dev_get_drvdata(hbm->panel->parent); |
| int rc; |
| |
| if (likely(display->bridge && |
| display->bridge->base.encoder && |
| display->bridge->base.encoder->crtc)) { |
| rc = drm_crtc_vblank_get( |
| display->bridge->base.encoder->crtc); |
| } else { |
| pr_err("hbm: missing crtc during dimming start.\n"); |
| return -EINVAL; |
| } |
| |
| if (rc) { |
| pr_err("hbm: failed drm request to get vblank\n: %d", |
| rc); |
| return rc; |
| } |
| } |
| |
| hbm->dimming_frames_total = num_frames; |
| hbm->dimming_frames_left = num_frames; |
| hbm->dimming_stop_cmd = stop_cmd; |
| hbm->dimming_active = true; |
| |
| pr_debug("hbm dimming starting\n"); |
| queue_work(hbm->dimming_workq, &hbm->dimming_work); |
| |
| return 0; |
| } |
| |
| void dsi_backlight_hbm_dimming_stop(struct dsi_backlight_config *bl) |
| { |
| struct dsi_display *display; |
| struct hbm_data *hbm = bl->hbm; |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| int rc = 0; |
| |
| if (!hbm || !hbm->dimming_active) |
| return; |
| |
| display = dev_get_drvdata(hbm->panel->parent); |
| if (likely(display->bridge && |
| display->bridge->base.encoder && |
| display->bridge->base.encoder->crtc)) { |
| drm_crtc_vblank_put(display->bridge->base.encoder->crtc); |
| } else { |
| pr_err("hbm: missing crtc during dimming end.\n"); |
| } |
| |
| hbm->dimming_frames_total = 0; |
| hbm->dimming_frames_left = 0; |
| hbm->dimming_active = false; |
| |
| if (hbm->dimming_stop_cmd) { |
| rc = dsi_panel_switch_update_hbm(panel); |
| if (rc == -EOPNOTSUPP) |
| rc = dsi_panel_cmd_set_transfer(hbm->panel, |
| hbm->dimming_stop_cmd); |
| if (rc) |
| pr_err("hbm: failed to disable brightness dimming.\n"); |
| } |
| |
| hbm->dimming_stop_cmd = NULL; |
| |
| if (panel->hbm_pending_irc_on) { |
| rc = dsi_panel_bl_update_irc(bl, true); |
| |
| if (rc) |
| pr_err("hmb sv: failed to enable IRC.\n"); |
| panel->hbm_pending_irc_on = false; |
| } |
| |
| pr_debug("hbm dimming stopped\n"); |
| } |
| |
| static void dsi_backlight_hbm_dimming_restart(struct dsi_backlight_config *bl) |
| { |
| struct hbm_data *hbm = bl->hbm; |
| |
| if (!hbm || !hbm->dimming_active) |
| return; |
| |
| hbm->dimming_frames_left = hbm->dimming_frames_total; |
| pr_debug("hbm: dimming restarted\n"); |
| } |
| |
| static int dsi_backlight_hbm_wait_frame(struct hbm_data *hbm) |
| { |
| struct dsi_display *display = dev_get_drvdata(hbm->panel->parent); |
| |
| if (likely(display->bridge && display->bridge->base.encoder)) { |
| int rc = sde_encoder_wait_for_event( |
| display->bridge->base.encoder, MSM_ENC_VBLANK); |
| if (rc) |
| return rc; |
| } else { |
| pr_err("hbm: missing sde encoder, can't wait for vblank\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void dsi_backlight_hbm_dimming_work(struct work_struct *work) |
| { |
| struct dsi_panel *panel; |
| struct hbm_data *hbm = |
| container_of(work, struct hbm_data, dimming_work); |
| |
| if (!hbm) |
| return; |
| |
| panel = hbm->panel; |
| while (hbm->dimming_active) { |
| int rc = dsi_backlight_hbm_wait_frame(hbm); |
| |
| /* |
| * it's possible that this thread is running while the driver is |
| * attempting to shut down. if this is the case, the driver |
| * will signal for dimming to stop while holding panel_lock. |
| * so if we fail to acquire the lock, wait a bit, then check the |
| * state of dimming_active again. |
| */ |
| if (!mutex_trylock(&panel->panel_lock)) { |
| usleep_range(1000, 2000); |
| continue; |
| } |
| |
| pr_debug("hbm: dimming waited on frame %d of %d\n", |
| hbm->dimming_frames_left, hbm->dimming_frames_total); |
| if (!hbm->dimming_active) { |
| mutex_unlock(&panel->panel_lock); |
| break; |
| } |
| |
| if (rc) { |
| pr_err("hbm: failed to wait for vblank, disabling dimming now\n"); |
| hbm->dimming_frames_left = 0; |
| } else if (hbm->dimming_frames_left > 0) { |
| hbm->dimming_frames_left--; |
| } |
| |
| if (!hbm->dimming_frames_left) |
| dsi_backlight_hbm_dimming_stop(&panel->bl_config); |
| |
| mutex_unlock(&panel->panel_lock); |
| } |
| } |
| |
| int dsi_backlight_hbm_find_range(struct dsi_backlight_config *bl, |
| int brightness, u32 *range) |
| { |
| u32 i; |
| |
| if (!bl || !bl->hbm || !range) |
| return -EINVAL; |
| |
| for (i = 0; i < bl->hbm->num_ranges; i++) { |
| if (brightness <= bl->hbm->ranges[i].user_bri_end) { |
| *range = i; |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static u32 dsi_backlight_calculate_hbm(struct dsi_backlight_config *bl, |
| int brightness) |
| { |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| struct hbm_data *hbm = bl->hbm; |
| struct hbm_range *range = NULL; |
| u32 bl_lvl = 0; |
| int rc = 0; |
| /* It's unlikely that a brightness value of 0 will make it to this |
| * function, but if it does use the dimmest HBM range. |
| */ |
| u32 target_range = 0; |
| |
| if (likely(brightness)) { |
| rc = dsi_backlight_hbm_find_range(bl, brightness, |
| &target_range); |
| if (rc) { |
| pr_err("Did not find a matching HBM range for brightness %d\n", |
| brightness); |
| return bl->bl_actual; |
| } |
| } |
| |
| range = hbm->ranges + target_range; |
| if (hbm->cur_range != target_range) { |
| dsi_backlight_hbm_dimming_start(bl, range->num_dimming_frames, |
| &range->dimming_stop_cmd); |
| pr_info("hbm: range %d -> %d\n", hbm->cur_range, target_range); |
| hbm->cur_range = target_range; |
| |
| rc = dsi_panel_switch_update_hbm(panel); |
| if (rc == -EOPNOTSUPP) |
| rc = dsi_panel_cmd_set_transfer(panel, |
| &range->entry_cmd); |
| if (rc) { |
| pr_err("Failed to send command for range %d\n", |
| target_range); |
| return bl->bl_actual; |
| } |
| } |
| |
| rc = dsi_backlight_lerp( |
| range->user_bri_start, range->user_bri_end, |
| range->panel_bri_start, range->panel_bri_end, |
| brightness, &bl_lvl); |
| if (unlikely(rc)) |
| pr_err("hbm: failed to linearly interpolate, brightness unmodified\n"); |
| |
| pr_debug("hbm: user %d-%d, panel %d-%d\n", |
| range->user_bri_start, range->user_bri_end, |
| range->panel_bri_start, range->panel_bri_end); |
| |
| return bl_lvl; |
| } |
| |
| static u32 dsi_backlight_calculate(struct dsi_backlight_config *bl, |
| int brightness) |
| { |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| u32 bl_lvl = 0; |
| u32 bl_temp; |
| |
| if (brightness <= 0) |
| return 0; |
| |
| /* scale backlight */ |
| bl_temp = mult_frac(brightness, bl->bl_scale, |
| MAX_BL_SCALE_LEVEL); |
| |
| bl_temp = mult_frac(bl_temp, bl->bl_scale_sv, |
| MAX_SV_BL_SCALE_LEVEL); |
| |
| if (panel->hbm_mode != HBM_MODE_OFF) |
| bl_lvl = dsi_backlight_calculate_hbm(bl, bl_temp); |
| else |
| bl_lvl = dsi_backlight_calculate_normal(bl, bl_temp); |
| |
| pr_debug("brightness=%d, bl_scale=%d, sv=%d, bl_lvl=%d, hbm = %d\n", |
| brightness, bl->bl_scale, bl->bl_scale_sv, bl_lvl, |
| panel->hbm_mode); |
| |
| 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; |
| |
| if (elvss->enable_dynamic_elvss == false) |
| mode = ELVSS_MODE_DISABLE; |
| else |
| 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; |
| } |
| |
| void dsi_panel_debugfs_create_dynamic_elvss(struct dentry *parent, |
| struct dynamic_elvss_data *elvss) |
| { |
| if (!parent || !elvss) |
| return; |
| |
| debugfs_create_bool("enable_dynamic_elvss", 0600, |
| parent, &elvss->enable_dynamic_elvss); |
| |
| } |
| |
| 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); |
| |
| bl->elvss->enable_dynamic_elvss = true; |
| |
| 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); |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| struct dsi_display *display; |
| int brightness = bd->props.brightness; |
| int bl_lvl; |
| int rc = 0; |
| bool need_notify = false; |
| |
| mutex_lock(&panel->panel_lock); |
| mutex_lock(&bl->state_lock); |
| if ((bd->props.state & (BL_CORE_FBBLANK | BL_CORE_SUSPENDED)) || |
| (bd->props.power != FB_BLANK_UNBLANK)) |
| brightness = 0; |
| |
| bl_lvl = dsi_backlight_calculate(bl, brightness); |
| if (bl_lvl == bl->bl_actual && bl->last_state == bd->props.state) |
| goto done; |
| |
| if (!bl->allow_bl_update) { |
| bl->bl_update_pending = true; |
| goto done; |
| } |
| |
| dsi_backlight_hbm_dimming_restart(bl); |
| |
| if (dsi_panel_initialized(panel) && bl->update_bl) { |
| 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) |
| && !(dsi_panel_get_hbm(panel))) { |
| u32 target_range = 0; |
| |
| rc = dsi_panel_bl_find_range(bl, brightness, &target_range); |
| if (rc) { |
| pr_err("unable to find range from the backlight table (%d)\n", rc); |
| } else if (bl->bl_notifier->cur_range != target_range) { |
| bl->bl_notifier->cur_range = target_range; |
| sysfs_notify(&bd->dev.kobj, NULL, "brightness"); |
| pr_debug("cur_range = %d, brightness = %d\n", |
| bl->bl_notifier->cur_range, brightness); |
| } |
| } |
| } |
| bl->bl_actual = bl_lvl; |
| bl->last_state = bd->props.state; |
| |
| done: |
| mutex_unlock(&bl->state_lock); |
| mutex_unlock(&panel->panel_lock); |
| |
| /* skip notifying user space if bl is 0 */ |
| if (likely(need_notify && brightness)) { |
| display = dev_get_drvdata(panel->parent); |
| if (unlikely(!display)) |
| return rc; |
| |
| sde_connector_event_notify(display->drm_conn, |
| DRM_EVENT_SYS_BACKLIGHT, sizeof(u32), brightness); |
| } |
| |
| return rc; |
| } |
| |
| static int dsi_backlight_get_brightness(struct backlight_device *bd) |
| { |
| struct dsi_backlight_config *bl = bl_get_data(bd); |
| |
| return bl->bl_actual; |
| } |
| |
| static const struct backlight_ops dsi_backlight_ops = { |
| .update_status = dsi_backlight_update_status, |
| .get_brightness = dsi_backlight_get_brightness, |
| }; |
| |
| static ssize_t alpm_mode_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct backlight_device *bd = to_backlight_device(dev); |
| struct dsi_backlight_config *bl = bl_get_data(bd); |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| int rc, alpm_mode; |
| const unsigned int lp_state = bl->bl_device->props.state & |
| (BL_STATE_LP | BL_STATE_LP2); |
| |
| rc = kstrtoint(buf, 0, &alpm_mode); |
| if (rc) |
| return rc; |
| |
| if (bl->bl_device->props.state & BL_CORE_FBBLANK) { |
| return -EINVAL; |
| } else if ((alpm_mode == 1) && (lp_state != BL_STATE_LP)) { |
| pr_info("activating lp1 mode\n"); |
| dsi_panel_set_lp1(panel); |
| } else if ((alpm_mode > 1) && !(lp_state & BL_STATE_LP2)) { |
| pr_info("activating lp2 mode\n"); |
| dsi_panel_set_lp2(panel); |
| } else if (!alpm_mode && lp_state) { |
| pr_info("activating normal mode\n"); |
| dsi_panel_set_nolp(panel); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t alpm_mode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct backlight_device *bd = to_backlight_device(dev); |
| int alpm_mode; |
| |
| if (bd->props.state & BL_STATE_LP2) |
| alpm_mode = 2; |
| else |
| alpm_mode = (bd->props.state & BL_STATE_LP) != 0; |
| |
| return sprintf(buf, "%d\n", alpm_mode); |
| } |
| static DEVICE_ATTR_RW(alpm_mode); |
| |
| static ssize_t hbm_mode_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct backlight_device *bd = NULL; |
| struct dsi_backlight_config *bl = NULL; |
| struct dsi_panel *panel = NULL; |
| int rc = 0; |
| int hbm_mode = 0; |
| |
| /* dev is non-NULL, enforced by sysfs_create_file_ns */ |
| bd = to_backlight_device(dev); |
| bl = bl_get_data(bd); |
| |
| if (!bl->hbm) |
| return -ENOTSUPP; |
| |
| rc = kstrtoint(buf, 10, &hbm_mode); |
| if (rc) |
| return rc; |
| |
| panel = container_of(bl, struct dsi_panel, bl_config); |
| rc = dsi_panel_update_hbm(panel, hbm_mode); |
| if (rc) { |
| pr_err("hbm_mode store failed: %d\n", rc); |
| return rc; |
| } |
| pr_debug("hbm_mode set to %d\n", panel->hbm_mode); |
| |
| return count; |
| } |
| |
| static ssize_t hbm_mode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct backlight_device *bd = NULL; |
| struct dsi_backlight_config *bl = NULL; |
| struct dsi_panel *panel = NULL; |
| int hbm_mode = false; |
| |
| /* dev is non-NULL, enforced by sysfs_create_file_ns */ |
| bd = to_backlight_device(dev); |
| bl = bl_get_data(bd); |
| |
| if (!bl->hbm) |
| return snprintf(buf, PAGE_SIZE, "unsupported\n"); |
| |
| panel = container_of(bl, struct dsi_panel, bl_config); |
| hbm_mode = dsi_panel_get_hbm(panel); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", hbm_mode); |
| } |
| |
| static DEVICE_ATTR_RW(hbm_mode); |
| |
| static ssize_t hbm_sv_enabled_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct backlight_device *bd; |
| struct dsi_backlight_config *bl; |
| struct dsi_panel *panel; |
| int rc = 0; |
| bool hbm_sv_enabled = false; |
| |
| /* dev is non-NULL, enforced by sysfs_create_file_ns */ |
| bd = to_backlight_device(dev); |
| bl = bl_get_data(bd); |
| |
| if (!bl->hbm) |
| return -ENOTSUPP; |
| |
| rc = kstrtobool(buf, &hbm_sv_enabled); |
| if (rc) |
| return rc; |
| |
| panel = container_of(bl, struct dsi_panel, bl_config); |
| if (!hbm_sv_enabled && panel->hbm_mode == HBM_MODE_SV) |
| return -EBUSY; |
| |
| panel->hbm_sv_enabled = hbm_sv_enabled; |
| |
| return count; |
| } |
| |
| static ssize_t hbm_sv_enabled_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct backlight_device *bd; |
| struct dsi_backlight_config *bl; |
| struct dsi_panel *panel; |
| |
| /* dev is non-NULL, enforced by sysfs_create_file_ns */ |
| bd = to_backlight_device(dev); |
| bl = bl_get_data(bd); |
| |
| if (!bl->hbm) |
| return snprintf(buf, PAGE_SIZE, "unsupported\n"); |
| |
| panel = container_of(bl, struct dsi_panel, bl_config); |
| return snprintf(buf, PAGE_SIZE, "%s\n", |
| panel->hbm_sv_enabled ? "true" : "false"); |
| } |
| |
| static DEVICE_ATTR_RW(hbm_sv_enabled); |
| |
| static ssize_t state_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct backlight_device *bd = to_backlight_device(dev); |
| struct dsi_backlight_config *bl = bl_get_data(bd); |
| struct dsi_panel *panel = container_of(bl, |
| struct dsi_panel, bl_config); |
| bool show_mode = false; |
| char *statestr; |
| int rc; |
| |
| mutex_lock(&bl->state_lock); |
| if (is_standby_mode(bd->props.state)) { |
| statestr = "Off"; |
| } else if (is_lp_mode(bd->props.state)) { |
| statestr = "LP"; |
| } else { |
| show_mode = true; |
| if (dsi_panel_get_hbm(panel)) |
| statestr = "HBM"; |
| else |
| statestr = "On"; |
| } |
| mutex_unlock(&bl->state_lock); |
| |
| if (show_mode) { |
| const struct dsi_display_mode *mode = |
| get_panel_display_mode(panel); |
| |
| if (unlikely(!mode)) |
| return -ENODEV; |
| |
| rc = snprintf(buf, PAGE_SIZE, "%s: %dx%d@%d\n", statestr, |
| mode->timing.h_active, mode->timing.v_active, |
| mode->timing.refresh_rate); |
| } else { |
| rc = snprintf(buf, PAGE_SIZE, "%s\n", statestr); |
| } |
| |
| return rc; |
| } |
| |
| static DEVICE_ATTR_RO(state); |
| |
| int parse_u32_buf(char *src, size_t src_len, u32 *out, size_t out_len) |
| { |
| int rc = 0, cnt = 0; |
| char *str; |
| const char *delim = " "; |
| |
| if (unlikely(!src || !src_len || !out || !out_len)) |
| return -EINVAL; |
| |
| /* src_len is the length of src including null character '\0' */ |
| if (strnlen(src, src_len) == src_len) |
| return -EINVAL; |
| |
| for (str = strsep(&src, delim); str != NULL; str = strsep(&src, delim)) { |
| rc = kstrtou32(str, 0, out + cnt); |
| if (rc) |
| return -EINVAL; |
| |
| cnt++; |
| |
| if (out_len == cnt) |
| break; |
| } |
| |
| return cnt; |
| } |
| |
| static ssize_t als_table_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| |
| struct backlight_device *bd = to_backlight_device(dev); |
| struct dsi_backlight_config *bl = bl_get_data(bd); |
| ssize_t als_count, buf_dup_len; |
| u8 i; |
| u32 ranges[BL_RANGE_MAX] = {0}; |
| char *buf_dup; |
| |
| if (unlikely(!bl || !bl->bl_notifier)) |
| return -EINVAL; |
| |
| if (count == 0) |
| return -EINVAL; |
| |
| buf_dup = kstrndup(buf, count, GFP_KERNEL); |
| if (!buf_dup) |
| return -ENOMEM; |
| |
| buf_dup_len = strlen(buf_dup) + 1; |
| |
| als_count = parse_u32_buf(buf_dup, buf_dup_len, ranges, BL_RANGE_MAX); |
| if ((als_count < 0) || (als_count > BL_RANGE_MAX)) { |
| kfree(buf_dup); |
| pr_warn("als: incorrect parameters from als table node\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&bl->state_lock); |
| |
| bl->bl_notifier->num_ranges = als_count; |
| for (i = 0; i < bl->bl_notifier->num_ranges; i++) |
| bl->bl_notifier->ranges[i] = ranges[i]; |
| |
| mutex_unlock(&bl->state_lock); |
| |
| kfree(buf_dup); |
| |
| return count; |
| } |
| |
| static ssize_t als_table_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct backlight_device *bd = to_backlight_device(dev); |
| struct dsi_backlight_config *bl = bl_get_data(bd); |
| ssize_t rc = 0; |
| size_t len = 0; |
| u32 i = 0; |
| |
| if (unlikely(!bl || !bl->bl_notifier)) |
| return -EINVAL; |
| |
| mutex_lock(&bl->state_lock); |
| |
| for (i = 0; i < bl->bl_notifier->num_ranges; i++) { |
| rc = scnprintf((buf + len), PAGE_SIZE - len, |
| "%u ", bl->bl_notifier->ranges[i]); |
| if (rc < 0) { |
| pr_warn("als: incorrect bl_notifier ranges\n"); |
| mutex_unlock(&bl->state_lock); |
| return -EINVAL; |
| } |
| len = len + rc; |
| } |
| |
| mutex_unlock(&bl->state_lock); |
| |
| len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); |
| |
| return len; |
| } |
| static DEVICE_ATTR_RW(als_table); |
| |
| static struct attribute *bl_device_attrs[] = { |
| &dev_attr_alpm_mode.attr, |
| &dev_attr_hbm_mode.attr, |
| &dev_attr_hbm_sv_enabled.attr, |
| &dev_attr_state.attr, |
| &dev_attr_als_table.attr, |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(bl_device); |
| |
| static int dsi_backlight_register(struct dsi_backlight_config *bl) |
| { |
| static int display_count; |
| char bl_node_name[BL_NODE_NAME_SIZE]; |
| struct backlight_properties props = { |
| .type = BACKLIGHT_RAW, |
| .power = FB_BLANK_UNBLANK, |
| }; |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| struct regulator *reg; |
| |
| props.max_brightness = bl->brightness_max_level; |
| props.brightness = bl->brightness_max_level / 2; |
| |
| snprintf(bl_node_name, BL_NODE_NAME_SIZE, "panel%u-backlight", |
| display_count); |
| bl->bl_device = devm_backlight_device_register(panel->parent, |
| bl_node_name, panel->parent, bl, |
| &dsi_backlight_ops, &props); |
| if (IS_ERR_OR_NULL(bl->bl_device)) { |
| bl->bl_device = NULL; |
| return -ENODEV; |
| } |
| |
| if (sysfs_create_groups(&bl->bl_device->dev.kobj, bl_device_groups)) |
| pr_warn("unable to create device groups\n"); |
| |
| reg = regulator_get_optional(panel->parent, "lab"); |
| if (!PTR_ERR_OR_ZERO(reg)) { |
| pr_info("LAB regulator found\n"); |
| panel->bl_config.lab_vreg = reg; |
| } |
| |
| display_count++; |
| return 0; |
| } |
| |
| static unsigned long get_state_after_dpms(struct dsi_backlight_config *bl, |
| int power_mode) |
| { |
| struct backlight_device *bd = bl->bl_device; |
| unsigned long state = bd->props.state; |
| |
| switch (power_mode) { |
| case SDE_MODE_DPMS_ON: |
| state &= ~(BL_CORE_FBBLANK | BL_STATE_LP | BL_STATE_LP2); |
| break; |
| case SDE_MODE_DPMS_OFF: |
| state &= ~(BL_STATE_LP | BL_STATE_LP2); |
| state |= BL_CORE_FBBLANK; |
| break; |
| case SDE_MODE_DPMS_LP1: |
| state |= BL_STATE_LP; |
| state &= ~BL_STATE_LP2; |
| break; |
| case SDE_MODE_DPMS_LP2: |
| state |= BL_STATE_LP | BL_STATE_LP2; |
| break; |
| } |
| |
| return state; |
| } |
| |
| static int dsi_backlight_update_regulator(struct dsi_backlight_config *bl, |
| unsigned int state) |
| { |
| int rc = 0; |
| |
| if (bl->lab_vreg) { |
| const unsigned int mode = regulator_mode_from_state(state); |
| const unsigned int last_mode = |
| regulator_mode_from_state(bl->last_state); |
| |
| if (last_mode != mode) { |
| pr_debug("set lab vreg mode: 0x%0x\n", mode); |
| rc = regulator_set_mode(bl->lab_vreg, mode); |
| } |
| } |
| |
| return rc; |
| } |
| |
| int dsi_backlight_early_dpms(struct dsi_backlight_config *bl, int power_mode) |
| { |
| struct backlight_device *bd = bl->bl_device; |
| unsigned long state; |
| int rc = 0; |
| |
| if (!bd) |
| return 0; |
| |
| pr_info("power_mode:%d state:0x%0x\n", power_mode, bd->props.state); |
| |
| mutex_lock(&bl->state_lock); |
| state = get_state_after_dpms(bl, power_mode); |
| |
| if (is_lp_mode(state)) { |
| rc = dsi_backlight_update_regulator(bl, state); |
| if (rc) |
| pr_warn("Error updating regulator state: 0x%x (%d)\n", |
| state, rc); |
| } |
| mutex_unlock(&bl->state_lock); |
| |
| return rc; |
| } |
| |
| int dsi_backlight_late_dpms(struct dsi_backlight_config *bl, int power_mode) |
| { |
| struct backlight_device *bd = bl->bl_device; |
| unsigned long state; |
| |
| if (!bd) |
| return 0; |
| |
| pr_debug("power_mode:%d state:0x%0x\n", power_mode, bd->props.state); |
| |
| mutex_lock(&bl->state_lock); |
| state = get_state_after_dpms(bl, power_mode); |
| |
| if (!is_lp_mode(state)) { |
| const int rc = dsi_backlight_update_regulator(bl, state); |
| |
| if (rc) |
| pr_warn("Error updating regulator state: 0x%x (%d)\n", |
| state, rc); |
| } |
| |
| bd->props.power = state & BL_CORE_FBBLANK ? FB_BLANK_POWERDOWN : |
| 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"); |
| |
| pr_info("sysfs_notify state:0x%0x\n", bd->props.state); |
| |
| return 0; |
| } |
| |
| #define MAX_BINNED_BL_MODES 10 |
| |
| struct binned_lp_node { |
| struct list_head head; |
| const char *name; |
| u32 bl_threshold; |
| struct dsi_panel_cmd_set dsi_cmd; |
| struct dentry *mode_dir; |
| }; |
| |
| struct binned_lp_data { |
| struct list_head mode_list; |
| struct binned_lp_node *last_lp_mode; |
| struct binned_lp_node priv_pool[MAX_BINNED_BL_MODES]; |
| }; |
| |
| static int dsi_panel_te2_lp_mode_update(struct dsi_panel *panel, |
| struct binned_lp_node *node) |
| { |
| int rc; |
| |
| if (unlikely(!panel->funcs || !panel->funcs->update_te2)) |
| return -EINVAL; |
| |
| if (unlikely(!panel->te2_config.te2_ready)) |
| return -EINVAL; |
| |
| if (node->bl_threshold > panel->te2_config.lp_threshold) |
| panel->te2_config.current_type = TE2_EDGE_LP_HIGH; |
| else |
| panel->te2_config.current_type = TE2_EDGE_LP_LOW; |
| |
| rc = panel->funcs->update_te2(panel); |
| if (rc < 0) |
| pr_warn("TE2: LP '%s' mode failed, rc=%d\n", node->name, rc); |
| |
| return rc; |
| } |
| |
| static int dsi_panel_binned_bl_update(struct dsi_backlight_config *bl, |
| u32 bl_lvl) |
| { |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| struct binned_lp_data *lp_data = bl->priv; |
| struct binned_lp_node *node = NULL; |
| struct backlight_properties *props = &bl->bl_device->props; |
| |
| if (is_lp_mode(props->state)) { |
| struct binned_lp_node *tmp; |
| |
| list_for_each_entry(tmp, &lp_data->mode_list, head) { |
| if (props->brightness <= tmp->bl_threshold) { |
| node = tmp; |
| break; |
| } |
| } |
| WARN(!node, "unable to find lp node for bl_lvl: %d\n", |
| props->brightness); |
| } |
| |
| if (node != lp_data->last_lp_mode) { |
| lp_data->last_lp_mode = node; |
| if (node) { |
| pr_debug("switching display lp mode: %s (%d)\n", |
| node->name, props->brightness); |
| dsi_panel_cmd_set_transfer(panel, &node->dsi_cmd); |
| |
| dsi_panel_te2_lp_mode_update(panel, node); |
| } else { |
| /* ensure update after lpm */ |
| bl->bl_actual = -1; |
| } |
| } |
| |
| /* no need to send backlight command if HLPM active */ |
| if (node) |
| return 0; |
| |
| return dsi_backlight_update_dcs(bl, bl_lvl); |
| } |
| |
| static int _dsi_panel_binned_lp_parse(struct device_node *np, |
| struct binned_lp_node *node) |
| { |
| int rc; |
| u32 val = 0; |
| |
| rc = of_property_read_u32(np, "google,dsi-lp-brightness-threshold", |
| &val); |
| /* treat lack of property as max threshold */ |
| node->bl_threshold = !rc ? val : UINT_MAX; |
| |
| rc = dsi_panel_parse_dt_cmd_set(np, "google,dsi-lp-command", |
| "google,dsi-lp-command-state", |
| &node->dsi_cmd); |
| if (rc) { |
| pr_err("Unable to parse dsi-lp-command\n"); |
| return rc; |
| } |
| |
| of_property_read_string(np, "label", &node->name); |
| |
| pr_debug("Successfully parsed lp mode: %s threshold: %d\n", |
| node->name, node->bl_threshold); |
| |
| return 0; |
| } |
| |
| static int _dsi_panel_binned_bl_cmp(void *priv, struct list_head *lha, |
| struct list_head *lhb) |
| { |
| struct binned_lp_node *a = list_entry(lha, struct binned_lp_node, head); |
| struct binned_lp_node *b = list_entry(lhb, struct binned_lp_node, head); |
| |
| return a->bl_threshold - b->bl_threshold; |
| } |
| |
| void dsi_panel_debugfs_create_binned_bl(struct dentry *parent, |
| struct dsi_backlight_config *bl) |
| { |
| struct dentry *r, *file; |
| struct binned_lp_data *lp_data; |
| struct binned_lp_node *tmp; |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| |
| r = debugfs_create_dir("lp_modes", parent); |
| if (IS_ERR(r)) { |
| pr_err("create debugfs lp_modes failed\n"); |
| return; |
| } |
| |
| lp_data = bl->priv; |
| |
| list_for_each_entry(tmp, &lp_data->mode_list, head) { |
| tmp->mode_dir = debugfs_create_dir(tmp->name, r); |
| if (IS_ERR(tmp->mode_dir)) { |
| pr_err("create debugfs binned_bl failed\n"); |
| goto error; |
| } |
| |
| file = debugfs_create_u32("threshold", 0600, |
| tmp->mode_dir, |
| &tmp->bl_threshold); |
| if (IS_ERR_OR_NULL(file)) { |
| pr_err("debugfs create threshold file failed\n"); |
| goto error; |
| } |
| |
| if (dsi_panel_debugfs_create_cmdset(tmp->mode_dir, "cmd", |
| panel, &tmp->dsi_cmd)) { |
| pr_err("debugfs create cmd file failed\n"); |
| goto error; |
| } |
| } |
| |
| return; |
| |
| error: |
| debugfs_remove_recursive(r); |
| } |
| |
| void dsi_panel_bl_elvss_debugfs_init(struct dentry *parent, |
| struct dsi_panel *panel) |
| { |
| struct dsi_backlight_config *bl = &panel->bl_config; |
| struct dynamic_elvss_data *elvss; |
| |
| if (!parent || !bl->elvss) |
| return; |
| |
| elvss = bl->elvss; |
| |
| dsi_panel_debugfs_create_dynamic_elvss(parent, elvss); |
| } |
| |
| static int dsi_panel_binned_lp_register(struct dsi_backlight_config *bl) |
| { |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| struct binned_lp_data *lp_data; |
| struct device_node *lp_modes_np, *child_np; |
| struct binned_lp_node *lp_node; |
| int num_modes; |
| int rc = -ENOTSUPP; |
| |
| lp_data = kzalloc(sizeof(*lp_data), GFP_KERNEL); |
| if (!lp_data) |
| return -ENOMEM; |
| |
| lp_modes_np = of_get_child_by_name(panel->panel_of_node, |
| "google,lp-modes"); |
| |
| if (!lp_modes_np) { |
| kfree(lp_data); |
| return rc; |
| } |
| |
| num_modes = of_get_child_count(lp_modes_np); |
| if (!num_modes || (num_modes > MAX_BINNED_BL_MODES)) { |
| pr_err("Invalid binned brightness modes: %d\n", num_modes); |
| goto exit; |
| } |
| |
| INIT_LIST_HEAD(&lp_data->mode_list); |
| lp_node = lp_data->priv_pool; |
| |
| for_each_child_of_node(lp_modes_np, child_np) { |
| rc = _dsi_panel_binned_lp_parse(child_np, lp_node); |
| if (rc) |
| goto exit; |
| |
| list_add_tail(&lp_node->head, &lp_data->mode_list); |
| lp_node++; |
| if (lp_node > &lp_data->priv_pool[MAX_BINNED_BL_MODES - 1]) { |
| pr_err("Too many LP modes\n"); |
| rc = -ENOTSUPP; |
| goto exit; |
| } |
| } |
| list_sort(NULL, &lp_data->mode_list, _dsi_panel_binned_bl_cmp); |
| |
| bl->update_bl = dsi_panel_binned_bl_update; |
| bl->unregister = dsi_panel_bl_free_unregister; |
| bl->debugfs_init = dsi_panel_debugfs_create_binned_bl; |
| bl->priv = lp_data; |
| |
| exit: |
| of_node_put(lp_modes_np); |
| if (rc) |
| kfree(lp_data); |
| |
| return rc; |
| } |
| |
| static const struct of_device_id dsi_backlight_dt_match[] = { |
| { |
| .compatible = "google,dsi_binned_lp", |
| .data = dsi_panel_binned_lp_register, |
| }, |
| {} |
| }; |
| |
| void dsi_panel_bl_debugfs_init(struct dentry *parent, struct dsi_panel *panel) |
| { |
| struct dsi_backlight_config *bl = &panel->bl_config; |
| |
| if (bl->debugfs_init) |
| bl->debugfs_init(parent, bl); |
| } |
| |
| int dsi_backlight_get_dpms(struct dsi_backlight_config *bl) |
| { |
| struct backlight_device *bd = bl->bl_device; |
| int power = 0; |
| int state = 0; |
| |
| mutex_lock(&bl->state_lock); |
| power = bd->props.power; |
| state = bd->props.state; |
| mutex_unlock(&bl->state_lock); |
| |
| if (power == FB_BLANK_POWERDOWN) |
| return SDE_MODE_DPMS_OFF; |
| else if (state & BL_STATE_LP2) |
| return SDE_MODE_DPMS_LP2; |
| else if (state & BL_STATE_LP) |
| return SDE_MODE_DPMS_LP1; |
| else |
| return SDE_MODE_DPMS_ON; |
| } |
| |
| static int dsi_panel_bl_parse_hbm_node(struct device *parent, |
| struct dsi_backlight_config *bl, struct device_node *np, |
| struct hbm_range *range) |
| { |
| int rc; |
| u32 val = 0; |
| |
| rc = of_property_read_u32(np, |
| "google,dsi-hbm-range-brightness-threshold", &val); |
| if (rc) { |
| pr_err("Unable to parse dsi-hbm-range-brightness-threshold"); |
| return rc; |
| } |
| if (val > bl->brightness_max_level) { |
| pr_err("hbm-brightness-threshold exceeds max userspace brightness\n"); |
| return rc; |
| } |
| range->user_bri_start = val; |
| |
| rc = of_property_read_u32(np, "google,dsi-hbm-range-bl-min-level", |
| &val); |
| if (rc) { |
| pr_err("dsi-hbm-range-bl-min-level unspecified\n"); |
| return rc; |
| } |
| range->panel_bri_start = val; |
| |
| rc = of_property_read_u32(np, "google,dsi-hbm-range-bl-max-level", |
| &val); |
| if (rc) { |
| pr_err("dsi-hbm-range-bl-max-level unspecified\n"); |
| return rc; |
| } |
| if (val < range->panel_bri_start) { |
| pr_err("Invalid HBM panel brightness range: bl-hbm-max-level < bl-hbm-min-level\n"); |
| return rc; |
| } |
| range->panel_bri_end = val; |
| |
| rc = dsi_panel_parse_dt_cmd_set(np, |
| "google,dsi-hbm-range-entry-command", |
| "google,dsi-hbm-range-commands-state", &range->entry_cmd); |
| if (rc) |
| pr_info("Unable to parse optional dsi-hbm-range-entry-command\n"); |
| |
| rc = of_property_read_u32(np, |
| "google,dsi-hbm-range-num-dimming-frames", &val); |
| if (rc) { |
| pr_debug("Unable to parse optional hbm-range-entry-num-dimming-frames\n"); |
| range->num_dimming_frames = 0; |
| } else { |
| range->num_dimming_frames = val; |
| } |
| |
| rc = dsi_panel_parse_dt_cmd_set(np, |
| "google,dsi-hbm-range-dimming-stop-command", |
| "google,dsi-hbm-range-commands-state", |
| &range->dimming_stop_cmd); |
| if (rc) |
| pr_debug("Unable to parse optional dsi-hbm-range-dimming-stop-command\n"); |
| |
| if ((range->dimming_stop_cmd.count && !range->num_dimming_frames) || |
| (!range->dimming_stop_cmd.count && range->num_dimming_frames)) { |
| pr_err("HBM dimming requires both stop command and number of frames.\n"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| int dsi_panel_bl_register(struct dsi_panel *panel) |
| { |
| int rc = 0; |
| struct dsi_backlight_config *bl = &panel->bl_config; |
| const struct of_device_id *match; |
| int (*register_func)(struct dsi_backlight_config *) = NULL; |
| |
| mutex_init(&bl->state_lock); |
| match = of_match_node(dsi_backlight_dt_match, panel->panel_of_node); |
| if (match && match->data) { |
| register_func = match->data; |
| rc = register_func(bl); |
| } |
| |
| if (!register_func || (rc == -ENOTSUPP)) { |
| switch (bl->type) { |
| case DSI_BACKLIGHT_WLED: |
| break; |
| case DSI_BACKLIGHT_DCS: |
| bl->update_bl = dsi_backlight_update_dcs; |
| break; |
| case DSI_BACKLIGHT_PWM: |
| register_func = dsi_panel_pwm_bl_register; |
| break; |
| default: |
| pr_err("Backlight type(%d) not supported\n", bl->type); |
| rc = -ENOTSUPP; |
| break; |
| } |
| |
| if (register_func) |
| rc = register_func(bl); |
| } |
| |
| if (!rc) |
| rc = dsi_backlight_register(bl); |
| |
| return rc; |
| } |
| |
| |
| int dsi_panel_bl_unregister(struct dsi_panel *panel) |
| { |
| struct dsi_backlight_config *bl = &panel->bl_config; |
| |
| mutex_destroy(&bl->state_lock); |
| if (bl->unregister) |
| bl->unregister(bl); |
| |
| if (bl->bl_device) |
| 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; |
| } |
| |
| static int dsi_panel_bl_parse_pwm_config(struct dsi_panel *panel, |
| struct dsi_backlight_pwm_config *config) |
| { |
| int rc = 0; |
| u32 val; |
| struct dsi_parser_utils *utils = &panel->utils; |
| |
| rc = utils->read_u32(utils->data, "qcom,bl-pmic-pwm-period-usecs", |
| &val); |
| if (rc) { |
| pr_err("bl-pmic-pwm-period-usecs is not defined, rc=%d\n", rc); |
| goto error; |
| } |
| config->pwm_period_usecs = val; |
| |
| error: |
| return rc; |
| } |
| |
| static void dsi_panel_pwm_bl_unregister(struct dsi_backlight_config *bl) |
| { |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| struct dsi_backlight_pwm_config *pwm_cfg = bl->priv; |
| |
| devm_pwm_put(panel->parent, pwm_cfg->pwm_bl); |
| kfree(pwm_cfg); |
| } |
| |
| static int dsi_panel_pwm_bl_register(struct dsi_backlight_config *bl) |
| { |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| struct dsi_backlight_pwm_config *pwm_cfg; |
| int rc = 0; |
| |
| pwm_cfg = kzalloc(sizeof(*pwm_cfg), GFP_KERNEL); |
| if (!pwm_cfg) |
| return -ENOMEM; |
| |
| pwm_cfg->pwm_bl = devm_of_pwm_get(panel->parent, panel->panel_of_node, NULL); |
| if (IS_ERR_OR_NULL(pwm_cfg->pwm_bl)) { |
| rc = PTR_ERR(pwm_cfg->pwm_bl); |
| pr_err("[%s] failed to request pwm, rc=%d\n", panel->name, |
| rc); |
| kfree(pwm_cfg); |
| return rc; |
| } |
| |
| rc = dsi_panel_bl_parse_pwm_config(panel, pwm_cfg); |
| if (rc) { |
| pr_err("[%s] failed to parse pwm config, rc=%d\n", |
| panel->name, rc); |
| dsi_panel_pwm_bl_unregister(bl); |
| return rc; |
| } |
| |
| bl->priv = pwm_cfg; |
| bl->unregister = dsi_panel_pwm_bl_unregister; |
| |
| return 0; |
| } |
| |
| static int dsi_panel_bl_parse_lut(struct device *parent, |
| struct device_node *of_node, const char *bl_lut_prop_name, |
| u32 brightness_max_level, u16 **lut) |
| { |
| u32 len = 0; |
| u32 i = 0; |
| u32 rc = 0; |
| const __be32 *val = 0; |
| struct property *prop = NULL; |
| u32 lut_length = brightness_max_level + 1; |
| u16 *bl_lut_tmp = NULL; |
| |
| if (!of_node || !bl_lut_prop_name || !lut) |
| return -EINVAL; |
| |
| if (*lut) { |
| pr_warn("LUT for %s already exists, freeing before reparsing\n", |
| bl_lut_prop_name); |
| devm_kfree(parent, *lut); |
| *lut = NULL; |
| } |
| |
| prop = of_find_property(of_node, bl_lut_prop_name, &len); |
| if (!prop) |
| goto done; /* LUT is unspecified */ |
| |
| len /= sizeof(u32); |
| if (len != lut_length) { |
| pr_warn("%s length %d doesn't match brightness_max_level + 1 %d\n", |
| bl_lut_prop_name, len, lut_length); |
| goto done; |
| } |
| |
| pr_debug("%s length %d\n", bl_lut_prop_name, lut_length); |
| bl_lut_tmp = devm_kmalloc(parent, sizeof(u16) * lut_length, GFP_KERNEL); |
| if (bl_lut_tmp == NULL) { |
| rc = -ENOMEM; |
| goto done; |
| } |
| |
| val = prop->value; |
| for (i = 0; i < len; i++) |
| bl_lut_tmp[i] = (u16)(be32_to_cpup(val++) & 0xffff); |
| |
| *lut = bl_lut_tmp; |
| |
| done: |
| return rc; |
| } |
| |
| static void dsi_panel_bl_hbm_free(struct device *dev, |
| struct dsi_backlight_config *bl) |
| { |
| u32 i = 0; |
| struct hbm_data *hbm = bl->hbm; |
| |
| if (!hbm) |
| return; |
| |
| if (hbm->dimming_workq) { |
| dsi_backlight_hbm_dimming_stop(bl); |
| flush_workqueue(hbm->dimming_workq); |
| destroy_workqueue(hbm->dimming_workq); |
| } |
| |
| dsi_panel_destroy_cmd_packets(&hbm->exit_cmd); |
| dsi_panel_dealloc_cmd_packets(&hbm->exit_cmd); |
| dsi_panel_destroy_cmd_packets(&hbm->exit_dimming_stop_cmd); |
| dsi_panel_dealloc_cmd_packets(&hbm->exit_dimming_stop_cmd); |
| |
| dsi_panel_destroy_cmd_packets(&hbm->irc_unlock_cmd); |
| dsi_panel_destroy_cmd_packets(&hbm->irc_lock_cmd); |
| kfree(hbm->irc_data); |
| |
| for (i = 0; i < hbm->num_ranges; i++) { |
| dsi_panel_destroy_cmd_packets(&hbm->ranges[i].entry_cmd); |
| dsi_panel_dealloc_cmd_packets(&hbm->ranges[i].entry_cmd); |
| |
| dsi_panel_destroy_cmd_packets(&hbm->ranges[i].dimming_stop_cmd); |
| dsi_panel_dealloc_cmd_packets(&hbm->ranges[i].dimming_stop_cmd); |
| } |
| |
| devm_kfree(dev, hbm); |
| bl->hbm = NULL; |
| } |
| |
| static int dsi_panel_bl_parse_hbm(struct device *parent, |
| struct dsi_backlight_config *bl, struct device_node *of_node) |
| { |
| struct device_node *hbm_ranges_np; |
| struct device_node *child_np; |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| u32 rc = 0; |
| u32 i = 0; |
| u32 num_ranges = 0; |
| u32 val = 0; |
| bool dimming_used = false; |
| |
| panel->hbm_mode = HBM_MODE_OFF; |
| |
| if (bl->hbm) { |
| pr_warn("HBM data already parsed, freeing before reparsing\n"); |
| dsi_panel_bl_hbm_free(parent, bl); |
| } |
| |
| hbm_ranges_np = of_get_child_by_name(of_node, "google,hbm-ranges"); |
| if (!hbm_ranges_np) { |
| pr_info("HBM modes list not found\n"); |
| return 0; |
| } |
| |
| num_ranges = of_get_child_count(hbm_ranges_np); |
| if (!num_ranges || (num_ranges > HBM_RANGE_MAX)) { |
| pr_err("Invalid number of HBM modes: %d\n", num_ranges); |
| return -EINVAL; |
| } |
| |
| bl->hbm = devm_kzalloc(parent, sizeof(struct hbm_data), GFP_KERNEL); |
| if (bl->hbm == NULL) { |
| pr_err("Failed to allocate memory for HBM data\n"); |
| return -ENOMEM; |
| } |
| |
| rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np, |
| "google,dsi-hbm-exit-command", |
| "google,dsi-hbm-commands-state", &bl->hbm->exit_cmd); |
| if (rc) |
| pr_info("Unable to parse optional dsi-hbm-exit-command\n"); |
| |
| bl->hbm->num_ranges = num_ranges; |
| |
| rc = of_property_read_u32(hbm_ranges_np, |
| "google,dsi-hbm-exit-num-dimming-frames", &val); |
| if (rc) { |
| pr_debug("Unable to parse optional num-dimming-frames\n"); |
| bl->hbm->exit_num_dimming_frames = 0; |
| } else { |
| bl->hbm->exit_num_dimming_frames = val; |
| } |
| |
| rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np, |
| "google,dsi-hbm-exit-dimming-stop-command", |
| "google,dsi-hbm-commands-state", |
| &bl->hbm->exit_dimming_stop_cmd); |
| if (rc) |
| pr_debug("Unable to parse optional dsi-hbm-exit-dimming-stop-command\n"); |
| |
| if ((bl->hbm->exit_dimming_stop_cmd.count && |
| !bl->hbm->exit_num_dimming_frames) || |
| (!bl->hbm->exit_dimming_stop_cmd.count && |
| bl->hbm->exit_num_dimming_frames)) { |
| pr_err("HBM dimming requires both stop command and number of frames.\n"); |
| goto exit_free; |
| } |
| |
| rc = of_property_read_u32(hbm_ranges_np, |
| "google,dsi-irc-addr", &val); |
| if (rc) { |
| pr_debug("Unable to parse dsi-irc-addr\n"); |
| bl->hbm->irc_addr = 0; |
| } else { |
| bl->hbm->irc_addr = val; |
| rc = of_property_read_u32(hbm_ranges_np, |
| "google,dsi-irc-bit-offset", &val); |
| if (rc) { |
| bl->hbm->irc_bit_offset = 0; |
| bl->hbm->irc_addr = 0; |
| pr_warn("Unable to parse dsi-irc-bit-offset\n"); |
| } else { |
| bl->hbm->irc_bit_offset = val; |
| } |
| |
| rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np, |
| "google,dsi-irc-unlock-command", |
| "google,dsi-irc-unlock-commands-state", |
| &bl->hbm->irc_unlock_cmd); |
| if (rc) |
| pr_debug("Unable to parse optional dsi-irc-unlock-command\n"); |
| |
| rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np, |
| "google,dsi-irc-lock-command", |
| "google,dsi-irc-lock-commands-state", |
| &bl->hbm->irc_lock_cmd); |
| if (rc) |
| pr_debug("Unable to parse optional dsi-irc-lock-command\n"); |
| |
| if (!bl->hbm->irc_unlock_cmd.count != !bl->hbm->irc_lock_cmd.count) { |
| dsi_panel_destroy_cmd_packets(&bl->hbm->irc_unlock_cmd); |
| dsi_panel_destroy_cmd_packets(&bl->hbm->irc_lock_cmd); |
| bl->hbm->irc_addr = 0; |
| pr_warn("Unable to get a pair of dsi-irc-unlock/lock command\n"); |
| } |
| } |
| |
| for_each_child_of_node(hbm_ranges_np, child_np) { |
| rc = dsi_panel_bl_parse_hbm_node(parent, bl, |
| child_np, bl->hbm->ranges + i); |
| if (rc) { |
| pr_err("Failed to parse HBM range %d of %d\n", |
| i + 1, num_ranges); |
| goto exit_free; |
| } |
| i++; |
| } |
| |
| for (i = 0; i < num_ranges - 1; i++) { |
| /* Make sure ranges are sorted and not overlapping */ |
| if (bl->hbm->ranges[i].user_bri_start >= |
| bl->hbm->ranges[i + 1].user_bri_start) { |
| pr_err("HBM ranges must be sorted by hbm-brightness-threshold\n"); |
| rc = -EINVAL; |
| goto exit_free; |
| } |
| |
| if (bl->hbm->ranges[i].num_dimming_frames) |
| dimming_used = true; |
| |
| /* Fill in user_bri_end for each range */ |
| bl->hbm->ranges[i].user_bri_end = |
| bl->hbm->ranges[i + 1].user_bri_start - 1; |
| } |
| |
| if (bl->hbm->ranges[num_ranges - 1].num_dimming_frames || |
| bl->hbm->exit_num_dimming_frames) |
| dimming_used = true; |
| |
| |
| if (dimming_used) { |
| bl->hbm->dimming_workq = |
| create_singlethread_workqueue("dsi_dimming_workq"); |
| if (!bl->hbm->dimming_workq) |
| pr_err("failed to create hbm dimming workq!\n"); |
| else |
| INIT_WORK(&bl->hbm->dimming_work, |
| dsi_backlight_hbm_dimming_work); |
| } |
| |
| bl->hbm->ranges[i].user_bri_end = bl->brightness_max_level; |
| bl->hbm->cur_range = HBM_RANGE_MAX; |
| bl->hbm->dimming_active = false; |
| bl->hbm->dimming_frames_total = 0; |
| bl->hbm->dimming_frames_left = 0; |
| bl->hbm->panel = panel; |
| |
| return 0; |
| |
| exit_free: |
| dsi_panel_bl_hbm_free(parent, bl); |
| return rc; |
| } |
| |
| static int dsi_panel_bl_find_range(struct dsi_backlight_config *bl, |
| int brightness, u32 *range) |
| { |
| u32 i; |
| |
| if (!bl || !bl->bl_notifier || !range) |
| return -EINVAL; |
| |
| for (i = 0; i < bl->bl_notifier->num_ranges; i++) { |
| if (brightness <= bl->bl_notifier->ranges[i]) { |
| *range = i; |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static void dsi_panel_bl_notifier_free(struct device *dev, |
| struct dsi_backlight_config *bl) |
| { |
| struct bl_notifier_data *bl_notifier = bl->bl_notifier; |
| |
| if (!bl_notifier) |
| return; |
| |
| devm_kfree(dev, bl_notifier); |
| bl->bl_notifier = NULL; |
| } |
| |
| static int dsi_panel_bl_parse_ranges(struct device *parent, |
| struct dsi_backlight_config *bl, struct device_node *of_node) |
| { |
| int num_ranges = 0; |
| |
| bl->bl_notifier = devm_kzalloc(parent, sizeof(struct bl_notifier_data), GFP_KERNEL); |
| if (bl->bl_notifier == NULL) { |
| pr_err("Failed to allocate memory for bl_notifier_data\n"); |
| return -ENOMEM; |
| } |
| |
| num_ranges = of_property_read_variable_u32_array(of_node, |
| "qcom,mdss-dsi-bl-notifier-ranges", |
| (u32 *)&bl->bl_notifier->ranges, |
| 0, |
| BL_RANGE_MAX); |
| if (num_ranges >= 0) { |
| bl->bl_notifier->num_ranges = num_ranges; |
| } else { |
| dsi_panel_bl_notifier_free(parent, bl); |
| pr_debug("Unable to parse optional backlight ranges (%d)\n", num_ranges); |
| return num_ranges; |
| } |
| |
| return 0; |
| } |
| |
| int dsi_panel_bl_parse_config(struct device *parent, struct dsi_backlight_config *bl) |
| { |
| struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config); |
| int rc = 0; |
| u32 val = 0; |
| const char *bl_type; |
| const char *data; |
| struct dsi_parser_utils *utils = &panel->utils; |
| char *bl_name; |
| |
| if (!strcmp(panel->type, "primary")) |
| bl_name = "qcom,mdss-dsi-bl-pmic-control-type"; |
| else |
| bl_name = "qcom,mdss-dsi-sec-bl-pmic-control-type"; |
| |
| bl_type = utils->get_property(utils->data, bl_name, NULL); |
| if (!bl_type) { |
| bl->type = DSI_BACKLIGHT_UNKNOWN; |
| } else if (!strcmp(bl_type, "bl_ctrl_pwm")) { |
| bl->type = DSI_BACKLIGHT_PWM; |
| } else if (!strcmp(bl_type, "bl_ctrl_wled")) { |
| bl->type = DSI_BACKLIGHT_WLED; |
| } else if (!strcmp(bl_type, "bl_ctrl_dcs")) { |
| bl->type = DSI_BACKLIGHT_DCS; |
| } else if (!strcmp(bl_type, "bl_ctrl_external")) { |
| bl->type = DSI_BACKLIGHT_EXTERNAL; |
| } else { |
| pr_debug("[%s] bl-pmic-control-type unknown-%s\n", |
| panel->name, bl_type); |
| bl->type = DSI_BACKLIGHT_UNKNOWN; |
| } |
| data = utils->get_property(utils->data, "qcom,bl-update-flag", NULL); |
| if (!data) { |
| panel->bl_config.bl_update = BL_UPDATE_NONE; |
| } else if (!strcmp(data, "delay_until_first_frame")) { |
| panel->bl_config.bl_update = BL_UPDATE_DELAY_UNTIL_FIRST_FRAME; |
| } else { |
| pr_debug("[%s] No valid bl-update-flag: %s\n", |
| panel->name, data); |
| panel->bl_config.bl_update = BL_UPDATE_NONE; |
| } |
| panel->bl_config.bl_scale = MAX_BL_SCALE_LEVEL; |
| panel->bl_config.bl_scale_sv = MAX_SV_BL_SCALE_LEVEL; |
| rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-min-level", &val); |
| if (rc) { |
| pr_debug("[%s] bl-min-level unspecified, defaulting to zero\n", |
| panel->name); |
| bl->bl_min_level = 0; |
| } else { |
| bl->bl_min_level = val; |
| } |
| |
| rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-max-level", &val); |
| if (rc) { |
| pr_debug("[%s] bl-max-level unspecified, defaulting to max level\n", |
| panel->name); |
| bl->bl_max_level = MAX_BL_LEVEL; |
| } else { |
| bl->bl_max_level = val; |
| } |
| |
| rc = utils->read_u32(utils->data, "qcom,mdss-brightness-max-level", |
| &val); |
| if (rc) { |
| pr_debug("[%s] brigheness-max-level unspecified, defaulting to 255\n", |
| panel->name); |
| bl->brightness_max_level = 255; |
| } else { |
| bl->brightness_max_level = val; |
| } |
| |
| rc = dsi_panel_bl_parse_lut(parent, utils->data, "qcom,mdss-dsi-bl-lut", |
| bl->brightness_max_level, &bl->lut); |
| if (rc) { |
| pr_err("[%s] failed to create backlight LUT, rc=%d\n", |
| panel->name, rc); |
| goto error; |
| } |
| pr_debug("[%s] bl-lut %sused\n", panel->name, bl->lut ? "" : "un"); |
| |
| bl->dimming_mode = utils->read_bool(utils->data, |
| "google,dsi-panel-dimming-enable"); |
| |
| rc = dsi_panel_bl_parse_hbm(parent, bl, utils->data); |
| if (rc) |
| 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", |
| panel->name, rc); |
| |
| rc = utils->read_u32(utils->data, "google,dsi-bl-cmd-high-byte-offset", |
| &val); |
| if (rc) { |
| pr_debug("[%s] dsi-bl-cmd-high-byte-offset unspecified, defaulting to 8\n", |
| panel->name); |
| bl->high_byte_offset = 8; |
| } else { |
| bl->high_byte_offset = val; |
| } |
| |
| bl->en_gpio = utils->get_named_gpio(utils->data, |
| "qcom,platform-bklight-en-gpio", |
| 0); |
| if (!gpio_is_valid(bl->en_gpio)) { |
| if (bl->en_gpio == -EPROBE_DEFER) { |
| pr_debug("[%s] failed to get bklt gpio, rc=%d\n", |
| panel->name, rc); |
| rc = -EPROBE_DEFER; |
| goto error; |
| } else { |
| pr_debug("[%s] failed to get bklt gpio, rc=%d\n", |
| panel->name, rc); |
| rc = 0; |
| goto error; |
| } |
| } |
| |
| error: |
| return rc; |
| } |
| |
| static int dsi_panel_bl_read_brightness(struct dsi_panel *panel, |
| struct dsi_backlight_config *bl_cfg, int *lvl) |
| { |
| u32 rc; |
| u8 buf[BL_BRIGHTNESS_BUF_SIZE]; |
| |
| rc = mipi_dsi_dcs_read(&panel->mipi_device, |
| MIPI_DCS_GET_DISPLAY_BRIGHTNESS, buf, BL_BRIGHTNESS_BUF_SIZE); |
| |
| if (rc <= 0 || rc > BL_BRIGHTNESS_BUF_SIZE) { |
| pr_err("mipi_dsi_dcs_read error: %d\n", rc); |
| return -EIO; |
| } |
| |
| if (rc == 1) |
| *lvl = buf[0]; |
| else if (rc == 2) |
| *lvl = be16_to_cpu(*(const __be16 *)buf); |
| else { |
| pr_err("unexpected buffer size: %d\n", rc); |
| return -EIO; |
| } |
| |
| /* Some panels may not clear non-functional bits. */ |
| *lvl &= (1 << fls(bl_cfg->bl_max_level)) - 1; |
| |
| return 0; |
| } |
| int dsi_panel_bl_brightness_handoff(struct dsi_panel *panel) |
| { |
| struct dsi_backlight_config *bl_cfg; |
| struct backlight_device *bl_device; |
| int bl_lvl = 0, brightness, rc; |
| |
| if (!panel || !panel->bl_config.bl_device) |
| return -EINVAL; |
| |
| bl_cfg = &panel->bl_config; |
| bl_device = bl_cfg->bl_device; |
| |
| rc = dsi_panel_bl_read_brightness(panel, bl_cfg, &bl_lvl); |
| if (rc) { |
| pr_err("Failed to read brightness from panel.\n"); |
| return rc; |
| } |
| |
| rc = dsi_backlight_lerp(bl_cfg->bl_min_level, bl_cfg->bl_max_level, 1, |
| bl_cfg->brightness_max_level, bl_lvl, &brightness); |
| if (rc) { |
| pr_err("Failed to map brightness to user space.\n"); |
| return rc; |
| } |
| |
| pr_debug("brightness 0x%x to user space %d\n", bl_lvl, brightness); |
| bl_device->props.brightness = brightness; |
| |
| return rc; |
| } |
| |
| int dsi_panel_bl_update_irc(struct dsi_backlight_config *bl, bool enable) |
| { |
| struct hbm_data *hbm = bl->hbm; |
| int rc = 0; |
| u32 byte_offset; |
| u32 bit_mask; |
| u32 irc_data_size; |
| |
| if (!hbm || hbm->irc_addr == 0) |
| return -EOPNOTSUPP; |
| |
| byte_offset = hbm->irc_bit_offset / BITS_PER_BYTE; |
| bit_mask = BIT(hbm->irc_bit_offset % BITS_PER_BYTE); |
| irc_data_size = byte_offset + 1; |
| |
| pr_info("irc update: %d\n", enable); |
| dsi_panel_cmd_set_transfer(hbm->panel, &hbm->irc_unlock_cmd); |
| if (hbm->irc_data == NULL) { |
| hbm->irc_data = kzalloc(irc_data_size, GFP_KERNEL); |
| if (hbm->irc_data == NULL) { |
| pr_err("failed to alloc irc_data.\n"); |
| goto done; |
| } |
| |
| rc = mipi_dsi_dcs_read(&hbm->panel->mipi_device, hbm->irc_addr, |
| hbm->irc_data, irc_data_size); |
| if (rc != irc_data_size) { |
| pr_err("failed to read irc.\n"); |
| goto done; |
| } |
| pr_info("Read back irc initial configuration\n"); |
| } |
| |
| if (enable) |
| hbm->irc_data[byte_offset] |= bit_mask; |
| else |
| hbm->irc_data[byte_offset] &= ~bit_mask; |
| |
| rc = mipi_dsi_dcs_write(&hbm->panel->mipi_device, |
| hbm->irc_addr, hbm->irc_data, irc_data_size); |
| |
| if (rc) |
| pr_err("failed to send irc cmd.\n"); |
| done: |
| dsi_panel_cmd_set_transfer(hbm->panel, &hbm->irc_lock_cmd); |
| return rc; |
| } |