aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/omap2/displays/panel-s6e8aa0.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/omap2/displays/panel-s6e8aa0.c')
-rw-r--r--drivers/video/omap2/displays/panel-s6e8aa0.c1967
1 files changed, 1967 insertions, 0 deletions
diff --git a/drivers/video/omap2/displays/panel-s6e8aa0.c b/drivers/video/omap2/displays/panel-s6e8aa0.c
new file mode 100644
index 0000000..38f68cb
--- /dev/null
+++ b/drivers/video/omap2/displays/panel-s6e8aa0.c
@@ -0,0 +1,1967 @@
+/*
+ * Samsung s6e8aa0 panel support
+ *
+ * Copyright 2011 Google, Inc.
+ * Author: Erik Gilling <konkers@google.com>
+ *
+ * based on d2l panel driver by Jerry Alexander <x0135174@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+#include <linux/uaccess.h>
+
+
+#include <video/omapdss.h>
+
+#include <linux/platform_data/panel-s6e8aa0.h>
+
+/* DSI Command Virtual channel */
+#define CMD_VC_CHANNEL 1
+
+#define V1_ADJ_MAX 140
+#define V255_ADJ_MAX 430
+#define NUM_GAMMA_REGS 24
+#define NUM_DY_REGS (32)
+
+enum {
+ V1,
+ V15,
+ V35,
+ V59,
+ V87,
+ V171,
+ V255,
+ V_COUNT,
+};
+
+#define DRIVER_NAME "s6e8aa0_i2c"
+#define DEVICE_NAME "s6e8aa0_i2c"
+
+static int s6e8aa0_update(struct omap_dss_device *dssdev,
+ u16 x, u16 y, u16 w, u16 h);
+
+static struct omap_video_timings s6e8aa0_timings = {
+ .x_res = 720,
+ .y_res = 1280,
+ .pixel_clock = 80842,
+ .hfp = 158,
+ .hsw = 2,
+ .hbp = 160,
+ .vfp = 13,
+ .vsw = 1,
+ .vbp = 2,
+};
+
+static const struct s6e8aa0_gamma_adj_points default_gamma_adj_points = {
+ .v1 = BV_1,
+ .v15 = BV_15,
+ .v35 = BV_35,
+ .v59 = BV_59,
+ .v87 = BV_87,
+ .v171 = BV_171,
+};
+
+static u32 s6e8aa0_srgb_dyi_to_b[NUM_DY_REGS] = {
+ 0x0027c8ac, /* 2 */
+ 0x004f9159, /* 4 */
+ 0x00775a06, /* 6 */
+ 0x009f22b3, /* 8 */
+ 0x00f0f18e, /* 12 */
+ 0x0153936c, /* 16 */
+ 0x02569c13, /* 24 */
+ 0x03b2977b, /* 32 */
+ 0x060e496b, /* 42.5 */
+ 0x0803965b, /* 49.5 */
+ 0x0bf23e3b, /* 61 */
+ 0x10048613, /* 70.8 */
+ 0x14041743, /* 79 */
+ 0x180da7c6, /* 86.4 */
+ 0x1c05aec3, /* 93 */
+ 0x200e2a06, /* 99.2 */
+ 0x280bf0ad, /* 110.2 */
+ 0x301505aa, /* 120 */
+ 0x38108cb8, /* 128.9 */
+ 0x400a5f93, /* 137 */
+ 0x5008b39d, /* 151.7 */
+ 0x6010dbad, /* 164.8 */
+ 0x700a375a, /* 176.6 */
+ 0x801a9901, /* 187.6 */
+ 0x901075cd, /* 197.7 */
+ 0xa01b515c, /* 207.2 */
+ 0xb00de3e4, /* 216.1 */
+ 0xc01c269c, /* 224.7 */
+ 0xd01871ca, /* 232.8 */
+ 0xe010831c, /* 240.5 */
+ 0xf02ca6b8, /* 247.9 */
+ 0xffffffff, /* 255.1 */
+};
+
+struct s6e8aa0_gamma_reg_offsets {
+ s16 v[2][3][7];
+};
+
+struct s6e8aa0_data {
+ struct mutex lock;
+
+ struct omap_dss_device *dssdev;
+ struct backlight_device *bldev;
+ struct dentry *debug_dir;
+ bool enabled;
+ u8 rotate;
+ bool mirror;
+ bool use_dsi_bl;
+ unsigned int bl;
+ const struct s6e8aa0_gamma_adj_points *gamma_adj_points;
+ const u32 *dyi_to_b;
+ struct s6e8aa0_gamma_reg_offsets gamma_reg_offsets;
+ struct s6e8aa0_gamma_entry *brightness_table;
+ int brightness_table_size;
+ u32 brightness_limit[3];
+ unsigned long hw_guard_end; /* next value of jiffies when we can
+ * issue the next sleep in/out command
+ */
+ unsigned long hw_guard_wait; /* max guard time in jiffies */
+
+ atomic_t do_update;
+ struct {
+ u16 x;
+ u16 y;
+ u16 w;
+ u16 h;
+ } update_region;
+
+ bool cabc_broken;
+ unsigned cabc_mode;
+
+ bool force_update;
+ struct omap_video_timings *timings;
+
+ struct panel_s6e8aa0_data *pdata;
+
+ unsigned int acl_cur;
+ bool acl_enable;
+ u8 acl_average;
+ unsigned int elvss_cur_i;
+ u8 panel_id[3];
+};
+
+const u8 s6e8aa0_mtp_unlock[] = {
+ 0xF1,
+ 0x5A,
+ 0x5A,
+};
+
+const u8 s6e8aa0_mtp_lock[] = {
+ 0xF1,
+ 0xA5,
+ 0xA5,
+};
+
+static int s6e8aa0_write_reg(struct omap_dss_device *dssdev, u8 reg, u8 val)
+{
+ u8 buf[2];
+ buf[0] = reg;
+ buf[1] = val;
+
+ return dsi_vc_dcs_write(dssdev, 1, buf, 2);
+}
+
+static int s6e8aa0_write_block(struct omap_dss_device *dssdev, const u8 *data, int len)
+{
+ // XXX: dsi_vc_dsc_write should take a const u8 *
+ int ret;
+ msleep(10); // XxX: why do we have to wait
+
+ ret = dsi_vc_dcs_write(dssdev, 1, (u8 *)data, len);
+ msleep(10); // XxX: why do we have to wait
+ return ret;
+}
+
+static int s6e8aa0_write_block_nosync(struct omap_dss_device *dssdev,
+ const u8 *data, int len)
+{
+ return dsi_vc_dcs_write_nosync(dssdev, 1, (u8 *)data, len);
+}
+
+static int s6e8aa0_read_block(struct omap_dss_device *dssdev,
+ u8 cmd, u8 *data, int len)
+{
+ return dsi_vc_dcs_read(dssdev, 1, cmd, data, len);
+}
+
+static void s6e8aa0_write_sequence(struct omap_dss_device *dssdev,
+ const struct s6e8aa0_sequence_entry *seq, int seq_len)
+{
+ while (seq_len--) {
+ if (seq->cmd_len)
+ s6e8aa0_write_block(dssdev, seq->cmd, seq->cmd_len);
+ if (seq->msleep)
+ msleep(seq->msleep);
+ seq++;
+ }
+}
+
+/***********************
+*** DUMMY FUNCTIONS ****
+***********************/
+
+static int s6e8aa0_rotate(struct omap_dss_device *dssdev, u8 rotate)
+{
+ return 0;
+}
+
+static u8 s6e8aa0_get_rotate(struct omap_dss_device *dssdev)
+{
+ return 0;
+}
+
+static int s6e8aa0_mirror(struct omap_dss_device *dssdev, bool enable)
+{
+ return 0;
+}
+
+static bool s6e8aa0_get_mirror(struct omap_dss_device *dssdev)
+{
+ return 0;
+}
+
+static void s6e8aa0_get_timings(struct omap_dss_device *dssdev,
+ struct omap_video_timings *timings)
+{
+ *timings = dssdev->panel.timings;
+}
+
+static void s6e8aa0_set_timings(struct omap_dss_device *dssdev,
+ struct omap_video_timings *timings)
+{
+ dssdev->panel.timings.x_res = timings->x_res;
+ dssdev->panel.timings.y_res = timings->y_res;
+ dssdev->panel.timings.pixel_clock = timings->pixel_clock;
+ dssdev->panel.timings.hsw = timings->hsw;
+ dssdev->panel.timings.hfp = timings->hfp;
+ dssdev->panel.timings.hbp = timings->hbp;
+ dssdev->panel.timings.vsw = timings->vsw;
+ dssdev->panel.timings.vfp = timings->vfp;
+ dssdev->panel.timings.vbp = timings->vbp;
+}
+
+static int s6e8aa0_check_timings(struct omap_dss_device *dssdev,
+ struct omap_video_timings *timings)
+{
+ return 0;
+}
+
+static void s6e8aa0_get_resolution(struct omap_dss_device *dssdev,
+ u16 *xres, u16 *yres)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+
+ if (s6->rotate == 0 || s6->rotate == 2) {
+ *xres = dssdev->panel.timings.x_res;
+ *yres = dssdev->panel.timings.y_res;
+ } else {
+ *yres = dssdev->panel.timings.x_res;
+ *xres = dssdev->panel.timings.y_res;
+ }
+}
+
+static int s6e8aa0_enable_te(struct omap_dss_device *dssdev, bool enable)
+{
+ return 0;
+}
+
+static int s6e8aa0_hw_reset(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+
+ gpio_set_value(s6->pdata->reset_gpio, 0);
+ msleep(20);
+ gpio_set_value(s6->pdata->reset_gpio, 1);
+ msleep(40);
+
+ return 0;
+}
+
+static u32 s6e8aa0_table_lookup(u32 b, int c,
+ const struct s6e8aa0_gamma_entry *table,
+ int table_size)
+{
+ int i;
+ u32 ret;
+ u32 bl = 0;
+ u32 bh = 0;
+ u32 vl = 0;
+ u32 vh;
+ u64 tmp;
+
+ if (!table_size)
+ return b;
+
+ for (i = 0; i < table_size; i++) {
+ bl = bh;
+ bh = table[i].brightness;
+ if (bh >= b)
+ break;
+ }
+ vh = table[i].v[c];
+ if (i == 0 || (b - bl) == 0) {
+ ret = vl = vh;
+ } else {
+ vl = table[i - 1].v[c];
+ tmp = (u64)vh * (b - bl) + (u64)vl * (bh - b);
+ do_div(tmp, bh - bl);
+ ret = tmp;
+ }
+ pr_debug("%s: looking for c %d, %08x, "
+ "found %08x:%08x, v %7d:%7d, ret %7d\n",
+ __func__, c, b, bl, bh, vl, vh, ret);
+ return ret;
+}
+
+static u32 s6e8aa0_raw_gamma_lookup(struct s6e8aa0_data *s6, u32 b, int c)
+{
+ struct panel_s6e8aa0_data *pdata = s6->pdata;
+ return s6e8aa0_table_lookup(b, c, pdata->gamma_table,
+ pdata->gamma_table_size);
+}
+
+static u32 s6e8aa0_gamma_lookup(struct s6e8aa0_data *s6,
+ u8 brightness, u32 val, int c)
+{
+ u32 b;
+ u32 ret;
+ u64 tmp;
+
+ tmp = val;
+ tmp *= brightness;
+ do_div(tmp, 255);
+
+ b = s6e8aa0_table_lookup(tmp, c, s6->brightness_table,
+ s6->brightness_table_size);
+
+ ret = s6e8aa0_raw_gamma_lookup(s6, b, c);
+
+ pr_debug("%s: looking for %3d %08x c %d, %08x, got %7d\n",
+ __func__, brightness, val, c, b, ret);
+
+ return ret;
+}
+
+/*
+ * V1 = V0 - V0 * ( 5 + v1_adj ) / 600
+ * V15 = V1 - (V1 - V35) * ( 20 + v15_adj ) / 320
+ * V35 = V1 - (V1 - V59) * ( 65 + v35_adj ) / 320
+ * V59 = V1 - (V1 - V87) * ( 65 + v59_adj ) / 320
+ * V87 = V1 - (V1 - V171) * ( 65 + v87_adj ) / 320
+ * V171 = V1 - (V1 - V255) * ( 65 + v171_adj ) / 320
+ * V255 = V0 - V0 * ( 100 + v255_adj ) / 600
+ *
+ * v_n_adj = v_n_reg + v_n_offset
+ */
+
+static u32 v1adj_to_v1(u8 v1_adj, u32 v0)
+{
+ return DIV_ROUND_CLOSEST((600 - 5 - v1_adj) * v0, 600);
+}
+
+static u32 v1_to_v1adj(u32 v1, u32 v0)
+{
+ return 600 - 5 - DIV_ROUND_CLOSEST(600 * v1, v0);
+}
+
+static u32 vnadj_to_vn(int n, u8 v_n_adj, u32 v1, u32 v_next)
+{
+ int base = (n == V15) ? 20 : 65;
+ return v1 - DIV_ROUND_CLOSEST((v1 - v_next) * (base + v_n_adj), 320);
+}
+
+static u32 vn_to_vnadj(int n, u32 v_n, u32 v1, u32 v_next)
+{
+ int base = (n == V15) ? 20 : 65;
+ return DIV_ROUND_CLOSEST(320 * (v1 - v_n), v1 - v_next) - base;
+}
+
+static u32 v255adj_to_v255(u16 v255_adj, u32 v0)
+{
+ return DIV_ROUND_CLOSEST((600 - 100 - v255_adj) * v0, 600);
+}
+
+static u32 v255_to_v255adj(u32 v255, u32 v0)
+{
+ return 600 - 100 - DIV_ROUND_CLOSEST(600 * v255, v0);
+}
+
+static int gamma_reg_index(int c, int i)
+{
+ return 3 * i + c;
+}
+
+static int gamma_reg_index_v255_h(int c)
+{
+ return 3 * V255 + 2 * c;
+}
+
+static int gamma_reg_index_v255_l(int c)
+{
+ return gamma_reg_index_v255_h(c) + 1;
+}
+
+static void s6e8aa0_setup_dy_regs(struct s6e8aa0_data *s6, int c,
+ u32 v0, u32 v[V_COUNT], u8 dy[NUM_DY_REGS])
+{
+ static const struct {
+ int base;
+ u32 scale;
+ } output_table[256] = {
+ [0] = { },
+ [1] = { V1, },
+ [2] = { V15, 0x100000000ULL * 47 / 52, },
+ [3] = { V15, 0x100000000ULL * 42 / 52, },
+ [4] = { V15, 0x100000000ULL * 37 / 52, },
+ [5] = { V15, 0x100000000ULL * 32 / 52, },
+ [6] = { V15, 0x100000000ULL * 27 / 52, },
+ [7] = { V15, 0x100000000ULL * 23 / 52, },
+ [8] = { V15, 0x100000000ULL * 19 / 52, },
+ [9] = { V15, 0x100000000ULL * 15 / 52, },
+ [10] = { V15, 0x100000000ULL * 12 / 52, },
+ [11] = { V15, 0x100000000ULL * 9 / 52, },
+ [12] = { V15, 0x100000000ULL * 6 / 52, },
+ [13] = { V15, 0x100000000ULL * 4 / 52, },
+ [14] = { V15, 0x100000000ULL * 2 / 52, },
+ [15] = { V15, },
+ [16] = { V35, 0x100000000ULL * 66 / 70, },
+ [17] = { V35, 0x100000000ULL * 62 / 70, },
+ [18] = { V35, 0x100000000ULL * 58 / 70, },
+ [19] = { V35, 0x100000000ULL * 54 / 70, },
+ [20] = { V35, 0x100000000ULL * 50 / 70, },
+ [21] = { V35, 0x100000000ULL * 46 / 70, },
+ [22] = { V35, 0x100000000ULL * 42 / 70, },
+ [23] = { V35, 0x100000000ULL * 38 / 70, },
+ [24] = { V35, 0x100000000ULL * 34 / 70, },
+ [25] = { V35, 0x100000000ULL * 30 / 70, },
+ [26] = { V35, 0x100000000ULL * 27 / 70, },
+ [27] = { V35, 0x100000000ULL * 24 / 70, },
+ [28] = { V35, 0x100000000ULL * 21 / 70, },
+ [29] = { V35, 0x100000000ULL * 18 / 70, },
+ [30] = { V35, 0x100000000ULL * 15 / 70, },
+ [31] = { V35, 0x100000000ULL * 12 / 70, },
+ [32] = { V35, 0x100000000ULL * 9 / 70, },
+ [33] = { V35, 0x100000000ULL * 6 / 70, },
+ [34] = { V35, 0x100000000ULL * 3 / 70, },
+ [35] = { V35, },
+ [36] = { V59, 0x100000000ULL * 23 / 24, },
+ [37] = { V59, 0x100000000ULL * 22 / 24, },
+ [38] = { V59, 0x100000000ULL * 21 / 24, },
+ [39] = { V59, 0x100000000ULL * 20 / 24, },
+ [40] = { V59, 0x100000000ULL * 19 / 24, },
+ [41] = { V59, 0x100000000ULL * 18 / 24, },
+ [42] = { V59, 0x100000000ULL * 17 / 24, },
+ [43] = { V59, 0x100000000ULL * 16 / 24, },
+ [44] = { V59, 0x100000000ULL * 15 / 24, },
+ [45] = { V59, 0x100000000ULL * 14 / 24, },
+ [46] = { V59, 0x100000000ULL * 13 / 24, },
+ [47] = { V59, 0x100000000ULL * 12 / 24, },
+ [48] = { V59, 0x100000000ULL * 11 / 24, },
+ [49] = { V59, 0x100000000ULL * 10 / 24, },
+ [50] = { V59, 0x100000000ULL * 9 / 24, },
+ [51] = { V59, 0x100000000ULL * 8 / 24, },
+ [52] = { V59, 0x100000000ULL * 7 / 24, },
+ [53] = { V59, 0x100000000ULL * 6 / 24, },
+ [54] = { V59, 0x100000000ULL * 5 / 24, },
+ [55] = { V59, 0x100000000ULL * 4 / 24, },
+ [56] = { V59, 0x100000000ULL * 3 / 24, },
+ [57] = { V59, 0x100000000ULL * 2 / 24, },
+ [58] = { V59, 0x100000000ULL * 1 / 24, },
+ [59] = { V59, },
+ [60] = { V87, 0x100000000ULL * 27 / 28, },
+ [61] = { V87, 0x100000000ULL * 26 / 28, },
+ [62] = { V87, 0x100000000ULL * 25 / 28, },
+ [63] = { V87, 0x100000000ULL * 24 / 28, },
+ [64] = { V87, 0x100000000ULL * 23 / 28, },
+ [65] = { V87, 0x100000000ULL * 22 / 28, },
+ [66] = { V87, 0x100000000ULL * 21 / 28, },
+ [67] = { V87, 0x100000000ULL * 20 / 28, },
+ [68] = { V87, 0x100000000ULL * 19 / 28, },
+ [69] = { V87, 0x100000000ULL * 18 / 28, },
+ [70] = { V87, 0x100000000ULL * 17 / 28, },
+ [71] = { V87, 0x100000000ULL * 16 / 28, },
+ [72] = { V87, 0x100000000ULL * 15 / 28, },
+ [73] = { V87, 0x100000000ULL * 14 / 28, },
+ [74] = { V87, 0x100000000ULL * 13 / 28, },
+ [75] = { V87, 0x100000000ULL * 12 / 28, },
+ [76] = { V87, 0x100000000ULL * 11 / 28, },
+ [77] = { V87, 0x100000000ULL * 10 / 28, },
+ [78] = { V87, 0x100000000ULL * 9 / 28, },
+ [79] = { V87, 0x100000000ULL * 8 / 28, },
+ [80] = { V87, 0x100000000ULL * 7 / 28, },
+ [81] = { V87, 0x100000000ULL * 6 / 28, },
+ [82] = { V87, 0x100000000ULL * 5 / 28, },
+ [83] = { V87, 0x100000000ULL * 4 / 28, },
+ [84] = { V87, 0x100000000ULL * 3 / 28, },
+ [85] = { V87, 0x100000000ULL * 2 / 28, },
+ [86] = { V87, 0x100000000ULL * 1 / 28, },
+ [87] = { V87, },
+ [88] = { V171, 0x100000000ULL * 83 / 84, },
+ [89] = { V171, 0x100000000ULL * 82 / 84, },
+ [90] = { V171, 0x100000000ULL * 81 / 84, },
+ [91] = { V171, 0x100000000ULL * 80 / 84, },
+ [92] = { V171, 0x100000000ULL * 79 / 84, },
+ [93] = { V171, 0x100000000ULL * 78 / 84, },
+ [94] = { V171, 0x100000000ULL * 77 / 84, },
+ [95] = { V171, 0x100000000ULL * 76 / 84, },
+ [96] = { V171, 0x100000000ULL * 75 / 84, },
+ [97] = { V171, 0x100000000ULL * 74 / 84, },
+ [98] = { V171, 0x100000000ULL * 73 / 84, },
+ [99] = { V171, 0x100000000ULL * 72 / 84, },
+ [100] = { V171, 0x100000000ULL * 71 / 84, },
+ [101] = { V171, 0x100000000ULL * 70 / 84, },
+ [102] = { V171, 0x100000000ULL * 69 / 84, },
+ [103] = { V171, 0x100000000ULL * 68 / 84, },
+ [104] = { V171, 0x100000000ULL * 67 / 84, },
+ [105] = { V171, 0x100000000ULL * 66 / 84, },
+ [106] = { V171, 0x100000000ULL * 65 / 84, },
+ [107] = { V171, 0x100000000ULL * 64 / 84, },
+ [108] = { V171, 0x100000000ULL * 63 / 84, },
+ [109] = { V171, 0x100000000ULL * 62 / 84, },
+ [110] = { V171, 0x100000000ULL * 61 / 84, },
+ [111] = { V171, 0x100000000ULL * 60 / 84, },
+ [112] = { V171, 0x100000000ULL * 59 / 84, },
+ [113] = { V171, 0x100000000ULL * 58 / 84, },
+ [114] = { V171, 0x100000000ULL * 57 / 84, },
+ [115] = { V171, 0x100000000ULL * 56 / 84, },
+ [116] = { V171, 0x100000000ULL * 55 / 84, },
+ [117] = { V171, 0x100000000ULL * 54 / 84, },
+ [118] = { V171, 0x100000000ULL * 53 / 84, },
+ [119] = { V171, 0x100000000ULL * 52 / 84, },
+ [120] = { V171, 0x100000000ULL * 51 / 84, },
+ [121] = { V171, 0x100000000ULL * 50 / 84, },
+ [122] = { V171, 0x100000000ULL * 49 / 84, },
+ [123] = { V171, 0x100000000ULL * 48 / 84, },
+ [124] = { V171, 0x100000000ULL * 47 / 84, },
+ [125] = { V171, 0x100000000ULL * 46 / 84, },
+ [126] = { V171, 0x100000000ULL * 45 / 84, },
+ [127] = { V171, 0x100000000ULL * 44 / 84, },
+ [128] = { V171, 0x100000000ULL * 43 / 84, },
+ [129] = { V171, 0x100000000ULL * 42 / 84, },
+ [130] = { V171, 0x100000000ULL * 41 / 84, },
+ [131] = { V171, 0x100000000ULL * 40 / 84, },
+ [132] = { V171, 0x100000000ULL * 39 / 84, },
+ [133] = { V171, 0x100000000ULL * 38 / 84, },
+ [134] = { V171, 0x100000000ULL * 37 / 84, },
+ [135] = { V171, 0x100000000ULL * 36 / 84, },
+ [136] = { V171, 0x100000000ULL * 35 / 84, },
+ [137] = { V171, 0x100000000ULL * 34 / 84, },
+ [138] = { V171, 0x100000000ULL * 33 / 84, },
+ [139] = { V171, 0x100000000ULL * 32 / 84, },
+ [140] = { V171, 0x100000000ULL * 31 / 84, },
+ [141] = { V171, 0x100000000ULL * 30 / 84, },
+ [142] = { V171, 0x100000000ULL * 29 / 84, },
+ [143] = { V171, 0x100000000ULL * 28 / 84, },
+ [144] = { V171, 0x100000000ULL * 27 / 84, },
+ [145] = { V171, 0x100000000ULL * 26 / 84, },
+ [146] = { V171, 0x100000000ULL * 25 / 84, },
+ [147] = { V171, 0x100000000ULL * 24 / 84, },
+ [148] = { V171, 0x100000000ULL * 23 / 84, },
+ [149] = { V171, 0x100000000ULL * 22 / 84, },
+ [150] = { V171, 0x100000000ULL * 21 / 84, },
+ [151] = { V171, 0x100000000ULL * 20 / 84, },
+ [152] = { V171, 0x100000000ULL * 19 / 84, },
+ [153] = { V171, 0x100000000ULL * 18 / 84, },
+ [154] = { V171, 0x100000000ULL * 17 / 84, },
+ [155] = { V171, 0x100000000ULL * 16 / 84, },
+ [156] = { V171, 0x100000000ULL * 15 / 84, },
+ [157] = { V171, 0x100000000ULL * 14 / 84, },
+ [158] = { V171, 0x100000000ULL * 13 / 84, },
+ [159] = { V171, 0x100000000ULL * 12 / 84, },
+ [160] = { V171, 0x100000000ULL * 11 / 84, },
+ [161] = { V171, 0x100000000ULL * 10 / 84, },
+ [162] = { V171, 0x100000000ULL * 9 / 84, },
+ [163] = { V171, 0x100000000ULL * 8 / 84, },
+ [164] = { V171, 0x100000000ULL * 7 / 84, },
+ [165] = { V171, 0x100000000ULL * 6 / 84, },
+ [166] = { V171, 0x100000000ULL * 5 / 84, },
+ [167] = { V171, 0x100000000ULL * 4 / 84, },
+ [168] = { V171, 0x100000000ULL * 3 / 84, },
+ [169] = { V171, 0x100000000ULL * 2 / 84, },
+ [170] = { V171, 0x100000000ULL * 1 / 84, },
+ [171] = { V171, },
+ [172] = { V255, 0x100000000ULL * 83 / 84, },
+ [173] = { V255, 0x100000000ULL * 82 / 84, },
+ [174] = { V255, 0x100000000ULL * 81 / 84, },
+ [175] = { V255, 0x100000000ULL * 80 / 84, },
+ [176] = { V255, 0x100000000ULL * 79 / 84, },
+ [177] = { V255, 0x100000000ULL * 78 / 84, },
+ [178] = { V255, 0x100000000ULL * 77 / 84, },
+ [179] = { V255, 0x100000000ULL * 76 / 84, },
+ [180] = { V255, 0x100000000ULL * 75 / 84, },
+ [181] = { V255, 0x100000000ULL * 74 / 84, },
+ [182] = { V255, 0x100000000ULL * 73 / 84, },
+ [183] = { V255, 0x100000000ULL * 72 / 84, },
+ [184] = { V255, 0x100000000ULL * 71 / 84, },
+ [185] = { V255, 0x100000000ULL * 70 / 84, },
+ [186] = { V255, 0x100000000ULL * 69 / 84, },
+ [187] = { V255, 0x100000000ULL * 68 / 84, },
+ [188] = { V255, 0x100000000ULL * 67 / 84, },
+ [189] = { V255, 0x100000000ULL * 66 / 84, },
+ [190] = { V255, 0x100000000ULL * 65 / 84, },
+ [191] = { V255, 0x100000000ULL * 64 / 84, },
+ [192] = { V255, 0x100000000ULL * 63 / 84, },
+ [193] = { V255, 0x100000000ULL * 62 / 84, },
+ [194] = { V255, 0x100000000ULL * 61 / 84, },
+ [195] = { V255, 0x100000000ULL * 60 / 84, },
+ [196] = { V255, 0x100000000ULL * 59 / 84, },
+ [197] = { V255, 0x100000000ULL * 58 / 84, },
+ [198] = { V255, 0x100000000ULL * 57 / 84, },
+ [199] = { V255, 0x100000000ULL * 56 / 84, },
+ [200] = { V255, 0x100000000ULL * 55 / 84, },
+ [201] = { V255, 0x100000000ULL * 54 / 84, },
+ [202] = { V255, 0x100000000ULL * 53 / 84, },
+ [203] = { V255, 0x100000000ULL * 52 / 84, },
+ [204] = { V255, 0x100000000ULL * 51 / 84, },
+ [205] = { V255, 0x100000000ULL * 50 / 84, },
+ [206] = { V255, 0x100000000ULL * 49 / 84, },
+ [207] = { V255, 0x100000000ULL * 48 / 84, },
+ [208] = { V255, 0x100000000ULL * 47 / 84, },
+ [209] = { V255, 0x100000000ULL * 46 / 84, },
+ [210] = { V255, 0x100000000ULL * 45 / 84, },
+ [211] = { V255, 0x100000000ULL * 44 / 84, },
+ [212] = { V255, 0x100000000ULL * 43 / 84, },
+ [213] = { V255, 0x100000000ULL * 42 / 84, },
+ [214] = { V255, 0x100000000ULL * 41 / 84, },
+ [215] = { V255, 0x100000000ULL * 40 / 84, },
+ [216] = { V255, 0x100000000ULL * 39 / 84, },
+ [217] = { V255, 0x100000000ULL * 38 / 84, },
+ [218] = { V255, 0x100000000ULL * 37 / 84, },
+ [219] = { V255, 0x100000000ULL * 36 / 84, },
+ [220] = { V255, 0x100000000ULL * 35 / 84, },
+ [221] = { V255, 0x100000000ULL * 34 / 84, },
+ [222] = { V255, 0x100000000ULL * 33 / 84, },
+ [223] = { V255, 0x100000000ULL * 32 / 84, },
+ [224] = { V255, 0x100000000ULL * 31 / 84, },
+ [225] = { V255, 0x100000000ULL * 30 / 84, },
+ [226] = { V255, 0x100000000ULL * 29 / 84, },
+ [227] = { V255, 0x100000000ULL * 28 / 84, },
+ [228] = { V255, 0x100000000ULL * 27 / 84, },
+ [229] = { V255, 0x100000000ULL * 26 / 84, },
+ [230] = { V255, 0x100000000ULL * 25 / 84, },
+ [231] = { V255, 0x100000000ULL * 24 / 84, },
+ [232] = { V255, 0x100000000ULL * 23 / 84, },
+ [233] = { V255, 0x100000000ULL * 22 / 84, },
+ [234] = { V255, 0x100000000ULL * 21 / 84, },
+ [235] = { V255, 0x100000000ULL * 20 / 84, },
+ [236] = { V255, 0x100000000ULL * 19 / 84, },
+ [237] = { V255, 0x100000000ULL * 18 / 84, },
+ [238] = { V255, 0x100000000ULL * 17 / 84, },
+ [239] = { V255, 0x100000000ULL * 16 / 84, },
+ [240] = { V255, 0x100000000ULL * 15 / 84, },
+ [241] = { V255, 0x100000000ULL * 14 / 84, },
+ [242] = { V255, 0x100000000ULL * 13 / 84, },
+ [243] = { V255, 0x100000000ULL * 12 / 84, },
+ [244] = { V255, 0x100000000ULL * 11 / 84, },
+ [245] = { V255, 0x100000000ULL * 10 / 84, },
+ [246] = { V255, 0x100000000ULL * 9 / 84, },
+ [247] = { V255, 0x100000000ULL * 8 / 84, },
+ [248] = { V255, 0x100000000ULL * 7 / 84, },
+ [249] = { V255, 0x100000000ULL * 6 / 84, },
+ [250] = { V255, 0x100000000ULL * 5 / 84, },
+ [251] = { V255, 0x100000000ULL * 4 / 84, },
+ [252] = { V255, 0x100000000ULL * 3 / 84, },
+ [253] = { V255, 0x100000000ULL * 2 / 84, },
+ [254] = { V255, 0x100000000ULL * 1 / 84, },
+ [255] = { V255, },
+ };
+
+ u8 brightness = s6->bl;
+ int last_y = 0;
+ int i;
+ int j = 0;
+ u32 vh = v0;
+ u32 vl = vh;
+ u32 vt;
+ u32 vb;
+ u64 tmp;
+ int y;
+ u32 scale;
+
+ for (i = 0; i < NUM_DY_REGS; i++) {
+ vt = s6e8aa0_gamma_lookup(s6, brightness, s6->dyi_to_b[i], c);
+ while (vl > vt && j < ARRAY_SIZE(output_table) - 1) {
+ j++;
+ vh = vl;
+ vl = v[output_table[j].base];
+ scale = output_table[j].scale;
+ if (scale) {
+ vb = v[output_table[j].base - 1];
+ tmp = vb - vl;
+ tmp *= scale;
+ tmp >>= 32;
+ vl += tmp;
+ }
+ }
+ y = j * 4;
+ if (vh > vl && vt >= vl)
+ y -= DIV_ROUND_CLOSEST(4 * (vt - vl), vh - vl);
+ pr_debug("%s: dy%d %d, v %d (vh %d @ %d, vl %d @ %d)\n",
+ __func__, i, y, vt, vh, j * 4 - 4, vl, j * 4);
+ if (y < last_y)
+ y = last_y;
+ dy[i] = y - last_y;
+ last_y = y;
+ }
+}
+
+static void s6e8aa0_setup_gamma_regs(struct s6e8aa0_data *s6, u8 gamma_regs[],
+ u8 dy_regs[3][NUM_DY_REGS + 1])
+{
+ int c, i;
+ u8 brightness = s6->bl;
+ const struct s6e8aa0_gamma_adj_points *bv = s6->gamma_adj_points;
+
+ for (c = 0; c < 3; c++) {
+ u32 adj;
+ u32 adj_min;
+ u32 adj_max;
+ s16 offset;
+ u32 v0 = s6e8aa0_gamma_lookup(s6, brightness, BV_0, c);
+ u32 v[V_COUNT];
+
+ v[V1] = s6e8aa0_gamma_lookup(s6, brightness, bv->v1, c);
+ offset = s6->gamma_reg_offsets.v[1][c][V1];
+ adj_max = min(V1_ADJ_MAX, V1_ADJ_MAX - offset);
+ adj_min = max(0, 0 - offset);
+ adj = v1_to_v1adj(v[V1], v0) - offset;
+ if (adj < adj_min || adj > adj_max) {
+ pr_debug("%s: bad adj value %d, v0 %d, v1 %d, c %d\n",
+ __func__, adj, v0, v[V1], c);
+ adj = clamp_t(int, adj, adj_min, adj_max);
+ }
+ gamma_regs[gamma_reg_index(c, V1)] = adj;
+ v[V1] = v1adj_to_v1(adj + offset, v0);
+
+ v[V255] = s6e8aa0_gamma_lookup(s6, brightness, BV_255, c);
+ offset = s6->gamma_reg_offsets.v[1][c][V255];
+ adj_max = min(V255_ADJ_MAX, V255_ADJ_MAX - offset);
+ adj_min = max(0, 0 - offset);
+ adj = v255_to_v255adj(v[V255], v0) - offset;
+ if (adj < adj_min || adj > adj_max) {
+ pr_debug("%s: bad adj value %d, v0 %d, v255 %d, c %d\n",
+ __func__, adj, v0, v[V255], c);
+ adj = clamp_t(int, adj, adj_min, adj_max);
+ }
+ gamma_regs[3 * V255 + 2 * c] = adj >> 8;
+ gamma_regs[3 * V255 + 2 * c + 1] = (adj & 0xff);
+ gamma_regs[gamma_reg_index_v255_h(c)] = adj >> 8;
+ gamma_regs[gamma_reg_index_v255_l(c)] = adj;
+ v[V255] = v255adj_to_v255(adj + offset, v0);
+
+ v[V15] = s6e8aa0_gamma_lookup(s6, brightness, bv->v15, c);
+ v[V35] = s6e8aa0_gamma_lookup(s6, brightness, bv->v35, c);
+ v[V59] = s6e8aa0_gamma_lookup(s6, brightness, bv->v59, c);
+ v[V87] = s6e8aa0_gamma_lookup(s6, brightness, bv->v87, c);
+ v[V171] = s6e8aa0_gamma_lookup(s6, brightness, bv->v171, c);
+
+ for (i = V171; i >= V15; i--) {
+ offset = s6->gamma_reg_offsets.v[1][c][i];
+ adj_max = min(255, 255 - offset);
+ adj_min = max(0, 0 - offset);
+ if (v[V1] <= v[i + 1] || v[V1] <= v[i]) {
+ adj = -1;
+ } else {
+ adj = vn_to_vnadj(i, v[i], v[V1], v[i + 1]);
+ adj -= offset;
+ }
+ if (adj < adj_min || adj > adj_max) {
+ pr_debug("%s: bad adj value %d, "
+ "vh %d, v %d, c %d\n",
+ __func__, adj, v[i + 1], v[i], c);
+ adj = clamp_t(int, adj, adj_min, adj_max);
+ }
+ gamma_regs[gamma_reg_index(c, i)] = adj;
+ v[i] = vnadj_to_vn(i, adj + offset, v[V1], v[i + 1]);
+ }
+
+ s6e8aa0_setup_dy_regs(s6, c, v0, v, dy_regs[c] + 1);
+ }
+}
+
+static void s6e8aa0_update_acl_set(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ struct panel_s6e8aa0_data *pdata = s6->pdata;
+ int i;
+ unsigned int cd;
+ unsigned int max_cd = 0;
+ const struct s6e8aa0_acl_parameters *acl;
+
+ /* Quietly return if you don't have a table */
+ if (!pdata->acl_table_size)
+ return;
+
+ max_cd = pdata->acl_table[pdata->acl_table_size - 1].cd;
+
+ cd = s6->bl * max_cd / 255;
+ if (cd > max_cd)
+ cd = max_cd;
+
+ if (s6->acl_enable) {
+ for (i = 0; i < pdata->acl_table_size; i++)
+ if (cd <= pdata->acl_table[i].cd)
+ break;
+
+ if (i == pdata->acl_table_size)
+ i = pdata->acl_table_size - 1;
+
+ acl = &pdata->acl_table[i];
+ if (s6->acl_cur != acl->acl_val) {
+ s6e8aa0_write_block_nosync(dssdev, acl->regs,
+ sizeof(acl->regs));
+ s6e8aa0_write_reg(dssdev, 0xC0,
+ 0x01 | (s6->acl_average << 4)); /* ACL ON */
+
+ s6->acl_cur = acl->acl_val;
+ }
+ } else {
+ if (s6->acl_cur != 0) {
+ s6->acl_cur = 0;
+ s6e8aa0_write_reg(dssdev, 0xC0, 0x00); /* ACL OFF */
+ }
+ }
+ pr_debug("%s : cur_acl=%d, %d\n", __func__, s6->acl_cur,
+ s6->acl_enable);
+ return;
+}
+
+static void s6e8aa0_update_elvss(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ struct panel_s6e8aa0_data *pdata = s6->pdata;
+ u8 elvss_cmd[3];
+ u8 elvss;
+ u8 limit = 0x9F;
+ unsigned int i;
+ unsigned int cd;
+ unsigned int max_cd = 0;
+
+ if (!pdata->elvss_table_size)
+ return;
+
+ elvss_cmd[0] = 0xB1;
+ elvss_cmd[1] = 0x04;
+
+ max_cd = pdata->elvss_table[pdata->elvss_table_size - 1].cd;
+ cd = s6->bl * max_cd / 255;
+
+ for (i = 0; i < pdata->elvss_table_size - 1; i++)
+ if (cd <= pdata->elvss_table[i].cd)
+ break;
+
+ if (i == s6->elvss_cur_i)
+ return;
+
+ s6->elvss_cur_i = i;
+
+ elvss = s6->panel_id[2] & 0x1F; /* ELVSS Pulse 0-4bits */
+ elvss += pdata->elvss_table[i].elvss_val;
+
+ if (elvss > limit)
+ elvss = limit;
+
+ elvss_cmd[2] = elvss;
+
+ s6e8aa0_write_block(dssdev, elvss_cmd, sizeof(elvss_cmd));
+ pr_debug("%s - brightness : %d, cd : %d, elvss : %02x\n",
+ __func__, s6->bl, cd, elvss);
+ return;
+}
+
+static int s6e8aa0_update_brightness(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ u8 gamma_regs[NUM_GAMMA_REGS + 2];
+ u8 dy_regs[3][NUM_DY_REGS + 1];
+
+ gamma_regs[0] = 0xFA;
+ gamma_regs[1] = 0x01;
+ dy_regs[0][0] = 0xb8;
+ dy_regs[1][0] = 0xb9;
+ dy_regs[2][0] = 0xba;
+
+ s6e8aa0_setup_gamma_regs(s6, gamma_regs + 2, dy_regs);
+ s6e8aa0_write_block_nosync(dssdev, gamma_regs, sizeof(gamma_regs));
+ s6e8aa0_write_block_nosync(dssdev, dy_regs[0], sizeof(dy_regs[0]));
+ s6e8aa0_write_block_nosync(dssdev, dy_regs[1], sizeof(dy_regs[1]));
+ s6e8aa0_write_block_nosync(dssdev, dy_regs[2], sizeof(dy_regs[2]));
+ s6e8aa0_write_reg(dssdev, 0xF7, 0x01);
+
+ s6e8aa0_update_acl_set(dssdev);
+ s6e8aa0_update_elvss(dssdev);
+ return 0;
+}
+
+static u64 s6e8aa0_voltage_lookup(struct s6e8aa0_data *s6, int c, u32 v)
+{
+ int i;
+ u32 vh = ~0, vl = ~0;
+ u32 bl = 0, bh = 0;
+ u64 ret;
+ struct panel_s6e8aa0_data *pdata = s6->pdata;
+
+ for (i = 0; i < pdata->gamma_table_size; i++) {
+ vh = vl;
+ vl = pdata->gamma_table[i].v[c];
+ bh = bl;
+ bl = pdata->gamma_table[i].brightness;
+ if (vl <= v)
+ break;
+ }
+ if (i == 0 || (v - vl) == 0) {
+ ret = bl;
+ } else {
+ ret = (u64)bh * (s32)(v - vl) + (u64)bl * (vh - v);
+ do_div(ret, vh - vl);
+ }
+ pr_debug("%s: looking for %7d c %d, "
+ "found %7d:%7d, b %08x:%08x, ret %08llx\n",
+ __func__, v, c, vl, vh, bl, bh, ret);
+ return ret;
+}
+
+static u64 s6e8aa0_limit_brightness(u64 bc[3], u64 bcmax)
+{
+ int c;
+ int shift;
+
+ for (c = 0; c < 3; c++)
+ if (bc[c] > bcmax)
+ bcmax = bc[c];
+
+ if (bcmax != 0xffffffff) {
+ u64 tmp;
+ pr_warn("s6e8aa: factory calibration info is out of range: scale to 0x%llx\n",
+ bcmax);
+ shift = fls(bcmax >> 32);
+ tmp = (bcmax << (32 - shift)) - 1;
+ do_div(tmp, 0xffffffff);
+ tmp++;
+ pr_warn("s6e8aa: factory calibration info is out of range: scale to 0x%llx, shift %d\n",
+ tmp, shift);
+ for (c = 0; c < 3; c++) {
+ bc[c] <<= 32 - shift;
+ do_div(bc[c], tmp);
+ }
+ }
+ return bcmax;
+}
+
+static void s6e8aa0_apply_color_adj(
+ const struct s6e8aa0_factory_calibration_info *fi, u64 bc[3])
+{
+ int c;
+ int shift = fi->color_adj.rshift;
+
+ if (!shift)
+ return;
+
+ for (c = 0; c < 3; c++) {
+ u64 b = bc[c];
+ u32 bh = b >> 32;
+ u32 bl = b;
+ u64 m = fi->color_adj.mult[c];
+ /*
+ * Calculate ((b * m) >> shift).
+ * If b is greater than 2^32, The 64 by 32 to 64 bit (b * m)
+ * multiplication can overflow, even if the end result fits,
+ * so we split it into two 32 by 32 to 64 bit operations.
+ */
+ bc[c] = ((bh * m) << (32 - shift)) + ((bl * m) >> shift);
+ }
+}
+
+static int s6e8aa0_cmp_gamma_entry(const void *pa, const void *pb)
+{
+ u32 a = ((const struct s6e8aa0_gamma_entry *)pa)->brightness;
+ u32 b = ((const struct s6e8aa0_gamma_entry *)pb)->brightness;
+ if (a > b)
+ return 1;
+ if (a < b)
+ return -1;
+ return 0;
+}
+
+static void s6e8aa0_adjust_brightness_from_mtp(struct s6e8aa0_data *s6)
+{
+ int b, c, i;
+ u32 v[2][3][V_COUNT];
+ u64 bc[3];
+ u64 bcmax;
+ struct panel_s6e8aa0_data *pdata = s6->pdata;
+ const struct s6e8aa0_gamma_reg_offsets *offset = &s6->gamma_reg_offsets;
+ struct s6e8aa0_factory_calibration_info *fi = pdata->factory_info;
+ struct s6e8aa0_gamma_entry *brightness_table;
+ int brightness_table_size = 1;
+
+ for (b = 0; b < 2; b++)
+ for (i = 0; i < V_COUNT; i++)
+ if (fi->brightness[b][i])
+ brightness_table_size++;
+
+ brightness_table = kmalloc(sizeof(*brightness_table) *
+ brightness_table_size, GFP_KERNEL);
+ if (!brightness_table) {
+ dev_err(&s6->dssdev->dev,
+ "Failed to allocate brightness table\n");
+ return;
+ }
+ brightness_table->brightness = 0;
+ for (c = 0; c < 3; c++)
+ brightness_table->v[c] = 0;
+ s6->brightness_table = brightness_table;
+ s6->brightness_table_size = brightness_table_size;
+ brightness_table++;
+
+ for (b = 0; b < 2; b++) {
+ for (c = 0; c < 3; c++) {
+ u32 v0 = s6e8aa0_raw_gamma_lookup(s6, BV_0, c);
+ v[b][c][V1] = v1adj_to_v1(fi->regs[b][c][V1] +
+ offset->v[b][c][V1], v0);
+ v[b][c][V255] = v255adj_to_v255(fi->regs[b][c][V255] +
+ offset->v[b][c][V255], v0);
+ for (i = V171; i >= V15; i--)
+ v[b][c][i] = vnadj_to_vn(i, fi->regs[b][c][i] +
+ offset->v[b][c][i],
+ v[b][c][V1], v[b][c][i + 1]);
+ }
+ }
+
+ for (b = 0; b < 2; b++)
+ for (i = 0; i < V_COUNT; i++)
+ pr_debug("%s: b %d, p %d, R %7dv, G %7dv, B %7dv\n",
+ __func__, b, i,
+ v[b][0][i], v[b][1][i], v[b][2][i]);
+
+ bcmax = 0xffffffff;
+ for (b = 0; b < 2; b++) {
+ for (i = 0; i < V_COUNT; i++) {
+ if (!fi->brightness[b][i])
+ continue;
+
+ for (c = 0; c < 3; c++)
+ bc[c] = s6e8aa0_voltage_lookup(s6, c,
+ v[b][c][i]);
+
+ s6e8aa0_apply_color_adj(fi, bc);
+ bcmax = s6e8aa0_limit_brightness(bc, bcmax);
+ }
+ }
+
+ for (b = 0; b < 2; b++) {
+ for (i = 0; i < V_COUNT; i++) {
+ if (!fi->brightness[b][i])
+ continue;
+
+ for (c = 0; c < 3; c++) {
+ bc[c] = s6e8aa0_voltage_lookup(s6, c,
+ v[b][c][i]);
+ pr_debug("s6e8aa: c%d, %d, b-%08llx, before scaling\n",
+ c, i, bc[c]);
+ }
+
+ s6e8aa0_apply_color_adj(fi, bc);
+ for (c = 0; c < 3; c++) {
+ pr_debug("s6e8aa: c%d, %d, b-%08llx, after color adj\n",
+ c, i, bc[c]);
+ }
+
+ s6e8aa0_limit_brightness(bc, bcmax);
+
+ brightness_table->brightness = fi->brightness[b][i];
+ pr_info("s6e8aa: d/b %d, p %d, b-%08x\n",
+ b, i, fi->brightness[b][i]);
+ for (c = 0; c < 3; c++) {
+ if (bc[c] > s6->brightness_limit[c])
+ s6->brightness_limit[c] = bc[c];
+ brightness_table->v[c] = bc[c];
+ pr_info("s6e8aa: c%d, %d, b-%08llx, got v %d, factory wants %d\n",
+ c, i, bc[c],
+ s6e8aa0_raw_gamma_lookup(s6, bc[c], c),
+ v[b][c][i]);
+ }
+ brightness_table++;
+ }
+ }
+ sort(s6->brightness_table + 1, s6->brightness_table_size - 1,
+ sizeof(*s6->brightness_table), s6e8aa0_cmp_gamma_entry, NULL);
+}
+
+static s16 s9_to_s16(s16 v)
+{
+ return (s16)(v << 7) >> 7;
+}
+
+static int mtp_reg_index(int c, int i)
+{
+ return c * (V_COUNT + 1) + i;
+}
+
+static void s6e8aa0_read_id_info(struct s6e8aa0_data *s6)
+{
+ struct omap_dss_device *dssdev = s6->dssdev;
+ int ret;
+ u8 cmd = 0xD1;
+
+ dsi_vc_set_max_rx_packet_size(dssdev, 1, 3);
+ ret = s6e8aa0_read_block(dssdev, cmd, s6->panel_id,
+ ARRAY_SIZE(s6->panel_id));
+ dsi_vc_set_max_rx_packet_size(dssdev, 1, 1);
+ if (ret < 0) {
+ pr_err("%s: Failed to read id data\n", __func__);
+ return;
+ }
+}
+
+static void s6e8aa0_read_mtp_info(struct s6e8aa0_data *s6, int b)
+{
+ int ret;
+ int c, i;
+ u8 mtp_data[24];
+ u8 cmd = b ? 0xD3 : 0xD4;
+ struct omap_dss_device *dssdev = s6->dssdev;
+
+ s6e8aa0_write_block(dssdev, s6e8aa0_mtp_unlock,
+ ARRAY_SIZE(s6e8aa0_mtp_unlock));
+ dsi_vc_set_max_rx_packet_size(dssdev, 1, 24);
+ ret = s6e8aa0_read_block(dssdev, cmd, mtp_data, ARRAY_SIZE(mtp_data));
+ dsi_vc_set_max_rx_packet_size(dssdev, 1, 1);
+ s6e8aa0_write_block(dssdev, s6e8aa0_mtp_lock,
+ ARRAY_SIZE(s6e8aa0_mtp_lock));
+ if (ret < 0) {
+ pr_err("%s: Failed to read mtp data\n", __func__);
+ return;
+ }
+ for (c = 0; c < 3; c++) {
+ for (i = 0; i < V255; i++)
+ s6->gamma_reg_offsets.v[b][c][i] =
+ (s8)mtp_data[mtp_reg_index(c, i)];
+
+ s6->gamma_reg_offsets.v[b][c][V255] =
+ s9_to_s16(mtp_data[mtp_reg_index(c, V255)] << 8 |
+ mtp_data[mtp_reg_index(c, V255 + 1)]);
+ }
+}
+
+static int s6e8aa0_set_brightness(struct backlight_device *bd)
+{
+ struct omap_dss_device *dssdev = dev_get_drvdata(&bd->dev);
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ int bl = bd->props.brightness;
+ int ret = 0;
+
+ if (bl == s6->bl)
+ return 0;
+
+ s6->bl = bl;
+ mutex_lock(&s6->lock);
+ if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) {
+ dsi_bus_lock(dssdev);
+ ret = s6e8aa0_update_brightness(dssdev);
+ dsi_bus_unlock(dssdev);
+ }
+ mutex_unlock(&s6->lock);
+ return ret;
+}
+
+static int s6e8aa0_get_brightness(struct backlight_device *bd)
+{
+ return bd->props.brightness;
+}
+
+static const struct backlight_ops s6e8aa0_backlight_ops = {
+ .get_brightness = s6e8aa0_get_brightness,
+ .update_status = s6e8aa0_set_brightness,
+};
+
+static void seq_print_gamma_regs(struct seq_file *m, const u8 gamma_regs[])
+{
+ struct s6e8aa0_data *s6 = m->private;
+ int c, i;
+ const int adj_points[] = { 1, 15, 35, 59, 87, 171, 255 };
+ const char color[] = { 'R', 'G', 'B' };
+ u8 brightness = s6->bl;
+ const struct s6e8aa0_gamma_adj_points *bv = s6->gamma_adj_points;
+ const struct s6e8aa0_gamma_reg_offsets *offset = &s6->gamma_reg_offsets;
+
+ for (c = 0; c < 3; c++) {
+ u32 adj[V_COUNT];
+ u32 vt[V_COUNT];
+ u32 v[V_COUNT];
+ u32 v0 = s6e8aa0_gamma_lookup(s6, brightness, BV_0, c);
+
+ vt[V1] = s6e8aa0_gamma_lookup(s6, brightness, bv->v1, c);
+ vt[V15] = s6e8aa0_gamma_lookup(s6, brightness, bv->v15, c);
+ vt[V35] = s6e8aa0_gamma_lookup(s6, brightness, bv->v35, c);
+ vt[V59] = s6e8aa0_gamma_lookup(s6, brightness, bv->v59, c);
+ vt[V87] = s6e8aa0_gamma_lookup(s6, brightness, bv->v87, c);
+ vt[V171] = s6e8aa0_gamma_lookup(s6, brightness, bv->v171, c);
+ vt[V255] = s6e8aa0_gamma_lookup(s6, brightness, BV_255, c);
+
+ adj[V1] = gamma_regs[gamma_reg_index(c, V1)];
+ v[V1] = v1adj_to_v1(adj[V1] + offset->v[1][c][V1], v0);
+
+ adj[V255] = gamma_regs[gamma_reg_index_v255_h(c)] << 8 |
+ gamma_regs[gamma_reg_index_v255_l(c)];
+ v[V255] = v255adj_to_v255(adj[V255] + offset->v[1][c][V255],
+ v0);
+
+ for (i = V171; i >= V15; i--) {
+ adj[i] = gamma_regs[gamma_reg_index(c, i)];
+ v[i] = vnadj_to_vn(i, adj[i] + offset->v[1][c][i],
+ v[V1], v[i + 1]);
+ }
+ seq_printf(m, "%c v0 %7d\n",
+ color[c], v0);
+ for (i = 0; i < V_COUNT; i++) {
+ seq_printf(m, "%c adj %3d (%02x) %+4d "
+ "v%-3d %7d - %7d %+8d\n",
+ color[c], adj[i], adj[i], offset->v[1][c][i],
+ adj_points[i], v[i], vt[i], v[i] - vt[i]);
+ }
+ }
+}
+
+static void seq_print_dy_regs(struct seq_file *m,
+ u8 dy_regs[3][NUM_DY_REGS + 1])
+{
+ int i, c;
+ u16 y[3] = {};
+ seq_printf(m, " R y (rv) G y (rv) B y (rv)\n");
+ for (i = 0; i < NUM_DY_REGS; i++) {
+ seq_printf(m, "%-2d:", i);
+ for (c = 0; c < 3; c++) {
+ y[c] += dy_regs[c][i + 1];
+ seq_printf(m, " %4d (%02x)", y[c], dy_regs[c][i + 1]);
+ }
+ seq_printf(m, "\n");
+ }
+}
+
+static int s6e8aa0_current_gamma_show(struct seq_file *m, void *unused)
+{
+ struct s6e8aa0_data *s6 = m->private;
+ u8 gamma_regs[NUM_GAMMA_REGS];
+ u8 dy_regs[3][NUM_DY_REGS + 1];
+
+ mutex_lock(&s6->lock);
+ s6e8aa0_setup_gamma_regs(s6, gamma_regs, dy_regs);
+ seq_printf(m, "brightness %3d:\n", s6->bl);
+ seq_print_gamma_regs(m, gamma_regs);
+ seq_printf(m, "\n");
+ seq_print_dy_regs(m, dy_regs);
+ mutex_unlock(&s6->lock);
+ return 0;
+}
+
+static int s6e8aa0_current_gamma_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, s6e8aa0_current_gamma_show, inode->i_private);
+}
+
+static int s6e8aa0_gamma_correction_show(struct seq_file *m, void *unused)
+{
+ struct s6e8aa0_data *s6 = m->private;
+ const struct s6e8aa0_gamma_entry *bte;
+ int n, c;
+
+ mutex_lock(&s6->lock);
+ n = s6->brightness_table_size;
+ bte = s6->brightness_table;
+ while (n--) {
+ seq_printf(m, "0x%08x", bte->brightness);
+ for (c = 0; c < 3; c++)
+ seq_printf(m, " 0x%08x", bte->v[c]);
+ seq_printf(m, "\n");
+ bte++;
+ }
+ seq_printf(m, "\n");
+ seq_printf(m, "0x%08x", BV_255);
+ for (c = 0; c < 3; c++)
+ seq_printf(m, " 0x%08x", s6->brightness_limit[c]);
+ seq_printf(m, "\n");
+ mutex_unlock(&s6->lock);
+ return 0;
+}
+
+static int s6e8aa0_gamma_correction_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, s6e8aa0_gamma_correction_show,
+ inode->i_private);
+}
+
+static ssize_t s6e8aa0_gamma_correction_write(struct file *file,
+ const char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ struct seq_file *m = file->private_data;
+ struct s6e8aa0_data *s6 = m->private;
+ struct omap_dss_device *dssdev = s6->dssdev;
+ char sbuf[80];
+ u32 val[4] = {
+ };
+ u32 last_val[4];
+ struct s6e8aa0_gamma_entry *bt = NULL;
+ struct s6e8aa0_gamma_entry *new_bt;
+ int bt_size = 0;
+
+ int ret;
+ size_t used;
+ size_t sbuf_len = sizeof(sbuf) - 1;
+ size_t rem = size;
+ int c;
+ int i;
+
+ while (rem && val[0] != BV_255) {
+ if (sbuf_len > rem)
+ sbuf_len = rem;
+ if (copy_from_user(sbuf, buf, sbuf_len)) {
+ ret = -EFAULT;
+ goto err;
+ }
+ sbuf[sbuf_len] = '\0';
+
+ for (i = 0; i < ARRAY_SIZE(val); i++)
+ last_val[i] = val[i];
+ ret = sscanf(sbuf, "%i %i %i %i\n%n",
+ &val[0], &val[1], &val[2], &val[3], &used);
+ if (ret < 4 || !used)
+ break;
+
+ buf += used;
+ rem -= used;
+
+ if (!bt_size) {
+ for (i = 0; i < ARRAY_SIZE(val); i++) {
+ if (val[i] != 0) {
+ pr_info("%s: invalid start value %d: "
+ "0x%08x != 0\n",
+ __func__, i, val[i]);
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(val); i++) {
+ if (val[i] <= last_val[i]) {
+ pr_info("%s: invalid value %d: "
+ "0x%08x <= 0x%08x\n", __func__,
+ i, val[i], last_val[i]);
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ for (c = 0; c < 3; c++) {
+ if (val[c + 1] > s6->brightness_limit[c]) {
+ pr_info("%s: invalid value %d: "
+ "0x%08x > 0x%08x\n", __func__,
+ c, val[c + 1],
+ s6->brightness_limit[c]);
+ ret = -EOVERFLOW;
+ goto err;
+ }
+ }
+ }
+
+ new_bt = krealloc(bt, (bt_size + 1) * sizeof(*bt), GFP_KERNEL);
+ if (!new_bt) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ bt = new_bt;
+ bt[bt_size].brightness = val[0];
+ for (c = 0; c < 3; c++)
+ bt[bt_size].v[c] = val[c + 1];
+ bt_size++;
+ }
+ if (val[0] != BV_255) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = size - rem;
+ mutex_lock(&s6->lock);
+ pr_debug("%s: got new brightness_table size %d\n", __func__, bt_size);
+ swap(bt, s6->brightness_table);
+ s6->brightness_table_size = bt_size;
+
+ if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) {
+ dsi_bus_lock(dssdev);
+ s6e8aa0_update_brightness(dssdev);
+ dsi_bus_unlock(dssdev);
+ }
+ mutex_unlock(&s6->lock);
+
+err:
+ kfree(bt);
+ return ret;
+}
+
+static ssize_t acl_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omap_dss_device *dssdev = dev_get_drvdata(dev);
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+
+ snprintf(buf, PAGE_SIZE, "%d\n", s6->acl_enable);
+
+ return strlen(buf);
+}
+
+static ssize_t acl_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct omap_dss_device *dssdev = dev_get_drvdata(dev);
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ long value;
+ bool enable;
+ int rc;
+
+ rc = strict_strtol(buf, 0, &value);
+
+ if (rc < 0)
+ return rc;
+
+ enable = value;
+
+ mutex_lock(&s6->lock);
+ if (s6->acl_enable != enable) {
+ dsi_bus_lock(dssdev);
+
+ s6->acl_enable = enable;
+ s6e8aa0_update_acl_set(dssdev);
+
+ dsi_bus_unlock(dssdev);
+ }
+ mutex_unlock(&s6->lock);
+ return size;
+}
+
+static DEVICE_ATTR(acl_set, S_IRUGO|S_IWUSR,
+ acl_enable_show, acl_enable_store);
+
+
+static ssize_t acl_average_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omap_dss_device *dssdev = dev_get_drvdata(dev);
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+
+ snprintf(buf, PAGE_SIZE, "%d\n", s6->acl_average);
+
+ return strlen(buf);
+}
+
+static ssize_t acl_average_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct omap_dss_device *dssdev = dev_get_drvdata(dev);
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ long value;
+ int rc;
+
+ rc = strict_strtol(buf, 0, &value);
+
+ if (rc < 0)
+ return rc;
+
+ if (value < 0 || value > 7)
+ return -EINVAL;
+
+ mutex_lock(&s6->lock);
+ if (s6->acl_average != value) {
+ dsi_bus_lock(dssdev);
+
+ s6->acl_average = value;
+ s6->acl_cur = 0;
+ s6e8aa0_update_acl_set(dssdev);
+
+ dsi_bus_unlock(dssdev);
+ }
+ mutex_unlock(&s6->lock);
+ return size;
+}
+
+static DEVICE_ATTR(acl_average, S_IRUGO|S_IWUSR,
+ acl_average_show, acl_average_store);
+
+static struct attribute *s6e8aa0_bl_attributes[] = {
+ &dev_attr_acl_set.attr,
+ &dev_attr_acl_average.attr,
+ NULL
+};
+
+static const struct attribute_group s6e8aa0_bl_attr_group = {
+ .attrs = s6e8aa0_bl_attributes,
+};
+
+static const struct file_operations s6e8aa0_current_gamma_fops = {
+ .open = s6e8aa0_current_gamma_open,
+ .read = seq_read,
+ .release = single_release,
+};
+
+static const struct file_operations s6e8aa0_gamma_correction_fops = {
+ .open = s6e8aa0_gamma_correction_open,
+ .read = seq_read,
+ .write = s6e8aa0_gamma_correction_write,
+ .release = single_release,
+};
+
+static int s6e8aa0_probe(struct omap_dss_device *dssdev)
+{
+ int ret = 0;
+ struct backlight_properties props = {
+ .brightness = 255,
+ .max_brightness = 255,
+ .type = BACKLIGHT_RAW,
+ };
+ struct s6e8aa0_data *s6 = NULL;
+
+ dev_dbg(&dssdev->dev, "s6e8aa0_probe\n");
+
+ if (dssdev->data == NULL) {
+ dev_err(&dssdev->dev, "no platform data!\n");
+ return -EINVAL;
+ }
+
+ dssdev->panel.config = OMAP_DSS_LCD_TFT;
+ dssdev->panel.timings = s6e8aa0_timings;
+
+ dssdev->ctrl.pixel_size = 24;
+ dssdev->panel.acbi = 0;
+ dssdev->panel.acb = 40;
+
+ s6 = kzalloc(sizeof(*s6), GFP_KERNEL);
+ if (!s6)
+ return -ENOMEM;
+
+ s6->dssdev = dssdev;
+ s6->pdata = dssdev->data;
+
+ s6->bl = props.brightness;
+
+ if (!s6->pdata->seq_display_set || !s6->pdata->seq_etc_set
+ || !s6->pdata->gamma_table) {
+ dev_err(&dssdev->dev, "Invalid platform data\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ s6->gamma_adj_points =
+ s6->pdata->gamma_adj_points ?: &default_gamma_adj_points;
+ s6->dyi_to_b = s6e8aa0_srgb_dyi_to_b;
+
+ ret = gpio_request(s6->pdata->reset_gpio, "s6e8aa0_reset");
+ if (ret < 0) {
+ dev_err(&dssdev->dev, "gpio_request %d failed!\n", s6->pdata->reset_gpio);
+ goto err;
+ }
+ gpio_direction_output(s6->pdata->reset_gpio, 1);
+
+ mutex_init(&s6->lock);
+
+ atomic_set(&s6->do_update, 0);
+
+ dev_set_drvdata(&dssdev->dev, s6);
+
+ /* Register DSI backlight control */
+ s6->bldev = backlight_device_register("s6e8aa0", &dssdev->dev, dssdev,
+ &s6e8aa0_backlight_ops, &props);
+ if (IS_ERR(s6->bldev)) {
+ ret = PTR_ERR(s6->bldev);
+ goto err_backlight_device_register;
+ }
+
+ s6->debug_dir = debugfs_create_dir("s6e8aa0", NULL);
+ if (!s6->debug_dir) {
+ dev_err(&dssdev->dev, "failed to create debug dir\n");
+ } else {
+ debugfs_create_file("current_gamma", S_IRUGO,
+ s6->debug_dir, s6, &s6e8aa0_current_gamma_fops);
+ debugfs_create_file("gamma_correction", S_IRUGO | S_IWUSR,
+ s6->debug_dir, s6, &s6e8aa0_gamma_correction_fops);
+ }
+
+ s6->acl_enable = true;
+ s6->acl_cur = 0;
+ s6->acl_average = s6->pdata->acl_average;
+ s6->elvss_cur_i = ~0;
+
+ ret = sysfs_create_group(&s6->bldev->dev.kobj, &s6e8aa0_bl_attr_group);
+ if (ret < 0) {
+ dev_err(&dssdev->dev, "failed to add sysfs entries\n");
+ goto err_backlight_device_register;
+ }
+
+ if (cpu_is_omap44xx())
+ s6->force_update = true;
+
+ dev_dbg(&dssdev->dev, "s6e8aa0_probe\n");
+ return ret;
+
+err_backlight_device_register:
+ mutex_destroy(&s6->lock);
+ gpio_free(s6->pdata->reset_gpio);
+err:
+ kfree(s6);
+
+ return ret;
+}
+
+static void s6e8aa0_remove(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ sysfs_remove_group(&s6->bldev->dev.kobj, &s6e8aa0_bl_attr_group);
+ debugfs_remove_recursive(s6->debug_dir);
+ backlight_device_unregister(s6->bldev);
+ mutex_destroy(&s6->lock);
+ gpio_free(s6->pdata->reset_gpio);
+ kfree(s6);
+}
+
+/**
+ * s6e8aa0_config - Configure S6E8AA0
+ *
+ * Initial configuration for S6E8AA0 configuration registers, PLL...
+ */
+static void s6e8aa0_config(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ struct panel_s6e8aa0_data *pdata = s6->pdata;
+ if (!s6->brightness_table) {
+ s6e8aa0_read_id_info(s6);
+ s6e8aa0_read_mtp_info(s6, 0);
+ s6e8aa0_read_mtp_info(s6, 1);
+ s6e8aa0_adjust_brightness_from_mtp(s6);
+ }
+
+ s6e8aa0_write_sequence(dssdev, pdata->seq_display_set,
+ pdata->seq_display_set_size);
+
+ s6->acl_cur = 0; /* make sure acl table and elvss value gets written */
+ s6->elvss_cur_i = ~0;
+ s6e8aa0_update_brightness(dssdev);
+
+ s6e8aa0_write_sequence(dssdev, pdata->seq_etc_set,
+ pdata->seq_etc_set_size);
+}
+
+static int s6e8aa0_power_on(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ int ret = 0;
+
+ /* At power on the first vsync has not been received yet*/
+ dssdev->first_vsync = false;
+
+ if (s6->enabled != 1) {
+ if (s6->pdata->set_power)
+ s6->pdata->set_power(true);
+
+ ret = omapdss_dsi_display_enable(dssdev);
+ if (ret) {
+ dev_err(&dssdev->dev, "failed to enable DSI\n");
+ goto err;
+ }
+
+ /* reset s6e8aa0 bridge */
+ if(!dssdev->skip_init){
+ s6e8aa0_hw_reset(dssdev);
+
+ /* XXX */
+ msleep(100);
+ s6e8aa0_config(dssdev);
+
+ dsi_video_mode_enable(dssdev, 0x3E); /* DSI_DT_PXLSTREAM_24BPP_PACKED; */
+ }
+
+ s6->enabled = 1;
+ }
+
+ if(dssdev->skip_init)
+ dssdev->skip_init = false;
+
+err:
+ return ret;
+}
+
+static void s6e8aa0_power_off(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+
+ gpio_set_value(s6->pdata->reset_gpio, 0);
+ msleep(10);
+
+ s6->enabled = 0;
+ omapdss_dsi_display_disable(dssdev, 0, 0);
+
+ if (s6->pdata->set_power)
+ s6->pdata->set_power(false);
+
+}
+
+static int s6e8aa0_start(struct omap_dss_device *dssdev)
+{
+ int r = 0;
+
+ dsi_bus_lock(dssdev);
+
+ r = s6e8aa0_power_on(dssdev);
+
+ dsi_bus_unlock(dssdev);
+
+ if (r) {
+ dev_dbg(&dssdev->dev, "enable failed\n");
+ dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
+ } else {
+ dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
+ dssdev->manager->enable(dssdev->manager);
+ }
+
+ return r;
+}
+
+static void s6e8aa0_stop(struct omap_dss_device *dssdev)
+{
+ dssdev->manager->disable(dssdev->manager);
+
+ dsi_bus_lock(dssdev);
+
+ s6e8aa0_power_off(dssdev);
+
+ dsi_bus_unlock(dssdev);
+}
+
+static void s6e8aa0_disable(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+
+ dev_dbg(&dssdev->dev, "disable\n");
+
+ mutex_lock(&s6->lock);
+ if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
+ s6e8aa0_stop(dssdev);
+
+ dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
+ mutex_unlock(&s6->lock);
+}
+
+static int s6e8aa0_enable(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ int ret;
+
+ dev_dbg(&dssdev->dev, "enable\n");
+
+ mutex_lock(&s6->lock);
+ if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = s6e8aa0_start(dssdev);
+out:
+ mutex_unlock(&s6->lock);
+ return ret;
+}
+
+static void s6e8aa0_framedone_cb(int err, void *data)
+{
+ struct omap_dss_device *dssdev = data;
+ dev_dbg(&dssdev->dev, "framedone, err %d\n", err);
+ dsi_bus_unlock(dssdev);
+}
+
+static int s6e8aa0_update(struct omap_dss_device *dssdev,
+ u16 x, u16 y, u16 w, u16 h)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ int r;
+ dev_dbg(&dssdev->dev, "update %d, %d, %d x %d\n", x, y, w, h);
+
+ mutex_lock(&s6->lock);
+
+ dsi_bus_lock(dssdev);
+
+ if (!s6->enabled) {
+ r = 0;
+ goto err;
+ }
+
+ r = omap_dsi_prepare_update(dssdev, &x, &y, &w, &h, true);
+ if (r)
+ goto err;
+
+ /* We use VC(0) for VideoPort Data and VC(1) for commands */
+ r = omap_dsi_update(dssdev, 0, x, y, w, h, s6e8aa0_framedone_cb, dssdev);
+ if (r)
+ goto err;
+
+ dsi_bus_unlock(dssdev);
+ /* note: no bus_unlock here. unlock is in framedone_cb */
+ mutex_unlock(&s6->lock);
+ return 0;
+err:
+ dsi_bus_unlock(dssdev);
+ mutex_unlock(&s6->lock);
+ return r;
+}
+
+static int s6e8aa0_sync(struct omap_dss_device *dssdev)
+{
+ /* TODO? */
+ return 0;
+}
+
+static int s6e8aa0_set_update_mode(struct omap_dss_device *dssdev,
+ enum omap_dss_update_mode mode)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+
+ if (s6->force_update) {
+ if (mode != OMAP_DSS_UPDATE_AUTO)
+ return -EINVAL;
+ } else {
+ if (mode != OMAP_DSS_UPDATE_MANUAL)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum omap_dss_update_mode s6e8aa0_get_update_mode(struct omap_dss_device
+ *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+
+ if (s6->force_update)
+ return OMAP_DSS_UPDATE_AUTO;
+ else
+ return OMAP_DSS_UPDATE_MANUAL;
+}
+
+#ifdef CONFIG_PM
+static int s6e8aa0_resume(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ int ret;
+
+ dev_dbg(&dssdev->dev, "resume\n");
+
+ mutex_lock(&s6->lock);
+ if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = s6e8aa0_start(dssdev);
+out:
+ mutex_unlock(&s6->lock);
+ return ret;
+}
+
+static int s6e8aa0_suspend(struct omap_dss_device *dssdev)
+{
+ struct s6e8aa0_data *s6 = dev_get_drvdata(&dssdev->dev);
+ int ret = 0;
+
+ dev_dbg(&dssdev->dev, "suspend\n");
+
+ mutex_lock(&s6->lock);
+ if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ s6e8aa0_stop(dssdev);
+ dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED;
+out:
+ mutex_unlock(&s6->lock);
+ return ret;
+}
+#endif
+
+static struct omap_dss_driver s6e8aa0_driver = {
+ .probe = s6e8aa0_probe,
+ .remove = s6e8aa0_remove,
+
+ .enable = s6e8aa0_enable,
+ .disable = s6e8aa0_disable,
+#ifdef CONFIG_PM
+ .suspend = s6e8aa0_suspend,
+ .resume = s6e8aa0_resume,
+#endif
+
+ .set_update_mode = s6e8aa0_set_update_mode,
+ .get_update_mode = s6e8aa0_get_update_mode,
+
+ .update = s6e8aa0_update,
+ .sync = s6e8aa0_sync,
+
+ .get_resolution = s6e8aa0_get_resolution,
+ .get_recommended_bpp = omapdss_default_get_recommended_bpp,
+
+ /* dummy entry start */
+ .enable_te = s6e8aa0_enable_te,
+ .set_rotate = s6e8aa0_rotate,
+ .get_rotate = s6e8aa0_get_rotate,
+ .set_mirror = s6e8aa0_mirror,
+ .get_mirror = s6e8aa0_get_mirror,
+ /* dummy entry end */
+
+ .get_timings = s6e8aa0_get_timings,
+ .set_timings = s6e8aa0_set_timings,
+ .check_timings = s6e8aa0_check_timings,
+
+ .driver = {
+ .name = "s6e8aa0",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s6e8aa0_init(void)
+{
+ omap_dss_register_driver(&s6e8aa0_driver);
+ return 0;
+}
+
+static void __exit s6e8aa0_exit(void)
+{
+ omap_dss_unregister_driver(&s6e8aa0_driver);
+}
+
+module_init(s6e8aa0_init);
+module_exit(s6e8aa0_exit);