| /* |
| * Copyright (c) 2019, 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: " fmt, __func__ |
| |
| #include <linux/atomic.h> |
| #include <linux/completion.h> |
| #include <linux/wait.h> |
| #include <linux/kthread.h> |
| #include <uapi/linux/sched/types.h> |
| #include <video/mipi_display.h> |
| |
| #include "dsi_display.h" |
| #include "dsi_panel.h" |
| #include "sde_trace.h" |
| #include "sde_connector.h" |
| |
| #define DSI_PANEL_GAMMA_NAME "google,dsi_s6e3hc2_gamma" |
| #define DSI_PANEL_SWITCH_NAME "google,dsi_panel_switch" |
| |
| #define TE_TIMEOUT_MS 50 |
| |
| #define for_each_display_mode(i, mode, panel) \ |
| for (i = 0, mode = dsi_panel_to_display(panel)->modes; \ |
| i < panel->num_timing_nodes; i++, mode++) |
| |
| |
| #define DSI_WRITE_CMD_BUF(dsi, cmd) \ |
| (IS_ERR_VALUE(mipi_dsi_dcs_write_buffer(dsi, cmd, ARRAY_SIZE(cmd)))) |
| |
| #define DEFAULT_GAMMA_STR "default" |
| #define CALI_GAMMA_HEADER_SIZE 3 |
| |
| #define S6E3HC2_DEFAULT_FPS 60 |
| |
| static const u8 unlock_cmd[] = { 0xF0, 0x5A, 0x5A }; |
| static const u8 lock_cmd[] = { 0xF0, 0xA5, 0xA5 }; |
| |
| struct panel_switch_funcs { |
| struct panel_switch_data *(*create)(struct dsi_panel *panel); |
| void (*destroy)(struct panel_switch_data *pdata); |
| void (*put_mode)(struct dsi_display_mode *mode); |
| void (*perform_switch)(struct panel_switch_data *pdata, |
| const struct dsi_display_mode *mode); |
| int (*post_enable)(struct panel_switch_data *pdata); |
| int (*support_update_te2)(struct dsi_panel *panel); |
| int (*support_update_hbm)(struct dsi_panel *); |
| int (*send_nolp_cmds)(struct dsi_panel *panel); |
| ssize_t (*support_cali_gamma_store)(struct dsi_panel *panel, |
| const char *buf, size_t count); |
| }; |
| |
| struct panel_switch_data { |
| struct dsi_panel *panel; |
| struct dentry *debug_root; |
| |
| struct kthread_work switch_work; |
| struct kthread_worker worker; |
| struct task_struct *thread; |
| |
| const struct dsi_display_mode *display_mode; |
| const struct dsi_display_mode *idle_mode; |
| wait_queue_head_t switch_wq; |
| bool switch_pending; |
| int switch_te_listen_count; |
| |
| atomic_t te_counter; |
| struct dsi_display_te_listener te_listener; |
| struct completion te_completion; |
| |
| const struct panel_switch_funcs *funcs; |
| }; |
| |
| static inline |
| struct dsi_display *dsi_panel_to_display(const struct dsi_panel *panel) |
| { |
| return dev_get_drvdata(panel->parent); |
| } |
| |
| static inline bool is_display_mode_same(const struct dsi_display_mode *m1, |
| const struct dsi_display_mode *m2) |
| { |
| return m1 && m2 && m1->timing.refresh_rate == m2->timing.refresh_rate; |
| } |
| |
| static inline void sde_atrace_mode_fps(const struct panel_switch_data *pdata, |
| const struct dsi_display_mode *mode) |
| { |
| sde_atrace('C', pdata->thread, "FPS", mode->timing.refresh_rate); |
| } |
| |
| ssize_t panel_dsi_write_buf(struct dsi_panel *panel, |
| const void *data, size_t len, bool send_last) |
| { |
| const struct mipi_dsi_device *dsi = &panel->mipi_device; |
| const struct mipi_dsi_host_ops *ops = panel->host->ops; |
| struct mipi_dsi_msg msg = { |
| .channel = dsi->channel, |
| .tx_buf = data, |
| .tx_len = len, |
| .flags = 0, |
| }; |
| |
| switch (len) { |
| case 0: |
| return -EINVAL; |
| |
| case 1: |
| msg.type = MIPI_DSI_DCS_SHORT_WRITE; |
| break; |
| |
| case 2: |
| msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM; |
| break; |
| |
| default: |
| msg.type = MIPI_DSI_DCS_LONG_WRITE; |
| break; |
| } |
| |
| if (send_last) |
| msg.flags |= MIPI_DSI_MSG_LASTCOMMAND; |
| |
| return ops->transfer(panel->host, &msg); |
| } |
| |
| static void panel_handle_te(struct dsi_display_te_listener *tl) |
| { |
| struct panel_switch_data *pdata; |
| |
| if (unlikely(!tl)) |
| return; |
| |
| pdata = container_of(tl, struct panel_switch_data, te_listener); |
| |
| complete(&pdata->te_completion); |
| |
| if (likely(pdata->thread)) { |
| /* 1-bit counter that shows up in panel thread timeline */ |
| sde_atrace('C', pdata->thread, "TE_VSYNC", |
| atomic_inc_return(&pdata->te_counter) & 1); |
| } |
| } |
| |
| enum s6e3hc2_wrctrld_flags { |
| S6E3HC2_WRCTRLD_DIMMING_BIT = BIT(3), |
| S6E3HC2_WRCTRLD_FRAME_RATE_BIT = BIT(4), |
| S6E3HC2_WRCTRLD_BCTRL_BIT = BIT(5), |
| S6E3HC2_WRCTRLD_HBM_BIT = BIT(7) | BIT(6), |
| }; |
| |
| struct s6e3hc2_wrctrl_data { |
| bool hbm_enable; |
| bool dimming_active; |
| u32 refresh_rate; |
| }; |
| |
| static int s6e3hc2_write_ctrld_reg(struct dsi_panel *panel, |
| const struct s6e3hc2_wrctrl_data *data, |
| bool send_last) |
| { |
| u8 wrctrl_reg = S6E3HC2_WRCTRLD_BCTRL_BIT; |
| u8 payload[2] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0 }; |
| |
| if (data->hbm_enable) |
| wrctrl_reg |= S6E3HC2_WRCTRLD_HBM_BIT; |
| |
| if (data->dimming_active) |
| wrctrl_reg |= S6E3HC2_WRCTRLD_DIMMING_BIT; |
| |
| if (data->refresh_rate == 90) |
| wrctrl_reg |= S6E3HC2_WRCTRLD_FRAME_RATE_BIT; |
| |
| pr_debug("hbm_enable: %d dimming_active: %d refresh_rate: %d hz\n", |
| data->hbm_enable, data->dimming_active, data->refresh_rate); |
| |
| payload[1] = wrctrl_reg; |
| |
| return panel_dsi_write_buf(panel, &payload, sizeof(payload), send_last); |
| } |
| |
| static int s6e3hc2_switch_mode_update(struct dsi_panel *panel, |
| const struct dsi_display_mode *mode, |
| bool send_last) |
| { |
| const struct hbm_data *hbm = panel->bl_config.hbm; |
| struct s6e3hc2_wrctrl_data data = {0}; |
| |
| if (unlikely(!mode || !hbm)) |
| return -EINVAL; |
| |
| /* display is expected not to operate in HBM mode for the first bl range |
| * (cur_range = 0) when panel->hbm_mode is not off |
| */ |
| data.hbm_enable = (panel->hbm_mode != HBM_MODE_OFF) && |
| hbm->cur_range != 0; |
| data.dimming_active = panel->bl_config.dimming_mode || |
| panel->bl_config.hbm->dimming_active; |
| data.refresh_rate = mode->timing.refresh_rate; |
| |
| return s6e3hc2_write_ctrld_reg(panel, &data, send_last); |
| } |
| |
| static void panel_switch_cmd_set_transfer(struct panel_switch_data *pdata, |
| const struct dsi_display_mode *mode) |
| { |
| struct dsi_panel *panel = pdata->panel; |
| struct dsi_panel_cmd_set *cmd; |
| int rc; |
| |
| cmd = &mode->priv_info->cmd_sets[DSI_CMD_SET_TIMING_SWITCH]; |
| |
| rc = dsi_panel_cmd_set_transfer(panel, cmd); |
| if (rc) |
| pr_warn("failed to send TIMING switch cmd, rc=%d\n", rc); |
| } |
| |
| static void panel_switch_to_mode(struct panel_switch_data *pdata, |
| const struct dsi_display_mode *mode) |
| { |
| struct dsi_panel *panel = pdata->panel; |
| |
| SDE_ATRACE_BEGIN(__func__); |
| if (pdata->funcs && pdata->funcs->perform_switch) |
| pdata->funcs->perform_switch(pdata, mode); |
| |
| if (pdata->switch_pending) { |
| pdata->switch_pending = false; |
| wake_up_all(&pdata->switch_wq); |
| } |
| |
| if (panel->bl_config.bl_device) |
| sysfs_notify(&panel->bl_config.bl_device->dev.kobj, NULL, |
| "state"); |
| |
| SDE_ATRACE_END(__func__); |
| } |
| |
| static void panel_switch_worker(struct kthread_work *work) |
| { |
| struct panel_switch_data *pdata; |
| struct dsi_panel *panel; |
| struct dsi_display *display; |
| const struct dsi_display_mode *mode; |
| const unsigned long timeout = msecs_to_jiffies(TE_TIMEOUT_MS); |
| int rc; |
| u32 te_listen_cnt; |
| |
| if (unlikely(!work)) |
| return; |
| |
| pdata = container_of(work, struct panel_switch_data, switch_work); |
| panel = pdata->panel; |
| if (unlikely(!panel)) |
| return; |
| |
| display = dsi_panel_to_display(panel); |
| if (unlikely(!display)) |
| return; |
| |
| mutex_lock(&panel->panel_lock); |
| mode = pdata->display_mode; |
| if (unlikely(!mode)) { |
| mutex_unlock(&panel->panel_lock); |
| return; |
| } |
| |
| SDE_ATRACE_BEGIN(__func__); |
| |
| pr_debug("switching mode to %dhz\n", mode->timing.refresh_rate); |
| |
| te_listen_cnt = pdata->switch_te_listen_count; |
| if (te_listen_cnt) { |
| reinit_completion(&pdata->te_completion); |
| dsi_display_add_te_listener(display, &pdata->te_listener); |
| } |
| |
| /* switch is shadowed by vsync so this can be done ahead of TE */ |
| panel_switch_to_mode(pdata, mode); |
| mutex_unlock(&panel->panel_lock); |
| |
| if (te_listen_cnt) { |
| rc = wait_for_completion_timeout(&pdata->te_completion, |
| timeout); |
| if (!rc) |
| pr_warn("Timed out waiting for TE while switching!\n"); |
| else |
| pr_debug("TE received after %dus\n", |
| jiffies_to_usecs(timeout - rc)); |
| } |
| |
| sde_atrace_mode_fps(pdata, mode); |
| SDE_ATRACE_END(__func__); |
| |
| /* |
| * this is meant only for debugging purposes, keep TE enabled for a few |
| * extra frames to see how they align after switch |
| */ |
| if (te_listen_cnt) { |
| te_listen_cnt--; |
| pr_debug("waiting for %d extra te\n", te_listen_cnt); |
| while (rc && te_listen_cnt) { |
| rc = wait_for_completion_timeout(&pdata->te_completion, |
| timeout); |
| te_listen_cnt--; |
| } |
| dsi_display_remove_te_listener(display, &pdata->te_listener); |
| } |
| } |
| |
| static bool dsi_mode_matches_cmdline(const struct dsi_display_mode *dm, |
| const struct drm_cmdline_mode *cm) |
| { |
| |
| if (!cm->refresh_specified && !cm->specified) |
| return false; |
| if (cm->refresh_specified && cm->refresh != dm->timing.refresh_rate) |
| return false; |
| if (cm->specified && (cm->xres != dm->timing.h_active || |
| cm->yres != dm->timing.v_active)) |
| return false; |
| return true; |
| } |
| |
| static const struct dsi_display_mode * |
| display_mode_from_cmdline(const struct dsi_panel *panel, |
| const char *modestr) |
| { |
| const struct dsi_display *display; |
| const struct dsi_display_mode *mode; |
| struct drm_cmdline_mode cm = {0}; |
| int i; |
| |
| display = dsi_panel_to_display(panel); |
| if (!display) |
| return ERR_PTR(-ENODEV); |
| |
| if (!drm_mode_parse_command_line_for_connector(modestr, |
| display->drm_conn, |
| &cm)) |
| return ERR_PTR(-EINVAL); |
| |
| for_each_display_mode(i, mode, panel) { |
| if (dsi_mode_matches_cmdline(mode, &cm)) |
| return mode; |
| } |
| |
| return NULL; |
| } |
| |
| static const struct dsi_display_mode * |
| display_mode_from_user(const struct dsi_panel *panel, |
| const char __user *user_buf, size_t user_len) |
| { |
| char modestr[40]; |
| size_t len = min(user_len, sizeof(modestr) - 1); |
| int rc; |
| |
| rc = copy_from_user(modestr, user_buf, len); |
| if (rc) |
| return ERR_PTR(-EFAULT); |
| |
| modestr[len] = '\0'; |
| |
| return display_mode_from_cmdline(panel, strim(modestr)); |
| } |
| |
| static void panel_queue_switch(struct panel_switch_data *pdata, |
| const struct dsi_display_mode *new_mode) |
| { |
| if (unlikely(!pdata || !pdata->panel || !new_mode)) |
| return; |
| |
| kthread_flush_work(&pdata->switch_work); |
| |
| mutex_lock(&pdata->panel->panel_lock); |
| pdata->display_mode = new_mode; |
| pdata->switch_pending = true; |
| mutex_unlock(&pdata->panel->panel_lock); |
| |
| kthread_queue_work(&pdata->worker, &pdata->switch_work); |
| } |
| |
| static int panel_switch(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| |
| if (!panel->cur_mode) |
| return -EINVAL; |
| |
| SDE_ATRACE_BEGIN(__func__); |
| panel_queue_switch(pdata, panel->cur_mode); |
| SDE_ATRACE_END(__func__); |
| |
| return 0; |
| } |
| |
| static int panel_pre_kickoff(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| const unsigned long timeout = msecs_to_jiffies(TE_TIMEOUT_MS); |
| |
| if (!wait_event_timeout(pdata->switch_wq, |
| !pdata->switch_pending, timeout)) |
| pr_warn("Timed out waiting for panel switch\n"); |
| |
| return 0; |
| } |
| |
| static int panel_flush_switch_queue(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| |
| kthread_flush_worker(&pdata->worker); |
| |
| return 0; |
| } |
| |
| static int panel_post_enable(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| int rc = 0; |
| |
| if (!pdata) |
| return -EINVAL; |
| |
| if (pdata->funcs && pdata->funcs->post_enable) |
| rc = pdata->funcs->post_enable(pdata); |
| |
| return rc; |
| } |
| |
| const struct dsi_display_mode *get_panel_display_mode(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| |
| return unlikely(!pdata) ? NULL : pdata->display_mode; |
| } |
| |
| static int panel_idle(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| const struct dsi_display_mode *idle_mode; |
| |
| if (unlikely(!pdata)) |
| return -EINVAL; |
| |
| kthread_flush_work(&pdata->switch_work); |
| |
| mutex_lock(&panel->panel_lock); |
| idle_mode = pdata->idle_mode; |
| if (idle_mode && !is_display_mode_same(idle_mode, panel->cur_mode)) { |
| /* |
| * clocks are going to be turned off right after this call, so |
| * switch needs to happen synchronously |
| */ |
| pdata->display_mode = idle_mode; |
| panel_switch_to_mode(pdata, idle_mode); |
| |
| sde_atrace_mode_fps(pdata, idle_mode); |
| } |
| mutex_unlock(&panel->panel_lock); |
| |
| SDE_ATRACE_INT("display_idle", 1); |
| |
| return 0; |
| } |
| |
| static int panel_wakeup(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| const struct dsi_display_mode *mode = NULL; |
| |
| if (unlikely(!pdata)) |
| return -EINVAL; |
| |
| mutex_lock(&panel->panel_lock); |
| if (!is_display_mode_same(pdata->display_mode, panel->cur_mode)) |
| mode = panel->cur_mode; |
| mutex_unlock(&panel->panel_lock); |
| |
| if (mode) |
| panel_queue_switch(pdata, mode); |
| |
| SDE_ATRACE_INT("display_idle", 0); |
| |
| return 0; |
| } |
| |
| static int panel_update_hbm(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| |
| if (unlikely(!pdata || !pdata->funcs)) |
| return -EINVAL; |
| |
| if (!pdata->funcs->support_update_hbm) |
| return -EOPNOTSUPP; |
| |
| return pdata->funcs->support_update_hbm(panel); |
| } |
| |
| static int s6e3hc2_te2_set_edge_info(struct dsi_panel *panel) |
| { |
| struct dsi_panel_te2_config *te2; |
| enum dsi_panel_te2_type current_type; |
| u8 payload[5] = { 0xB9, 0x00, 0x77, 0xBA, 0xBC }; |
| u8 rising_low_byte, rising_high_byte; |
| u8 falling_low_byte, falling_high_byte; |
| |
| if (unlikely(!panel)) |
| return -EINVAL; |
| |
| te2 = &panel->te2_config; |
| current_type = te2->current_type; |
| |
| rising_low_byte = te2->te2_edge[current_type].rising & 0xff; |
| rising_high_byte = (te2->te2_edge[current_type].rising >> 8) & 0xff; |
| falling_low_byte = te2->te2_edge[current_type].falling & 0xff; |
| falling_high_byte = (te2->te2_edge[current_type].falling >> 8) & 0xff; |
| |
| payload[2] = (rising_high_byte << 4) | falling_high_byte; |
| payload[3] = rising_low_byte; |
| payload[4] = falling_low_byte; |
| |
| return panel_dsi_write_buf(panel, |
| &payload, sizeof(payload), true); |
| } |
| |
| static int s6e3hc2_te2_update(struct dsi_panel *panel) |
| { |
| struct mipi_dsi_device *dsi = &panel->mipi_device; |
| const u8 global_para[] = { 0xB0, 0x2C, 0xF2 }; |
| const u8 tout_enable[] = { 0xF2, 0x01 }; |
| |
| if (s6e3hc2_te2_set_edge_info(panel)) |
| goto error; |
| |
| if (DSI_WRITE_CMD_BUF(dsi, global_para)) |
| goto error; |
| |
| if (DSI_WRITE_CMD_BUF(dsi, tout_enable)) |
| goto error; |
| |
| return 0; |
| |
| error: |
| pr_err("TE2: Failed to te2 setting update\n"); |
| return -EFAULT; |
| } |
| |
| /* |
| * s6e3hc2_te2_update_reg_locked() write the TE2 register data |
| * into panel. Control TE2 signal timing. |
| */ |
| static int s6e3hc2_te2_update_reg_locked(struct dsi_panel *panel) |
| { |
| struct mipi_dsi_device *dsi; |
| int rc; |
| |
| if (unlikely(!panel || !panel->private_data)) |
| return -EINVAL; |
| |
| dsi = &panel->mipi_device; |
| |
| if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd)) |
| return -EFAULT; |
| |
| rc = s6e3hc2_te2_update(panel); |
| if (rc < 0) |
| pr_warn("TE2: setting update fail\n"); |
| |
| if (DSI_WRITE_CMD_BUF(dsi, lock_cmd)) |
| return -EFAULT; |
| |
| return rc; |
| } |
| |
| static int panel_update_te2(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| |
| if (unlikely(!panel || !pdata || !pdata->funcs)) |
| return -EINVAL; |
| |
| if (!pdata->funcs->support_update_te2) |
| return -EOPNOTSUPP; |
| |
| return pdata->funcs->support_update_te2(panel); |
| } |
| |
| static int s6e3hc2_te2_normal_mode_update(struct dsi_panel *panel, |
| bool reg_locked) |
| { |
| const struct dsi_display_mode *mode; |
| |
| if (unlikely(!panel || !panel->te2_config.te2_ready)) |
| return -EINVAL; |
| |
| mode = panel->cur_mode; |
| panel->te2_config.current_type = |
| ((mode->timing.refresh_rate == S6E3HC2_DEFAULT_FPS) |
| ? TE2_EDGE_60HZ : TE2_EDGE_90HZ); |
| |
| if (reg_locked == true) |
| s6e3hc2_te2_update_reg_locked(panel); |
| else |
| s6e3hc2_te2_update(panel); |
| |
| return 0; |
| } |
| |
| static int panel_send_nolp(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| |
| if (unlikely(!pdata || !pdata->funcs)) |
| return -EINVAL; |
| |
| if (!pdata->funcs->send_nolp_cmds) |
| return -EOPNOTSUPP; |
| |
| return pdata->funcs->send_nolp_cmds(panel); |
| } |
| |
| static ssize_t debugfs_panel_switch_mode_write(struct file *file, |
| const char __user *user_buf, |
| size_t user_len, |
| loff_t *ppos) |
| { |
| struct seq_file *seq = file->private_data; |
| struct panel_switch_data *pdata = seq->private; |
| const struct dsi_display_mode *mode; |
| |
| if (!pdata->panel || !dsi_panel_initialized(pdata->panel)) |
| return -ENOENT; |
| |
| mode = display_mode_from_user(pdata->panel, user_buf, user_len); |
| if (IS_ERR(mode)) |
| return PTR_ERR(mode); |
| else if (!mode) |
| return -ENOENT; |
| |
| panel_queue_switch(pdata, mode); |
| |
| return user_len; |
| } |
| |
| static int debugfs_panel_switch_mode_read(struct seq_file *seq, void *data) |
| { |
| struct panel_switch_data *pdata = seq->private; |
| const struct dsi_display_mode *mode; |
| |
| if (unlikely(!pdata->panel)) |
| return -ENOENT; |
| |
| mutex_lock(&pdata->panel->panel_lock); |
| mode = pdata->display_mode; |
| mutex_unlock(&pdata->panel->panel_lock); |
| |
| if (mode) |
| seq_printf(seq, "%dx%d@%d\n", mode->timing.h_active, |
| mode->timing.v_active, mode->timing.refresh_rate); |
| else |
| seq_puts(seq, "unknown"); |
| |
| return 0; |
| } |
| |
| static int debugfs_panel_switch_mode_open(struct inode *inode, struct file *f) |
| { |
| return single_open(f, debugfs_panel_switch_mode_read, inode->i_private); |
| } |
| |
| static const struct file_operations panel_switch_fops = { |
| .owner = THIS_MODULE, |
| .open = debugfs_panel_switch_mode_open, |
| .write = debugfs_panel_switch_mode_write, |
| .read = seq_read, |
| .release = single_release, |
| }; |
| |
| static ssize_t te2_table_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| const struct dsi_display *display; |
| struct dsi_panel *panel; |
| struct dsi_backlight_config *bl; |
| struct backlight_device *bd; |
| u32 table[TE2_EDGE_MAX * 2] = {0}; |
| char *buf_dup; |
| ssize_t rc, buf_dup_len; |
| u8 i, table_len; |
| |
| display = dev_get_drvdata(dev); |
| if (unlikely(!display || !display->panel || !count)) |
| return -EINVAL; |
| |
| panel = display->panel; |
| bl = &panel->bl_config; |
| bd = bl->bl_device; |
| if (unlikely(!bd)) |
| return -EINVAL; |
| |
| if (!panel->te2_config.te2_ready) |
| return -EOPNOTSUPP; |
| |
| buf_dup = kstrndup(buf, count, GFP_KERNEL); |
| if (!buf_dup) |
| return -ENOMEM; |
| |
| buf_dup_len = strlen(buf_dup) + 1; |
| |
| rc = parse_u32_buf(buf_dup, buf_dup_len, table, TE2_EDGE_MAX * 2); |
| if (rc < 0) { |
| kfree(buf_dup); |
| pr_warn("incorrect parameters from te2 table node, rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| table_len = rc / 2; |
| if (table_len != ARRAY_SIZE(panel->te2_config.te2_edge)) { |
| kfree(buf_dup); |
| pr_warn("incorrect array size of te2 table.\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&panel->panel_lock); |
| for (i = 0; i < table_len; i++) { |
| panel->te2_config.te2_edge[i].rising = table[i * 2]; |
| panel->te2_config.te2_edge[i].falling = table[i * 2 + 1]; |
| } |
| |
| if (!is_standby_mode(bd->props.state)) { |
| pr_debug("te2_config.current_type =%d\n", |
| panel->te2_config.current_type); |
| s6e3hc2_te2_update_reg_locked(panel); |
| } |
| |
| mutex_unlock(&panel->panel_lock); |
| |
| kfree(buf_dup); |
| |
| return count; |
| } |
| |
| static ssize_t te2_table_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| const struct dsi_display *display; |
| struct dsi_panel *panel; |
| int i; |
| ssize_t len = 0; |
| |
| display = dev_get_drvdata(dev); |
| if (unlikely(!display || !display->panel)) |
| return -EINVAL; |
| |
| panel = display->panel; |
| if (!panel->te2_config.te2_ready) |
| return -EOPNOTSUPP; |
| |
| mutex_lock(&panel->panel_lock); |
| |
| for (i = 0; i < TE2_EDGE_MAX; i++) { |
| len += scnprintf((buf + len), PAGE_SIZE - len, "%u %u ", |
| panel->te2_config.te2_edge[i].rising, |
| panel->te2_config.te2_edge[i].falling); |
| } |
| |
| mutex_unlock(&panel->panel_lock); |
| |
| len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); |
| |
| return len; |
| } |
| |
| static DEVICE_ATTR_RW(te2_table); |
| |
| static struct attribute *panel_te2_sysfs_attrs[] = { |
| &dev_attr_te2_table.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group panel_te2_attrs_group = { |
| .attrs = panel_te2_sysfs_attrs, |
| }; |
| |
| |
| static ssize_t sysfs_idle_mode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| const struct dsi_display *display; |
| struct panel_switch_data *pdata; |
| const struct dsi_display_mode *mode = NULL; |
| ssize_t rc; |
| |
| display = dev_get_drvdata(dev); |
| if (unlikely(!display || !display->panel || |
| !display->panel->private_data)) |
| return -EINVAL; |
| |
| pdata = display->panel->private_data; |
| |
| mutex_lock(&pdata->panel->panel_lock); |
| mode = pdata->idle_mode; |
| mutex_unlock(&pdata->panel->panel_lock); |
| |
| if (mode) |
| rc = snprintf(buf, PAGE_SIZE, "%dx%d@%d\n", |
| mode->timing.h_active, mode->timing.v_active, |
| mode->timing.refresh_rate); |
| else |
| rc = snprintf(buf, PAGE_SIZE, "none\n"); |
| |
| return rc; |
| } |
| static ssize_t sysfs_idle_mode_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| const struct dsi_display *display; |
| struct panel_switch_data *pdata; |
| const struct dsi_display_mode *mode = NULL; |
| |
| display = dev_get_drvdata(dev); |
| if (unlikely(!display || !display->panel || |
| !display->panel->private_data)) |
| return -EINVAL; |
| |
| pdata = display->panel->private_data; |
| if (count > 1 && strncmp(buf, "none", 4)) { |
| char *modestr = kstrndup(buf, count, GFP_KERNEL); |
| |
| /* remove any trailing lf at end of sysfs input */ |
| mode = display_mode_from_cmdline(display->panel, |
| strim(modestr)); |
| kfree(modestr); |
| |
| if (IS_ERR(mode)) |
| return PTR_ERR(mode); |
| } |
| mutex_lock(&display->panel->panel_lock); |
| pdata->idle_mode = mode; |
| mutex_unlock(&display->panel->panel_lock); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(idle_mode, 0644, |
| sysfs_idle_mode_show, |
| sysfs_idle_mode_store); |
| |
| static struct attribute *panel_switch_sysfs_attrs[] = { |
| &dev_attr_idle_mode.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group panel_switch_sysfs_attrs_group = { |
| .attrs = panel_switch_sysfs_attrs, |
| }; |
| |
| static const struct dsi_panel_funcs panel_funcs = { |
| .mode_switch = panel_switch, |
| .pre_disable = panel_flush_switch_queue, |
| .pre_kickoff = panel_pre_kickoff, |
| .idle = panel_idle, |
| .wakeup = panel_wakeup, |
| .post_enable = panel_post_enable, |
| .pre_lp1 = panel_flush_switch_queue, |
| .update_te2 = panel_update_te2, |
| .update_hbm = panel_update_hbm, |
| .send_nolp = panel_send_nolp, |
| }; |
| |
| static int panel_switch_data_init(struct dsi_panel *panel, |
| struct panel_switch_data *pdata) |
| { |
| struct sched_param param = { |
| .sched_priority = 16, |
| }; |
| const struct dsi_display *display; |
| |
| display = dsi_panel_to_display(panel); |
| if (unlikely(!display)) |
| return -ENOENT; |
| |
| kthread_init_work(&pdata->switch_work, panel_switch_worker); |
| kthread_init_worker(&pdata->worker); |
| pdata->thread = kthread_run(kthread_worker_fn, &pdata->worker, "panel"); |
| if (IS_ERR_OR_NULL(pdata->thread)) |
| return -EFAULT; |
| |
| pdata->panel = panel; |
| pdata->te_listener.handler = panel_handle_te; |
| pdata->display_mode = panel->cur_mode; |
| |
| sched_setscheduler(pdata->thread, SCHED_FIFO, ¶m); |
| init_completion(&pdata->te_completion); |
| init_waitqueue_head(&pdata->switch_wq); |
| atomic_set(&pdata->te_counter, 0); |
| |
| panel->private_data = pdata; |
| panel->funcs = &panel_funcs; |
| |
| pdata->debug_root = debugfs_create_dir("switch", display->root); |
| debugfs_create_file("mode", 0600, pdata->debug_root, pdata, |
| &panel_switch_fops); |
| debugfs_create_u32("te_listen_count", 0600, pdata->debug_root, |
| &pdata->switch_te_listen_count); |
| debugfs_create_atomic_t("te_counter", 0600, pdata->debug_root, |
| &pdata->te_counter); |
| |
| sysfs_create_group(&panel->parent->kobj, |
| &panel_switch_sysfs_attrs_group); |
| sysfs_create_group(&panel->parent->kobj, |
| &panel_te2_attrs_group); |
| |
| return 0; |
| } |
| |
| static void panel_switch_data_deinit(struct panel_switch_data *pdata) |
| { |
| kthread_flush_worker(&pdata->worker); |
| kthread_stop(pdata->thread); |
| sysfs_remove_group(&pdata->panel->parent->kobj, |
| &panel_switch_sysfs_attrs_group); |
| sysfs_remove_group(&pdata->panel->parent->kobj, |
| &panel_te2_attrs_group); |
| } |
| |
| static void panel_switch_data_destroy(struct panel_switch_data *pdata) |
| { |
| panel_switch_data_deinit(pdata); |
| if (pdata->panel && pdata->panel->parent) |
| devm_kfree(pdata->panel->parent, pdata); |
| } |
| |
| static struct panel_switch_data *panel_switch_create(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata; |
| int rc; |
| |
| pdata = devm_kzalloc(panel->parent, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) |
| return ERR_PTR(-ENOMEM); |
| |
| rc = panel_switch_data_init(panel, pdata); |
| if (rc) |
| return ERR_PTR(rc); |
| |
| return pdata; |
| } |
| |
| const struct panel_switch_funcs panel_switch_default_funcs = { |
| .create = panel_switch_create, |
| .destroy = panel_switch_data_destroy, |
| .perform_switch = panel_switch_cmd_set_transfer, |
| }; |
| |
| struct gamma_calibration_info { |
| u8 *data; |
| size_t len; |
| }; |
| |
| struct gamma_fixup_location { |
| u32 src_idx; |
| u32 dst_idx; |
| u8 cmd; |
| u8 mask; |
| }; |
| |
| struct gamma_fixup_info { |
| struct gamma_fixup_location *fixup_loc; |
| u32 fixup_loc_count; |
| u32 panel_id_len; |
| u8 *panel_id; |
| }; |
| |
| struct gamma_switch_data { |
| struct panel_switch_data base; |
| struct kthread_work gamma_work; |
| struct gamma_fixup_info fixup_gamma; |
| struct gamma_calibration_info cali_gamma; |
| u8 num_of_cali_gamma; |
| bool native_gamma_ready; |
| }; |
| |
| #define S6E3HC2_GAMMA_BAND_LEN 45 |
| |
| /** |
| * s6e3hc2_gamma_info - Information used to access gamma data on s6e3hc2. |
| * @cmd: Command to use when writing/reading gamma from the DDIC. |
| * @len: Total number of bytes to write/read from DDIC, including prefix_len. |
| * @prefix_len: Number of bytes that precede gamma data when writing/reading |
| * from the DDIC. This is a subset of len. |
| * @flash_offset: Address offset to use when reading from flash. |
| */ |
| static const struct s6e3hc2_gamma_info { |
| u8 cmd; |
| u32 len; |
| u32 prefix_len; |
| u32 flash_offset; |
| } s6e3hc2_gamma_tables[] = { |
| /* order of commands matter due to use of cmds grouping */ |
| { 0xC8, S6E3HC2_GAMMA_BAND_LEN * 3, 0, 0x0000 }, |
| { 0xC9, S6E3HC2_GAMMA_BAND_LEN * 4, 0, 0x0087 }, |
| { 0xB3, 2 + S6E3HC2_GAMMA_BAND_LEN, 2, 0x013B }, |
| }; |
| |
| #define S6E3HC2_NUM_GAMMA_TABLES ARRAY_SIZE(s6e3hc2_gamma_tables) |
| #define MAX_GAMMA_PACKETS S6E3HC2_NUM_GAMMA_TABLES |
| |
| struct s6e3hc2_gamma_packet_group { |
| size_t num_packets; |
| struct { |
| u32 index; |
| size_t max_size; |
| } packets[MAX_GAMMA_PACKETS]; |
| }; |
| |
| struct s6e3hc2_gamma_packet { |
| u32 dbv_threshold; |
| size_t num_groups; |
| struct s6e3hc2_gamma_packet_group groups[MAX_GAMMA_PACKETS]; |
| }; |
| |
| static const struct s6e3hc2_gamma_packet s6e3hc2_gamma_packets[] = { |
| { .dbv_threshold = 0x39, .num_groups = 2, .groups = { |
| { .num_packets = 1, .packets = { |
| { 1 }, /* 0xC9 */ |
| }}, { .num_packets = 2, .packets = { |
| { 0 }, /* 0xC8 */ |
| { 2 }, /* 0xB3 */ |
| }}, |
| }}, { .dbv_threshold = 0xB6, .num_groups = 2, .groups = { |
| { .num_packets = 2, .packets = { |
| { 0 }, /* 0xC8 */ |
| { 1, S6E3HC2_GAMMA_BAND_LEN }, /* 0xC9: 1st gamma */ |
| }}, { .num_packets = 2, .packets = { |
| { 1 }, /* 0xC9: full gamma */ |
| { 2 }, /* 0xB3 */ |
| }}, |
| }}, { .dbv_threshold = UINT_MAX, .num_groups = 2, .groups = { |
| { .num_packets = 2, .packets = { |
| { 0 }, /* 0xC8 */ |
| { 2 }, /* 0xB3 */ |
| }}, { .num_packets = 1, .packets = { |
| { 1 }, /* 0xC9 */ |
| }}, |
| }}, |
| }; |
| |
| struct s6e3hc2_panel_data { |
| u8 *gamma_data[S6E3HC2_NUM_GAMMA_TABLES]; |
| u8 *native_gamma_data[S6E3HC2_NUM_GAMMA_TABLES]; |
| }; |
| |
| static void s6e3hc2_gamma_group_write(struct panel_switch_data *pdata, |
| struct s6e3hc2_panel_data *priv_data, |
| const struct s6e3hc2_gamma_packet_group *group) |
| { |
| const struct s6e3hc2_gamma_info *info; |
| const void *data; |
| size_t len; |
| int i; |
| |
| for (i = 0; i < group->num_packets; i++) { |
| const u32 ndx = group->packets[i].index; |
| const u32 max_size = group->packets[i].max_size; |
| const bool last_packet = (i + 1) == group->num_packets; |
| |
| if (WARN(ndx >= S6E3HC2_NUM_GAMMA_TABLES, "invalid ndx=%d\n")) |
| continue; |
| |
| data = priv_data->gamma_data[ndx]; |
| |
| if (WARN(!data, "Gamma table #%d not read\n", i)) |
| continue; |
| |
| info = &s6e3hc2_gamma_tables[ndx]; |
| /* extra byte for the dsi command */ |
| len = (max_size ?: info->len) + 1; |
| |
| pr_debug("Writing %d bytes to 0x%02X gamma last: %d\n", |
| len, info->cmd, last_packet); |
| |
| |
| if (IS_ERR_VALUE(panel_dsi_write_buf(pdata->panel, data, len, |
| last_packet))) |
| pr_warn("failed sending gamma cmd 0x%02x\n", info->cmd); |
| } |
| } |
| |
| /* |
| * s6e3hc2_gamma_update() expects DD-IC to be in unlocked state, so |
| * to make sure there are unlock/lock commands when calling this func. |
| */ |
| static void s6e3hc2_gamma_update(struct panel_switch_data *pdata, |
| const struct dsi_display_mode *mode) |
| { |
| struct s6e3hc2_panel_data *priv_data; |
| const struct s6e3hc2_gamma_packet *packet = NULL; |
| int i, dbv; |
| |
| if (unlikely(!mode || !mode->priv_info)) |
| return; |
| |
| priv_data = mode->priv_info->switch_data; |
| if (unlikely(!priv_data)) |
| return; |
| |
| dbv = pdata->panel->bl_config.bl_actual; |
| |
| for (i = 0; i < ARRAY_SIZE(s6e3hc2_gamma_packets); i++) { |
| if (dbv < s6e3hc2_gamma_packets[i].dbv_threshold) { |
| packet = &s6e3hc2_gamma_packets[i]; |
| break; |
| } |
| } |
| |
| if (!packet) { |
| pr_err("Unable to find packet for dbv=%02X\n", dbv); |
| return; |
| } |
| |
| pr_debug("Found packet ndx=%d for dbv=%02X\n", i, dbv); |
| |
| for (i = 0; i < packet->num_groups; i++) |
| s6e3hc2_gamma_group_write(pdata, priv_data, &packet->groups[i]); |
| } |
| |
| static void s6e3hc2_gamma_update_reg_locked(struct panel_switch_data *pdata, |
| const struct dsi_display_mode *mode) |
| { |
| struct dsi_panel *panel = pdata->panel; |
| struct mipi_dsi_device *dsi = &panel->mipi_device; |
| |
| if (unlikely(!mode)) |
| return; |
| |
| DSI_WRITE_CMD_BUF(dsi, unlock_cmd); |
| s6e3hc2_gamma_update(pdata, mode); |
| DSI_WRITE_CMD_BUF(dsi, lock_cmd); |
| } |
| |
| static int s6e3hc2_gamma_read_otp(struct panel_switch_data *pdata, |
| const struct s6e3hc2_panel_data *priv_data) |
| { |
| struct mipi_dsi_device *dsi; |
| ssize_t rc; |
| int i; |
| |
| SDE_ATRACE_BEGIN(__func__); |
| |
| dsi = &pdata->panel->mipi_device; |
| |
| for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) { |
| const struct s6e3hc2_gamma_info *info = |
| &s6e3hc2_gamma_tables[i]; |
| u8 *buf = priv_data->gamma_data[i]; |
| |
| /* store cmd on first byte to send payload as is */ |
| *buf = info->cmd; |
| buf++; |
| |
| rc = mipi_dsi_dcs_read(dsi, info->cmd, buf, info->len); |
| if (rc != info->len) |
| pr_warn("Only got %zd / %d bytes\n", rc, info->len); |
| } |
| |
| SDE_ATRACE_END(__func__); |
| |
| return 0; |
| } |
| |
| static int s6e3hc2_gamma_read_flash(struct panel_switch_data *pdata, |
| const struct s6e3hc2_panel_data *priv_data) |
| { |
| struct mipi_dsi_device *dsi; |
| const u8 flash_mode_en[] = { 0xF1, 0xF1, 0xA2 }; |
| const u8 flash_mode_dis[] = { 0xF1, 0xA5, 0xA5 }; |
| const u8 pgm_dis[] = { 0xC0, 0x00 }; |
| const u8 pgm_en[] = { 0xC0, 0x02 }; |
| const u8 exe_inst[] = { 0xC0, 0x03 }; |
| const u8 write_en[] = { 0xC1, |
| 0x00, 0x00, 0x00, 0x06, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x05 }; |
| const u8 quad_en[] = { 0xC1, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x40, 0x02, 0x00, 0x00, |
| 0x00, 0x00, 0x10 }; |
| ssize_t rc; |
| int i, j; |
| |
| SDE_ATRACE_BEGIN(__func__); |
| |
| dsi = &pdata->panel->mipi_device; |
| |
| if (DSI_WRITE_CMD_BUF(dsi, flash_mode_en) || |
| DSI_WRITE_CMD_BUF(dsi, pgm_en) || |
| DSI_WRITE_CMD_BUF(dsi, write_en) || |
| DSI_WRITE_CMD_BUF(dsi, exe_inst)) |
| goto error; |
| usleep_range(950, 1000); |
| |
| if (DSI_WRITE_CMD_BUF(dsi, quad_en) || |
| DSI_WRITE_CMD_BUF(dsi, exe_inst)) |
| goto error; |
| msleep(30); |
| |
| for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) { |
| const struct s6e3hc2_gamma_info *info; |
| const u8 gpar_cmd[] = { 0xB0, 0x0B }; |
| u8 flash_rd[] = { 0xC1, |
| 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, /*Read Inst*/ |
| 0x0A, 0x00, 0x00, /* Flash data Address : 0A0000h */ |
| 0x00, 0x05, /* Bit rate setting */ |
| 0x01 }; |
| u32 offset; |
| u8 *buf; |
| |
| info = &s6e3hc2_gamma_tables[i]; |
| offset = info->flash_offset; |
| buf = priv_data->gamma_data[i]; |
| /* store cmd on first byte to send payload as is */ |
| *buf = info->cmd; |
| buf++; |
| |
| for (j = info->prefix_len; j < info->len; j++, offset++) { |
| u8 tmp[2]; |
| |
| flash_rd[9] = (offset >> 8) & 0xFF; |
| flash_rd[10] = offset & 0xFF; |
| |
| if (DSI_WRITE_CMD_BUF(dsi, flash_rd) || |
| DSI_WRITE_CMD_BUF(dsi, exe_inst)) |
| goto error; |
| usleep_range(200, 250); |
| |
| if (DSI_WRITE_CMD_BUF(dsi, gpar_cmd)) |
| goto error; |
| |
| rc = mipi_dsi_dcs_read(dsi, 0xFB, tmp, sizeof(tmp)); |
| if (rc != 2) |
| pr_warn("Only got %zd / 2 bytes\n", rc); |
| |
| pr_debug("read flash offset %04x: %02X %02X\n", |
| offset, tmp[0], tmp[1]); |
| buf[j] = tmp[1]; |
| } |
| } |
| |
| if (DSI_WRITE_CMD_BUF(dsi, pgm_dis) || |
| DSI_WRITE_CMD_BUF(dsi, flash_mode_dis)) |
| goto error; |
| |
| SDE_ATRACE_END(__func__); |
| |
| return 0; |
| |
| error: |
| SDE_ATRACE_END(__func__); |
| |
| pr_err("Failed to read gamma from flash\n"); |
| return -EFAULT; |
| } |
| |
| static int s6e3hc2_gamma_alloc_mode_memory(const struct dsi_display_mode *mode) |
| { |
| struct s6e3hc2_panel_data *priv_data; |
| size_t offset, native_offset, total_size; |
| int i; |
| u8 *buf, *native_buf; |
| |
| if (unlikely(!mode || !mode->priv_info)) |
| return -EINVAL; |
| |
| if (mode->priv_info->switch_data) |
| return 0; |
| |
| total_size = sizeof(*priv_data); |
| |
| for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) |
| total_size += s6e3hc2_gamma_tables[i].len; |
| /* add an extra byte for cmd */ |
| total_size += S6E3HC2_NUM_GAMMA_TABLES; |
| |
| /* hold native gamma */ |
| native_offset = total_size; |
| total_size *= 2; |
| |
| priv_data = kmalloc(total_size, GFP_KERNEL); |
| if (!priv_data) |
| return -ENOMEM; |
| |
| /* use remaining data at the end of buffer */ |
| buf = (u8 *)(priv_data); |
| offset = sizeof(*priv_data); |
| native_buf = buf + native_offset; |
| |
| for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) { |
| const size_t len = s6e3hc2_gamma_tables[i].len; |
| |
| priv_data->gamma_data[i] = buf + offset; |
| priv_data->native_gamma_data[i] = native_buf + offset; |
| /* reserve extra byte to hold cmd */ |
| offset += len + 1; |
| } |
| |
| mode->priv_info->switch_data = priv_data; |
| |
| return 0; |
| } |
| |
| static int s6e3hc2_gamma_read_mode(struct panel_switch_data *pdata, |
| const struct dsi_display_mode *mode) |
| { |
| const struct s6e3hc2_panel_data *priv_data; |
| int rc; |
| |
| rc = s6e3hc2_gamma_alloc_mode_memory(mode); |
| if (rc) |
| return rc; |
| |
| priv_data = mode->priv_info->switch_data; |
| |
| switch (mode->timing.refresh_rate) { |
| case 60: |
| rc = s6e3hc2_gamma_read_otp(pdata, priv_data); |
| break; |
| case 90: |
| rc = s6e3hc2_gamma_read_flash(pdata, priv_data); |
| break; |
| default: |
| pr_warn("Unknown refresh rate!\n"); |
| rc = -EINVAL; |
| break; |
| } |
| |
| return rc; |
| } |
| |
| static int find_gamma_data_for_refresh_rate(struct dsi_panel *panel, |
| u32 refresh_rate, u8 ***gamma_data) |
| { |
| struct dsi_display_mode *mode; |
| int i; |
| |
| if (!gamma_data) |
| return -EINVAL; |
| |
| for_each_display_mode(i, mode, panel) |
| if (mode->timing.refresh_rate == refresh_rate) { |
| struct s6e3hc2_panel_data *priv_data = |
| mode->priv_info->switch_data; |
| |
| if (unlikely(!priv_data)) |
| return -ENODATA; |
| |
| *gamma_data = priv_data->gamma_data; |
| return 0; |
| } |
| |
| return -ENODATA; |
| } |
| |
| /* |
| * For some modes, gamma curves are located in registers addresses that require |
| * an offset to read/write. Because we cannot access a register offset directly, |
| * we must read the portion of the data that precedes the gamma curve data |
| * itself ("prefix") as well. In such cases, we read the prefix + gamma curve |
| * data from DDIC registers, and only gamma curve data from flash. |
| * |
| * This function looks for such gamma curves, and adjusts gamma data read from |
| * flash to include the prefix read from registers. The result is that, for all |
| * modes, wherever the gamma curves were read from (registers or flash), when |
| * that gamma data is written back to registers the write includes the original |
| * prefix. |
| * In other words, when we write gamma data to registers, we do not modify |
| * prefix data; we only modify gamma data. |
| */ |
| static int s6e3hc2_gamma_set_prefixes(struct panel_switch_data *pdata) |
| { |
| int i; |
| int rc = 0; |
| u8 **gamma_data_otp; |
| u8 **gamma_data_flash; |
| |
| /* |
| * For s6e3hc2, 60Hz gamma curves are read from OTP and 90Hz |
| * gamma curves are read from flash. |
| */ |
| rc = find_gamma_data_for_refresh_rate(pdata->panel, 60, |
| &gamma_data_otp); |
| if (rc) { |
| pr_err("Error setting gamma prefix: no matching OTP mode, err %d\n", |
| rc); |
| return rc; |
| } |
| |
| rc = find_gamma_data_for_refresh_rate(pdata->panel, 90, |
| &gamma_data_flash); |
| if (rc) { |
| pr_err("Error setting gamma prefix: no matching flash mode, err %d\n", |
| rc); |
| return rc; |
| } |
| |
| for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) { |
| const struct s6e3hc2_gamma_info *gamma_info = |
| &s6e3hc2_gamma_tables[i]; |
| u8 *gamma_curve_otp = gamma_data_otp[i]; |
| u8 *gamma_curve_flash = gamma_data_flash[i]; |
| |
| if (!gamma_info->prefix_len) |
| continue; |
| |
| /* skip command byte */ |
| gamma_curve_otp++; |
| gamma_curve_flash++; |
| |
| memcpy(gamma_curve_flash, gamma_curve_otp, |
| gamma_info->prefix_len); |
| } |
| |
| return rc; |
| } |
| |
| static int s6e3hc2_copy_gamma_table(u8 **dest_gamma, u8 **src_gamma) |
| { |
| int i; |
| |
| for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) { |
| const size_t len = s6e3hc2_gamma_tables[i].len; |
| |
| memcpy(dest_gamma[i], src_gamma[i], len * sizeof(**src_gamma)); |
| } |
| |
| return 0; |
| } |
| |
| static int s6e3hc2_gamma_read_tables(struct panel_switch_data *pdata) |
| { |
| struct gamma_switch_data *sdata; |
| const struct dsi_display_mode *mode; |
| struct mipi_dsi_device *dsi; |
| int i, rc = 0; |
| |
| if (unlikely(!pdata || !pdata->panel)) |
| return -ENOENT; |
| |
| sdata = container_of(pdata, struct gamma_switch_data, base); |
| if (sdata->native_gamma_ready) |
| return 0; |
| |
| dsi = &pdata->panel->mipi_device; |
| if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd)) |
| return -EFAULT; |
| |
| for_each_display_mode(i, mode, pdata->panel) { |
| rc = s6e3hc2_gamma_read_mode(pdata, mode); |
| if (rc) { |
| pr_err("Unable to read gamma for mode #%d\n", i); |
| goto abort; |
| } |
| } |
| |
| rc = s6e3hc2_gamma_set_prefixes(pdata); |
| if (rc) { |
| pr_err("Unable to set gamma prefix\n"); |
| goto abort; |
| } |
| |
| for_each_display_mode(i, mode, pdata->panel) { |
| struct s6e3hc2_panel_data *priv_data = |
| mode->priv_info->switch_data; |
| u8 **gamma_data = priv_data->gamma_data; |
| u8 **native_gamma_data = priv_data->native_gamma_data; |
| |
| s6e3hc2_copy_gamma_table(native_gamma_data, gamma_data); |
| } |
| |
| sdata->native_gamma_ready = true; |
| |
| abort: |
| if (DSI_WRITE_CMD_BUF(dsi, lock_cmd)) |
| return -EFAULT; |
| |
| return rc; |
| } |
| |
| static void s6e3hc2_gamma_print(struct seq_file *seq, |
| const struct dsi_display_mode *mode) |
| { |
| const struct s6e3hc2_panel_data *priv_data; |
| int i, j; |
| |
| if (!mode || !mode->priv_info) |
| return; |
| |
| seq_printf(seq, "\n=== %dhz Mode Gamma ===\n", |
| mode->timing.refresh_rate); |
| |
| priv_data = mode->priv_info->switch_data; |
| |
| if (!priv_data) { |
| seq_puts(seq, "No data available!\n"); |
| return; |
| } |
| |
| for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) { |
| const size_t len = s6e3hc2_gamma_tables[i].len; |
| const u8 cmd = s6e3hc2_gamma_tables[i].cmd; |
| const u8 *buf = priv_data->gamma_data[i] + 1; |
| |
| seq_printf(seq, "0x%02X:", cmd); |
| for (j = 0; j < len; j++) { |
| if (j && (j % 8) == 0) |
| seq_puts(seq, "\n "); |
| seq_printf(seq, " %02X", buf[j]); |
| } |
| seq_puts(seq, "\n"); |
| } |
| } |
| |
| static int debugfs_s6e3hc2_gamma_read(struct seq_file *seq, void *data) |
| { |
| struct panel_switch_data *pdata = seq->private; |
| int i, rc; |
| |
| if (unlikely(!pdata || !pdata->panel)) |
| return -EINVAL; |
| |
| if (!dsi_panel_initialized(pdata->panel)) |
| return -EPIPE; |
| |
| mutex_lock(&pdata->panel->panel_lock); |
| rc = s6e3hc2_gamma_read_tables(pdata); |
| if (!rc) { |
| const struct dsi_display_mode *mode; |
| |
| for_each_display_mode(i, mode, pdata->panel) |
| s6e3hc2_gamma_print(seq, mode); |
| } |
| mutex_unlock(&pdata->panel->panel_lock); |
| |
| return rc; |
| } |
| |
| ssize_t debugfs_s6e3hc2_gamma_write(struct file *file, |
| const char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct seq_file *seq = file->private_data; |
| struct panel_switch_data *pdata = seq->private; |
| struct gamma_switch_data *sdata; |
| |
| if (!pdata || !pdata->panel) |
| return -EINVAL; |
| |
| sdata = container_of(pdata, struct gamma_switch_data, base); |
| |
| mutex_lock(&pdata->panel->panel_lock); |
| sdata->native_gamma_ready = false; |
| mutex_unlock(&pdata->panel->panel_lock); |
| |
| return count; |
| } |
| |
| static int debugfs_s6e3hc2_gamma_open(struct inode *inode, struct file *f) |
| { |
| return single_open(f, debugfs_s6e3hc2_gamma_read, inode->i_private); |
| } |
| |
| static const struct file_operations s6e3hc2_read_gamma_fops = { |
| .owner = THIS_MODULE, |
| .open = debugfs_s6e3hc2_gamma_open, |
| .write = debugfs_s6e3hc2_gamma_write, |
| .read = seq_read, |
| .release = single_release, |
| }; |
| |
| static int s6e3hc2_check_gamma_infos(const struct s6e3hc2_gamma_info *infos, |
| size_t num_infos) |
| { |
| int i; |
| |
| if (!infos) { |
| pr_err("Null gamma infos\n"); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < num_infos; i++) { |
| const struct s6e3hc2_gamma_info *info = &infos[i]; |
| |
| if (unlikely(info->prefix_len >= info->len)) { |
| pr_err("Gamma prefix length (%u) >= total length length (%u)\n", |
| info->prefix_len, info->len); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int s6e3hc2_restore_gamma_data(struct gamma_switch_data *sdata) |
| { |
| struct panel_switch_data *pdata; |
| struct dsi_display_mode *mode; |
| int i; |
| |
| if (unlikely(!sdata)) |
| return -EINVAL; |
| |
| pdata = &sdata->base; |
| if (unlikely(!pdata->panel)) |
| return -EINVAL; |
| |
| for_each_display_mode(i, mode, pdata->panel) { |
| struct s6e3hc2_panel_data *priv_data = |
| mode->priv_info->switch_data; |
| u8 **gamma_data = priv_data->gamma_data; |
| u8 **native_gamma_data = priv_data->native_gamma_data; |
| |
| s6e3hc2_copy_gamma_table(gamma_data, native_gamma_data); |
| } |
| |
| sdata->num_of_cali_gamma = 0; |
| |
| return 0; |
| } |
| |
| static int parse_payload_len(const u8 *payload, size_t len, size_t *out_size) |
| { |
| size_t payload_len; |
| |
| if (!out_size || len <= CALI_GAMMA_HEADER_SIZE) |
| return -EINVAL; |
| |
| *out_size = payload_len = (payload[1] << 8) + payload[2]; |
| |
| return payload_len && |
| (payload_len + CALI_GAMMA_HEADER_SIZE <= len) ? 0 : -EINVAL; |
| } |
| |
| /* ID (1 byte) | payload size (2 bytes) | payload */ |
| static int s6e3hc2_check_gamma_payload(const u8 *src, size_t len) |
| { |
| size_t total_len = 0; |
| int num_payload = 0; |
| |
| if (!src) |
| return -EINVAL; |
| |
| while (len > total_len) { |
| const u8 *payload = src + total_len; |
| size_t payload_len; |
| int rc; |
| |
| rc = parse_payload_len(payload, len - total_len, &payload_len); |
| if (rc) |
| return -EINVAL; |
| |
| total_len += CALI_GAMMA_HEADER_SIZE + payload_len; |
| num_payload++; |
| } |
| |
| return num_payload; |
| } |
| |
| static void s6e3hc2_gamma_fixup_after_overwrite( |
| const struct gamma_fixup_location *fixup_loc, |
| u8 *gamma_buf, const size_t len) |
| { |
| const u32 src_idx = fixup_loc->src_idx; |
| const u32 dst_idx = fixup_loc->dst_idx; |
| const u8 mask = fixup_loc->mask; |
| |
| if (len <= src_idx || len <= dst_idx) { |
| pr_warn("gamma length %zu of cmd 0x%x not enough\n", len, |
| fixup_loc->cmd); |
| return; |
| } |
| |
| if (mask) { |
| gamma_buf[dst_idx] &= ~mask; |
| gamma_buf[dst_idx] |= (gamma_buf[src_idx] & mask); |
| } |
| } |
| |
| static int s6e3hc2_overwrite_gamma_bands( |
| const struct dsi_panel_vendor_info *panel_info, |
| const struct gamma_fixup_info *fixup_gamma, |
| u8 **gamma_data, const u8 *src, size_t len) |
| { |
| int i, num_of_reg; |
| size_t payload_len, read_len = 0; |
| |
| if (!gamma_data || !src) |
| return -EINVAL; |
| |
| num_of_reg = s6e3hc2_check_gamma_payload(src, len); |
| if (num_of_reg != S6E3HC2_NUM_GAMMA_TABLES - 1) { |
| pr_err("Invalid gamma bands\n"); |
| return -EINVAL; |
| } |
| |
| if (fixup_gamma) { |
| if (!panel_info || !panel_info->extinfo_length || |
| panel_info->extinfo_length != fixup_gamma->panel_id_len || |
| memcmp(panel_info->extinfo, fixup_gamma->panel_id, |
| panel_info->extinfo_length)) |
| fixup_gamma = NULL; |
| } |
| |
| /* reg (1 byte) | band size (2 bytes) | band */ |
| for (i = 0; i < num_of_reg; i++) { |
| const struct s6e3hc2_gamma_info *gamma_info = |
| &s6e3hc2_gamma_tables[i]; |
| const u8 *payload = src + read_len; |
| const u8 cmd = *payload; |
| u8 *tmp_buf = gamma_data[i] + sizeof(cmd) |
| + gamma_info->prefix_len; |
| |
| parse_payload_len(payload, len - read_len, &payload_len); |
| if (gamma_info->cmd != cmd || |
| payload_len != (gamma_info->len - gamma_info->prefix_len)) { |
| pr_err("Failed to overwrite 0x%02X reg\n", cmd); |
| return -EINVAL; |
| } |
| |
| memcpy(tmp_buf, payload + CALI_GAMMA_HEADER_SIZE, payload_len); |
| if (fixup_gamma && fixup_gamma->fixup_loc) { |
| int cnt; |
| |
| for (cnt = 0; cnt < fixup_gamma->fixup_loc_count; cnt++) |
| if (cmd == fixup_gamma->fixup_loc[cnt].cmd) |
| s6e3hc2_gamma_fixup_after_overwrite( |
| &fixup_gamma->fixup_loc[cnt], |
| tmp_buf, payload_len); |
| } |
| read_len = CALI_GAMMA_HEADER_SIZE + payload_len; |
| } |
| |
| return 0; |
| } |
| |
| static int s6e3hc2_overwrite_gamma_data(struct gamma_switch_data *sdata) |
| { |
| struct panel_switch_data *pdata; |
| struct dsi_panel *panel; |
| u8 *payload, **old_gamma_data; |
| size_t payload_len, total_len; |
| int rc = 0, num_of_fps; |
| u8 count = 0; |
| |
| if (unlikely(!sdata)) |
| return -EINVAL; |
| |
| pdata = &sdata->base; |
| payload = sdata->cali_gamma.data; |
| |
| if (unlikely(!pdata->panel || !payload)) |
| return -EINVAL; |
| |
| total_len = sdata->cali_gamma.len; |
| num_of_fps = s6e3hc2_check_gamma_payload(payload, total_len); |
| if (num_of_fps <= 0) { |
| pr_err("Invalid gamma data\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * 60Hz's gamma table and 90Hz's gamma table are the same size, |
| * so we won't recalculate payload_len. |
| */ |
| parse_payload_len(payload, total_len, &payload_len); |
| |
| panel = pdata->panel; |
| |
| /* FPS (1 byte) | gamma size (2 bytes) | gamma */ |
| while (count < num_of_fps) { |
| const u8 cali_fps = *payload; |
| |
| rc = find_gamma_data_for_refresh_rate(panel, |
| cali_fps, &old_gamma_data); |
| if (rc) { |
| pr_err("Not support %ufps, err %d\n", cali_fps, rc); |
| break; |
| } |
| payload += CALI_GAMMA_HEADER_SIZE; |
| rc = s6e3hc2_overwrite_gamma_bands(&panel->vendor_info, |
| &sdata->fixup_gamma, |
| old_gamma_data, payload, |
| payload_len); |
| if (rc) { |
| pr_err("Failed to overwrite gamma\n"); |
| break; |
| } |
| payload += payload_len; |
| count++; |
| } |
| |
| if (rc) { |
| s6e3hc2_restore_gamma_data(sdata); |
| } else { |
| s6e3hc2_gamma_update_reg_locked(pdata, panel->cur_mode); |
| sdata->num_of_cali_gamma = count; |
| pr_info("Finished overwriting gamma\n"); |
| } |
| return rc; |
| } |
| |
| static void s6e3hc2_release_cali_gamma(struct gamma_switch_data *sdata) |
| { |
| if (!sdata) |
| return; |
| |
| kfree(sdata->cali_gamma.data); |
| sdata->cali_gamma.data = NULL; |
| sdata->cali_gamma.len = 0; |
| } |
| |
| static int s6e3hc2_create_cali_gamma(struct gamma_switch_data *sdata, |
| size_t len, char *buf, size_t buf_len) |
| { |
| int rc = 0; |
| |
| if (unlikely(!sdata || !buf)) |
| return -EINVAL; |
| |
| if (sdata->cali_gamma.data) |
| s6e3hc2_release_cali_gamma(sdata); |
| |
| sdata->cali_gamma.data = kzalloc(len, GFP_KERNEL); |
| if (!sdata->cali_gamma.data) |
| return -ENOMEM; |
| |
| rc = parse_byte_buf(sdata->cali_gamma.data, len, buf, buf_len); |
| if (rc <= 0) { |
| pr_err("Invalid gamma data\n"); |
| s6e3hc2_release_cali_gamma(sdata); |
| return -EINVAL; |
| } |
| sdata->cali_gamma.len = rc; |
| |
| return 0; |
| } |
| |
| static void s6e3hc2_gamma_work(struct kthread_work *work) |
| { |
| struct gamma_switch_data *sdata; |
| struct dsi_panel *panel; |
| |
| sdata = container_of(work, struct gamma_switch_data, gamma_work); |
| panel = sdata->base.panel; |
| |
| if (!panel) |
| return; |
| |
| mutex_lock(&panel->panel_lock); |
| |
| s6e3hc2_gamma_read_tables(&sdata->base); |
| if (sdata->cali_gamma.data) { |
| s6e3hc2_overwrite_gamma_data(sdata); |
| s6e3hc2_release_cali_gamma(sdata); |
| } |
| |
| mutex_unlock(&panel->panel_lock); |
| } |
| |
| static ssize_t |
| s6e3hc2_gamma_store(struct dsi_panel *panel, const char *buf, size_t count) |
| { |
| struct panel_switch_data *pdata; |
| struct gamma_switch_data *sdata; |
| size_t len, buf_dup_len; |
| int rc = 0; |
| char *buf_dup; |
| |
| if (unlikely(!panel || !panel->private_data)) |
| return -EINVAL; |
| |
| pdata = panel->private_data; |
| sdata = container_of(pdata, struct gamma_switch_data, base); |
| |
| /* calculate length for worst case (1 digit per byte + whitespace) */ |
| len = (count + 1) / 2; |
| buf_dup = kstrndup(buf, count, GFP_KERNEL); |
| if (!buf_dup) |
| return -ENOMEM; |
| |
| buf_dup_len = strlen(buf_dup) + 1; |
| |
| mutex_lock(&panel->panel_lock); |
| |
| if (!strncmp(buf_dup, DEFAULT_GAMMA_STR, strlen(DEFAULT_GAMMA_STR))) { |
| if (!sdata->num_of_cali_gamma) |
| goto done; |
| |
| s6e3hc2_restore_gamma_data(sdata); |
| if (dsi_panel_initialized(panel)) |
| s6e3hc2_gamma_update_reg_locked(pdata, |
| panel->cur_mode); |
| pr_info("Finished restore gamma\n"); |
| goto done; |
| } |
| |
| rc = s6e3hc2_create_cali_gamma(sdata, len, buf_dup, buf_dup_len); |
| if (rc) |
| goto error; |
| |
| if (!sdata->native_gamma_ready) { |
| pr_info("Gamma table is not ready!\n"); |
| goto done; |
| } |
| |
| if (!dsi_panel_initialized(panel)) { |
| pr_warn("Panel not yet initialized.\n"); |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| rc = s6e3hc2_overwrite_gamma_data(sdata); |
| if (rc) |
| pr_warn("Failed to update calibrated gamma.\n"); |
| |
| s6e3hc2_release_cali_gamma(sdata); |
| |
| error: |
| done: |
| mutex_unlock(&panel->panel_lock); |
| kfree(buf_dup); |
| |
| return rc ? rc : count; |
| } |
| |
| static ssize_t |
| gamma_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| const struct dsi_display *display; |
| struct dsi_panel *panel; |
| struct panel_switch_data *pdata; |
| |
| display = dev_get_drvdata(dev); |
| if (unlikely(!display || !display->panel || |
| !display->panel->private_data)) |
| return -EINVAL; |
| |
| panel = display->panel; |
| pdata = panel->private_data; |
| |
| if (unlikely(!pdata || !pdata->funcs)) |
| return -EINVAL; |
| |
| if (!pdata->funcs->support_cali_gamma_store) |
| return -EOPNOTSUPP; |
| |
| return pdata->funcs->support_cali_gamma_store(panel, buf, count); |
| } |
| |
| struct device_attribute dev_attr_gamma = __ATTR_WO(gamma); |
| |
| static struct attribute *gamma_attrs[] = { |
| &dev_attr_gamma.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group gamma_group = { |
| .attrs = gamma_attrs, |
| }; |
| |
| static void |
| s6e3hc2_gamma_release_fixup_info(struct gamma_fixup_info *fixup_gamma) |
| { |
| if (!fixup_gamma) |
| return; |
| |
| kfree(fixup_gamma->fixup_loc); |
| fixup_gamma->fixup_loc = NULL; |
| fixup_gamma->fixup_loc_count = 0; |
| |
| kfree(fixup_gamma->panel_id); |
| fixup_gamma->panel_id = NULL; |
| fixup_gamma->panel_id_len = 0; |
| } |
| |
| #define GAMMA_FIXUP_LOCATION_LEN 4 |
| static void |
| s6e3hc2_gamma_create_fixup_info(struct dsi_panel *panel, |
| struct gamma_fixup_info *fixup_gamma) |
| { |
| struct dsi_display *display; |
| struct device_node *of_node; |
| int i, rc, id_len, loc_len, offset = 0; |
| u32 loc_count, *fixup_loc; |
| |
| if (unlikely(!panel || !fixup_gamma)) |
| return; |
| |
| display = dsi_panel_to_display(panel); |
| if (unlikely(!display || !display->pdev)) { |
| pr_warn("Invalid device\n"); |
| return; |
| } |
| |
| of_node = display->pdev->dev.of_node; |
| if (unlikely(!of_node)) |
| return; |
| |
| id_len = of_property_count_u8_elems(of_node, |
| "google,gamma_fixup_panel_id"); |
| if (id_len <= 0) { |
| pr_debug("Skip gamma fixup\n"); |
| return; |
| } |
| |
| fixup_gamma->panel_id = kzalloc(id_len * |
| sizeof(*fixup_gamma->panel_id), GFP_KERNEL); |
| if (!fixup_gamma->panel_id) |
| return; |
| |
| rc = of_property_read_u8_array(of_node, |
| "google,gamma_fixup_panel_id", |
| fixup_gamma->panel_id, id_len); |
| if (rc) { |
| pr_err("Failed to parse panel id\n"); |
| goto error; |
| } |
| |
| loc_len = of_property_count_u32_elems(of_node, |
| "google,gamma_fixup_location"); |
| if (loc_len <= 0 || loc_len % GAMMA_FIXUP_LOCATION_LEN != 0) { |
| pr_err("invalid format\n"); |
| goto error; |
| } |
| |
| loc_count = loc_len / GAMMA_FIXUP_LOCATION_LEN; |
| fixup_loc = kcalloc(loc_len, sizeof(*fixup_loc), GFP_KERNEL); |
| if (!fixup_loc) |
| goto error; |
| |
| rc = of_property_read_u32_array(of_node, |
| "google,gamma_fixup_location", fixup_loc, loc_len); |
| if (rc) { |
| pr_err("Failed to parse location\n"); |
| goto error_fixup_loc; |
| } |
| |
| fixup_gamma->fixup_loc = kcalloc(loc_count, |
| sizeof(*fixup_gamma->fixup_loc), GFP_KERNEL); |
| if (!fixup_gamma->fixup_loc) |
| goto error_fixup_loc; |
| |
| for (i = 0; i < loc_count; i++) { |
| fixup_gamma->fixup_loc[i].cmd = fixup_loc[offset]; |
| fixup_gamma->fixup_loc[i].src_idx = fixup_loc[offset + 1]; |
| fixup_gamma->fixup_loc[i].dst_idx = fixup_loc[offset + 2]; |
| fixup_gamma->fixup_loc[i].mask = fixup_loc[offset + 3]; |
| offset += GAMMA_FIXUP_LOCATION_LEN; |
| } |
| |
| fixup_gamma->fixup_loc_count = loc_count; |
| fixup_gamma->panel_id_len = id_len; |
| |
| kfree(fixup_loc); |
| |
| return; |
| |
| error_fixup_loc: |
| kfree(fixup_loc); |
| error: |
| kfree(fixup_gamma->panel_id); |
| fixup_gamma->panel_id = NULL; |
| } |
| |
| static struct panel_switch_data *s6e3hc2_switch_create(struct dsi_panel *panel) |
| { |
| struct gamma_switch_data *sdata; |
| int rc; |
| |
| rc = s6e3hc2_check_gamma_infos(s6e3hc2_gamma_tables, |
| S6E3HC2_NUM_GAMMA_TABLES); |
| if (rc) |
| return ERR_PTR(rc); |
| |
| sdata = devm_kzalloc(panel->parent, sizeof(*sdata), GFP_KERNEL); |
| if (!sdata) |
| return ERR_PTR(-ENOMEM); |
| |
| rc = panel_switch_data_init(panel, &sdata->base); |
| if (rc) |
| return ERR_PTR(rc); |
| |
| kthread_init_work(&sdata->gamma_work, s6e3hc2_gamma_work); |
| debugfs_create_file("gamma", 0600, sdata->base.debug_root, |
| &sdata->base, &s6e3hc2_read_gamma_fops); |
| s6e3hc2_gamma_create_fixup_info(panel, &sdata->fixup_gamma); |
| |
| return &sdata->base; |
| } |
| |
| static void s6e3hc2_switch_data_destroy(struct panel_switch_data *pdata) |
| { |
| struct gamma_switch_data *sdata; |
| |
| sdata = container_of(pdata, struct gamma_switch_data, base); |
| |
| s6e3hc2_gamma_release_fixup_info(&sdata->fixup_gamma); |
| panel_switch_data_deinit(pdata); |
| if (pdata->panel && pdata->panel->parent) |
| devm_kfree(pdata->panel->parent, sdata); |
| } |
| |
| static int s6e3hc2_update_hbm(struct dsi_panel *panel) |
| { |
| const struct panel_switch_data *pdata = panel->private_data; |
| |
| return s6e3hc2_switch_mode_update(panel, pdata->display_mode, true); |
| } |
| |
| static void s6e3hc2_perform_switch(struct panel_switch_data *pdata, |
| const struct dsi_display_mode *mode) |
| { |
| struct dsi_panel *panel = pdata->panel; |
| struct mipi_dsi_device *dsi = &panel->mipi_device; |
| |
| if (!mode) |
| return; |
| |
| if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd)) |
| return; |
| |
| s6e3hc2_switch_mode_update(panel, mode, false); |
| s6e3hc2_gamma_update(pdata, mode); |
| |
| s6e3hc2_te2_normal_mode_update(panel, false); |
| |
| DSI_WRITE_CMD_BUF(dsi, lock_cmd); |
| } |
| |
| int s6e3hc2_send_nolp_cmds(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata; |
| struct dsi_display_mode *cur_mode; |
| struct dsi_panel_cmd_set *cmd; |
| int rc = 0; |
| |
| if (unlikely(!panel || !panel->cur_mode)) |
| return -EINVAL; |
| |
| pdata = panel->private_data; |
| cur_mode = panel->cur_mode; |
| |
| cmd = &cur_mode->priv_info->cmd_sets[DSI_CMD_SET_NOLP]; |
| rc = dsi_panel_cmd_set_transfer(panel, cmd); |
| if (rc) { |
| pr_debug("[%s] failed to send DSI_CMD_SET_NOLP cmd, rc=%d\n", |
| panel->name, rc); |
| return rc; |
| } |
| |
| s6e3hc2_gamma_update(pdata, cur_mode); |
| |
| s6e3hc2_te2_normal_mode_update(panel, false); |
| |
| cmd = &cur_mode->priv_info->cmd_sets[DSI_CMD_SET_POST_NOLP]; |
| rc = dsi_panel_cmd_set_transfer(panel, cmd); |
| if (rc) |
| pr_debug("[%s] failed to send DSI_CMD_SET_POST_NOLP cmd, rc=%d\n", |
| panel->name, rc); |
| return rc; |
| } |
| |
| static bool s6e3hc2_need_update_gamma( |
| const struct gamma_switch_data *sdata, |
| const struct dsi_display_mode *mode) |
| { |
| const struct panel_switch_data *pdata = &sdata->base; |
| |
| return mode && pdata && pdata->panel && |
| !(mode->dsi_mode_flags & DSI_MODE_FLAG_DMS) && |
| (pdata->panel->power_mode == SDE_MODE_DPMS_ON) && |
| !((mode->timing.refresh_rate == S6E3HC2_DEFAULT_FPS) && |
| !sdata->num_of_cali_gamma); |
| } |
| |
| static int s6e3hc2_post_enable(struct panel_switch_data *pdata) |
| { |
| struct gamma_switch_data *sdata; |
| const struct dsi_display_mode *mode; |
| struct dsi_panel *panel = pdata->panel; |
| |
| if (unlikely(!pdata || !pdata->panel)) |
| return -ENOENT; |
| |
| sdata = container_of(pdata, struct gamma_switch_data, base); |
| mode = pdata->panel->cur_mode; |
| |
| kthread_flush_work(&sdata->gamma_work); |
| if (!sdata->native_gamma_ready) { |
| kthread_queue_work(&pdata->worker, &sdata->gamma_work); |
| } else if (s6e3hc2_need_update_gamma(sdata, mode)) { |
| mutex_lock(&pdata->panel->panel_lock); |
| s6e3hc2_gamma_update_reg_locked(pdata, mode); |
| mutex_unlock(&pdata->panel->panel_lock); |
| pr_debug("Updated gamma for %dhz\n", mode->timing.refresh_rate); |
| } |
| |
| if (!(mode->dsi_mode_flags & DSI_MODE_FLAG_DMS)) { |
| mutex_lock(&panel->panel_lock); |
| s6e3hc2_te2_normal_mode_update(panel, true); |
| mutex_unlock(&panel->panel_lock); |
| } |
| |
| return 0; |
| } |
| |
| const struct panel_switch_funcs s6e3hc2_switch_funcs = { |
| .create = s6e3hc2_switch_create, |
| .destroy = s6e3hc2_switch_data_destroy, |
| .perform_switch = s6e3hc2_perform_switch, |
| .post_enable = s6e3hc2_post_enable, |
| .support_update_te2 = s6e3hc2_te2_update_reg_locked, |
| .support_update_hbm = s6e3hc2_update_hbm, |
| .send_nolp_cmds = s6e3hc2_send_nolp_cmds, |
| .support_cali_gamma_store = s6e3hc2_gamma_store, |
| }; |
| |
| static const struct of_device_id panel_switch_dt_match[] = { |
| { |
| .compatible = DSI_PANEL_GAMMA_NAME, |
| .data = &s6e3hc2_switch_funcs, |
| }, |
| { |
| .compatible = DSI_PANEL_SWITCH_NAME, |
| .data = &panel_switch_default_funcs, |
| }, |
| {}, |
| }; |
| |
| int dsi_panel_switch_init(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = NULL; |
| const struct panel_switch_funcs *funcs = NULL; |
| const struct of_device_id *match; |
| |
| match = of_match_node(panel_switch_dt_match, panel->panel_of_node); |
| if (match && match->data) |
| funcs = match->data; |
| |
| if (!funcs) { |
| pr_info("Panel switch is not supported\n"); |
| return 0; |
| } |
| |
| if (funcs->create) |
| pdata = funcs->create(panel); |
| |
| if (IS_ERR_OR_NULL(pdata)) |
| return -ENOENT; |
| |
| pdata->funcs = funcs; |
| |
| if (pdata->funcs->support_cali_gamma_store) { |
| if (sysfs_create_group(&panel->parent->kobj, |
| &gamma_group)) |
| pr_warn("unable to create gamma_group\n"); |
| } |
| |
| return 0; |
| } |
| |
| void dsi_panel_switch_put_mode(struct dsi_display_mode *mode) |
| { |
| if (likely(mode->priv_info)) { |
| kfree(mode->priv_info->switch_data); |
| mode->priv_info->switch_data = NULL; |
| } |
| } |
| |
| /* |
| * This should be called without panel_lock as flush/wait on worker can |
| * deadlock while holding it |
| */ |
| void dsi_panel_switch_destroy(struct dsi_panel *panel) |
| { |
| struct panel_switch_data *pdata = panel->private_data; |
| struct dsi_display_mode *mode; |
| int i; |
| |
| if (pdata->funcs->support_cali_gamma_store) |
| sysfs_remove_group(&pdata->panel->parent->kobj, |
| &gamma_group); |
| |
| for_each_display_mode(i, mode, pdata->panel) |
| dsi_panel_switch_put_mode(mode); |
| |
| if (pdata && pdata->funcs && pdata->funcs->destroy) |
| pdata->funcs->destroy(pdata); |
| } |