diff options
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/Kconfig | 7 | ||||
-rw-r--r-- | drivers/video/Makefile | 2 | ||||
-rw-r--r-- | drivers/video/omap2/displays/Kconfig | 7 | ||||
-rw-r--r-- | drivers/video/omap2/displays/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/omap2/displays/panel-generic-dpi.c | 25 | ||||
-rw-r--r-- | drivers/video/omap2/displays/panel-s6e8aa0.c | 1970 | ||||
-rw-r--r-- | drivers/video/sii9234.c | 1438 |
7 files changed, 3450 insertions, 0 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 1d04ca2..acd429e 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2394,6 +2394,13 @@ config HDMI_TI_4XXX_IP HDMI Library Interface , for TI OMAP4/Netra IP. See http://www.hdmi.org/ for HDMI specification. +config SII9234 + bool "Support Silicon Image 9234 MHL Bridge" + depends on I2C + default n + help + Enabled support for the Silicon Image 9234 MHL Bridge + source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 22eaeb6..bd33003 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -158,6 +158,8 @@ obj-$(CONFIG_FB_MX3) += mx3fb.o obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o obj-$(CONFIG_FB_MXS) += mxsfb.o +obj-$(CONFIG_SII9234) += sii9234.o + # the test framebuffer is last obj-$(CONFIG_FB_VIRTUAL) += vfb.o diff --git a/drivers/video/omap2/displays/Kconfig b/drivers/video/omap2/displays/Kconfig index 609a280..e08aeca 100644 --- a/drivers/video/omap2/displays/Kconfig +++ b/drivers/video/omap2/displays/Kconfig @@ -30,6 +30,13 @@ config PANEL_NEC_NL8048HL11_01B This NEC NL8048HL11-01B panel is TFT LCD used in the Zoom2/3/3630 sdp boards. +config PANEL_S6E8AA0 + tristate "S6E8AA0 DSI Panel" + depends on OMAP2_DSS_DSI + select BACKLIGHT_CLASS_DEVICE + help + S6E8AA0 video mode panel. + config PANEL_TAAL tristate "Taal DSI Panel" depends on OMAP2_DSS_DSI diff --git a/drivers/video/omap2/displays/Makefile b/drivers/video/omap2/displays/Makefile index 0f601ab3a..8b1dd23 100644 --- a/drivers/video/omap2/displays/Makefile +++ b/drivers/video/omap2/displays/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PANEL_GENERIC_DPI) += panel-generic-dpi.o obj-$(CONFIG_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o obj-$(CONFIG_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o obj-$(CONFIG_PANEL_NEC_NL8048HL11_01B) += panel-nec-nl8048hl11-01b.o +obj-$(CONFIG_PANEL_S6E8AA0) += panel-s6e8aa0.o obj-$(CONFIG_PANEL_TAAL) += panel-taal.o obj-$(CONFIG_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o diff --git a/drivers/video/omap2/displays/panel-generic-dpi.c b/drivers/video/omap2/displays/panel-generic-dpi.c index 611be7e..6aa2027 100644 --- a/drivers/video/omap2/displays/panel-generic-dpi.c +++ b/drivers/video/omap2/displays/panel-generic-dpi.c @@ -256,6 +256,31 @@ static struct panel_config generic_dpi_panels[] = { .power_off_delay = 0, .name = "powertip_ph480272t", }, + /* Samsung AMS452GN05 */ + { + { + .x_res = 480, + .y_res = 800, + + .pixel_clock = 25600, + + .hfp = 16, + .hsw = 2, + .hbp = 16, + + .vfp = 9, + .vsw = 2, + .vbp = 3, + }, + .acbi = 0x0, + .acb = 0x0, + .config = OMAP_DSS_LCD_TFT | OMAP_DSS_LCD_IVS | + OMAP_DSS_LCD_IHS | OMAP_DSS_LCD_IPC | + OMAP_DSS_LCD_IEO | OMAP_DSS_LCD_ONOFF, + .power_on_delay = 0, + .power_off_delay = 0, + .name = "samsung_ams452gn05", + }, }; struct panel_drv_data { diff --git a/drivers/video/omap2/displays/panel-s6e8aa0.c b/drivers/video/omap2/displays/panel-s6e8aa0.c new file mode 100644 index 0000000..8fd29cba --- /dev/null +++ b/drivers/video/omap2/displays/panel-s6e8aa0.c @@ -0,0 +1,1970 @@ +/* + * 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> + +#include "../dss/dss.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 = 79494, + .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 * + return dsi_vc_dcs_write(dssdev, 1, (u8 *)data, len); +} + +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; + unsigned long pclk; + + 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); + } + + /* fixup pclk based on pll config */ + pclk = dispc_pclk_rate(dssdev->channel); + if (pclk) + dssdev->panel.timings.pixel_clock = (pclk + 500) / 1000; + + 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); diff --git a/drivers/video/sii9234.c b/drivers/video/sii9234.c new file mode 100644 index 0000000..04fb348 --- /dev/null +++ b/drivers/video/sii9234.c @@ -0,0 +1,1438 @@ +/* + * Copyright (C) 2011 Samsung Electronics + * + * Authors: Adam Hampson <ahampson@sta.samsung.com> + * Erik Gilling <konkers@android.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/sii9234.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <linux/usb/otg_id.h> + +#define T_SRC_VBUS_CBUS_TO_STABLE 200 +#define T_SRC_WAKE_PULSE_WIDTH_1 19 +#define T_SRC_WAKE_PULSE_WIDTH_2 60 +#define T_SRC_WAKE_TO_DISCOVER 500 +#define T_SRC_VBUS_CBUS_T0_STABLE 500 + +/* MHL TX Addr 0x72 Registers */ +#define MHL_TX_IDL_REG 0x02 +#define MHL_TX_IDH_REG 0x03 +#define MHL_TX_REV_REG 0x04 +#define MHL_TX_SRST 0x05 +#define MHL_TX_INTR1_REG 0x71 +#define MHL_TX_INTR2_REG 0x72 /* Not Documented */ +#define MHL_TX_INTR3_REG 0x73 /* Not Documented */ +#define MHL_TX_INTR4_REG 0x74 +#define MHL_TX_INTR1_ENABLE_REG 0x75 +#define MHL_TX_INTR2_ENABLE_REG 0x76 /* Not Documented */ +#define MHL_TX_INTR3_ENABLE_REG 0x77 /* Not Documented */ +#define MHL_TX_INTR4_ENABLE_REG 0x78 + +#define MHL_TX_INT_CTRL_REG 0x79 +#define INTR_POLARITY (1 << 1) +#define INTR_OPEN_DRAIN (1 << 2) +#define HPD_OUT_OVR_EN (1 << 4) +#define HPD_OUT_OVR_VAL (1 << 5) +#define HPD_OUT_OPEN_DRAIN (1 << 6) + +#define MHL_TX_TMDS_CCTRL 0x80 + +#define MHL_TX_DISC_CTRL1_REG 0x90 +#define MHL_TX_DISC_CTRL2_REG 0x91 +#define MHL_TX_DISC_CTRL3_REG 0x92 +#define MHL_TX_DISC_CTRL4_REG 0x93 /* Not Documented */ + +/* There doesn't seem to be any documentation for CTRL5 but it looks like + * it is some sort of pull up control register + */ +#define MHL_TX_DISC_CTRL5_REG 0x94 +#define MHL_TX_DISC_CTRL6_REG 0x95 +#define MHL_TX_DISC_CTRL7_REG 0x96 +#define MHL_TX_DISC_CTRL8_REG 0x97 /* Not Documented */ +#define MHL_TX_STAT1_REG 0x98 /* Not Documented */ +#define MHL_TX_STAT2_REG 0x99 + +#define MHL_TX_MHLTX_CTL1_REG 0xA0 +#define MHL_TX_MHLTX_CTL2_REG 0xA1 +#define MHL_TX_MHLTX_CTL4_REG 0xA3 +#define MHL_TX_MHLTX_CTL6_REG 0xA5 +#define MHL_TX_MHLTX_CTL7_REG 0xA6 + +/* MHL TX SYS STAT Registers + * Not Documented, mentioned only in reference of RSEN + */ +#define MHL_TX_SYSSTAT_REG 0x09 + +/* MHL TX SYS STAT Register Bits */ +#define RSEN_STATUS (1<<2) + +/* MHL TX INTR4 Register Bits */ +#define RGND_READY_INT (1<<6) +#define VBUS_LOW_INT (1<<5) +#define CBUS_LKOUT_INT (1<<4) +#define MHL_DISC_FAIL_INT (1<<3) +#define MHL_EST_INT (1<<2) + +/* MHL TX INTR4_ENABLE 0x78 Register Bits */ +#define RGND_READY_MASK (1<<6) +#define CBUS_LKOUT_MASK (1<<4) +#define MHL_DISC_FAIL_MASK (1<<3) +#define MHL_EST_MASK (1<<2) + +/* MHL TX INTR1 Register Bits*/ +#define HPD_CHANGE_INT (1<<6) +#define RSEN_CHANGE_INT (1<<5) + +/* MHL TX INTR1_ENABLE 0x75 Register Bits*/ +#define HPD_CHANGE_INT_MASK (1<<6) +#define RSEN_CHANGE_INT_MASK (1<<5) + +#define CBUS_CONFIG_REG 0x07 + +#define CBUS_INT_STATUS_1_REG 0x08 +#define CBUS_INT_1_MASK 0x09 + +#define CBUS_MSC_COMMAND_START 0x12 +#define START_MSC_MSG (1 << 1) +#define START_READ_DEVCAP (1 << 2) +#define START_WRITE_STAT_INT (1 << 3) +#define START_WRITE_BURST (1 << 4) + +#define CBUS_MSC_OFFSET_REG 0x13 +#define CBUS_MSC_FIRST_DATA_OUT 0x14 +#define CBUS_MSC_SECOND_DATA_OUT 0x15 +#define CBUS_MSC_FIRST_DATA_IN 0x16 +#define CBUS_MSC_MSG_CMD_IN 0x18 +#define CBUS_MSC_MSG_DATA_IN 0x19 +#define CBUS_INT_STATUS_2_REG 0x1E +#define CBUS_INT_2_MASK 0x1F +#define CBUS_LINK_CONTROL_2_REG 0x31 + +#define CBUS_INT_STATUS_2_REG 0x1E + +/* MHL Interrupt Registers */ +#define CBUS_MHL_INTR_REG_0 0xA0 + +#define CBUS_MHL_INTR_REG_1 0xA1 +#define MHL_INT_EDID_CHG (1<<1) + +#define CBUS_MHL_INTR_REG_2 0xA2 +#define CBUS_MHL_INTR_REG_3 0xA3 + +/* MHL Status Registers */ +#define CBUS_MHL_STATUS_REG_0 0xB0 +#define MHL_STATUS_DCAP_READY (1<<0) + +#define CBUS_MHL_STATUS_REG_1 0xB1 +#define CBUS_MHL_STATUS_REG_2 0xB2 +#define CBUS_MHL_STATUS_REG_3 0xB3 + +/* CBUS INTR1 STATUS Register bits */ +#define MSC_RESP_ABORT (1<<6) +#define MSC_REQ_ABORT (1<<5) +#define MSC_REQ_DONE (1<<4) +#define MSC_MSG_RECD (1<<3) +#define CBUS_DDC_ABORT (1<<2) + +/* CBUS INTR1 STATUS 0x09 Enable Mask*/ +#define MSC_RESP_ABORT_MASK (1<<6) +#define MSC_REQ_ABORT_MASK (1<<5) +#define MSC_REQ_DONE_MASK (1<<4) +#define MSC_MSG_RECD_MASK (1<<3) +#define CBUS_DDC_ABORT_MASK (1<<2) + +/* CBUS INTR2 STATUS Register bits */ +#define WRT_STAT_RECD (1<<3) +#define SET_INT_RECD (1<<2) +#define WRT_BURST_RECD (1<<0) + +/* CBUS INTR2 STATUS 0x1F Enable Mask*/ +#define WRT_STAT_RECD_MASK (1<<3) +#define SET_INT_RECD_MASK (1<<2) +#define WRT_BURST_RECD_MASK (1<<0) + +/* CBUS Control Registers*/ +/* Retry count for all MSC commands*/ +#define MSC_RETRY_FAIL_LIM_REG 0x1D + +/* reason for MSC_REQ_ABORT interrupt on CBUS */ +#define MSC_REQ_ABORT_REASON_REG 0x0D + +#define MSC_RESP_ABORT_REASON_REG 0x0E + +/* MSC Requestor Abort Reason Register bits*/ +#define ABORT_BY_PEER (1<<7) +#define UNDEF_CMD (1<<3) +#define TIMEOUT (1<<2) +#define PROTO_ERROR (1<<1) +#define MAX_FAIL (1<<0) + +/* MSC Responder Abort Reason Register bits*/ +#define ABORT_BY_PEER (1<<7) +#define UNDEF_CMD (1<<3) +#define TIMEOUT (1<<2) + +/* Set HPD came from Downstream, not documented */ +#define SET_HPD_DOWNSTREAM (1<<6) + +/* MHL TX DISC1 Register Bits */ +#define DISC_EN (1<<0) + +/* MHL TX DISC2 Register Bits */ +#define SKIP_GND (1<<6) +#define ATT_THRESH_SHIFT 0x04 +#define ATT_THRESH_MASK (0x03 << ATT_THRESH_SHIFT) +#define USB_D_OEN (1<<3) +#define DEGLITCH_TIME_MASK 0x07 +#define DEGLITCH_TIME_2MS 0 +#define DEGLITCH_TIME_4MS 1 +#define DEGLITCH_TIME_8MS 2 +#define DEGLITCH_TIME_16MS 3 +#define DEGLITCH_TIME_40MS 4 +#define DEGLITCH_TIME_50MS 5 +#define DEGLITCH_TIME_60MS 6 +#define DEGLITCH_TIME_128MS 7 + +#define DISC_CTRL3_COMM_IMME (1<<7) +#define DISC_CTRL3_FORCE_MHL (1<<6) +#define DISC_CTRL3_FORCE_USB (1<<4) +#define DISC_CTRL3_USB_EN (1<<3) + +/* MHL TX DISC4 0x93 Register Bits: undocumented */ +#define CBUS_DISC_PUP_SEL_SHIFT 6 +#define CBUS_DISC_PUP_SEL_MASK (3<<CBUS_DISC_PUP_SEL_SHIFT) +#define CBUS_DISC_PUP_SEL_10K (2<<CBUS_DISC_PUP_SEL_SHIFT) +#define CBUS_DISC_PUP_SEL_OPEN (0<<CBUS_DISC_PUP_SEL_SHIFT) +#define CBUS_IDLE_PUP_SEL_SHIFT 4 +#define CBUS_IDLE_PUP_SEL_MASK (3<<CBUS_IDLE_PUP_SEL_SHIFT) +#define CBUS_IDLE_PUP_SEL_OPEN (0<<CBUS_IDLE_PUP_SEL_SHIFT) + +/* MHL TX DISC5 0x94 Register Bits */ +#define CBUS_MHL_PUP_SEL_MASK 0x03 /* Not Documented */ +#define CBUS_MHL_PUP_SEL_5K 0x01 /* Not Documented */ +#define CBUS_MHL_PUP_SEL_OPEN 0x00 + +/* MHL TX DISC6 0x95 Register Bits */ +#define USB_D_OVR (1<<7) +#define USB_ID_OVR (1<<6) +#define DVRFLT_SEL (1<<5) +#define BLOCK_RGND_INT (1<<4) +#define SKIP_DEG (1<<3) +#define CI2CA_POL (1<<2) +#define CI2CA_WKUP (1<<1) +#define SINGLE_ATT (1<<0) + +/* MHL TX DISC7 0x96 Register Bits + * + * Bits 7 and 6 are labeled as reserved but seem to be related to toggling + * the CBUS signal when generating the wake pulse sequence. + */ +#define USB_D_ODN (1<<5) +#define VBUS_CHECK (1<<2) +#define RGND_INTP_MASK 0x03 +#define RGND_INTP_OPEN 0 +#define RGND_INTP_2K 1 +#define RGND_INTP_1K 2 +#define RGND_INTP_SHORT 3 + +/* TPI Addr 0x7A Registers */ +#define TPI_DPD_REG 0x3D + +#define TPI_PD_TMDS (1<<5) +#define TPI_PD_OSC_EN (1<<4) +#define TPI_TCLK_PHASE (1<<3) +#define TPI_PD_IDCK (1<<2) +#define TPI_PD_OSC (1<<1) +#define TPI_PD (1<<0) + + + +/* HDMI RX Registers */ +#define HDMI_RX_TMDS0_CCTRL1_REG 0x10 +#define HDMI_RX_TMDS_CLK_EN_REG 0x11 +#define HDMI_RX_TMDS_CH_EN_REG 0x12 +#define HDMI_RX_PLL_CALREFSEL_REG 0x17 +#define HDMI_RX_PLL_VCOCAL_REG 0x1A +#define HDMI_RX_EQ_DATA0_REG 0x22 +#define HDMI_RX_EQ_DATA1_REG 0x23 +#define HDMI_RX_EQ_DATA2_REG 0x24 +#define HDMI_RX_EQ_DATA3_REG 0x25 +#define HDMI_RX_EQ_DATA4_REG 0x26 +#define HDMI_RX_TMDS_ZONE_CTRL_REG 0x4C +#define HDMI_RX_TMDS_MODE_CTRL_REG 0x4D + +enum rgnd_state { + RGND_UNKNOWN = 0, + RGND_OPEN, + RGND_1K, + RGND_2K, + RGND_SHORT +}; + +enum mhl_state { + STATE_DISCONNECTED = 0, + STATE_DISCOVERY_FAILED, + STATE_CBUS_LOCKOUT, + STATE_ESTABLISHED, +}; + +static inline bool mhl_state_is_error(enum mhl_state state) +{ + return state == STATE_DISCOVERY_FAILED || + state == STATE_CBUS_LOCKOUT; +} + +struct sii9234_data { + struct sii9234_platform_data *pdata; + struct otg_id_notifier_block otg_id_nb; + wait_queue_head_t wq; + + bool claimed; + enum mhl_state state; + enum rgnd_state rgnd; + int irq; + bool rsen; + + struct mutex lock; + + bool msc_ready; + struct mutex msc_lock; + struct completion msc_complete; + + u8 devcap[16]; +}; + +static irqreturn_t sii9234_irq_thread(int irq, void *data); + +static int mhl_tx_write_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 value) +{ + return i2c_smbus_write_byte_data(sii9234->pdata->mhl_tx_client, offset, + value); +} + +static int mhl_tx_read_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 *value) +{ + int ret; + + if (!value) + return -EINVAL; + + ret = i2c_smbus_write_byte(sii9234->pdata->mhl_tx_client, offset); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte(sii9234->pdata->mhl_tx_client); + if (ret < 0) + return ret; + + *value = ret & 0x000000FF; + + return 0; +} + +static int mhl_tx_set_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 mask) +{ + int ret; + u8 value; + + ret = mhl_tx_read_reg(sii9234, offset, &value); + if (ret < 0) + return ret; + + value |= mask; + + return mhl_tx_write_reg(sii9234, offset, value); +} + +static int mhl_tx_clear_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 mask) +{ + int ret; + u8 value; + + ret = mhl_tx_read_reg(sii9234, offset, &value); + if (ret < 0) + return ret; + + value &= ~mask; + + return mhl_tx_write_reg(sii9234, offset, value); +} + +static int tpi_write_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 value) +{ + return i2c_smbus_write_byte_data(sii9234->pdata->tpi_client, offset, + value); +} + +static int tpi_read_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 *value) +{ + int ret; + + if (!value) + return -EINVAL; + + ret = i2c_smbus_write_byte(sii9234->pdata->tpi_client, offset); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte(sii9234->pdata->tpi_client); + if (ret < 0) + return ret; + + *value = ret & 0x000000FF; + + return 0; +} + +static int hdmi_rx_write_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 value) +{ + return i2c_smbus_write_byte_data(sii9234->pdata->hdmi_rx_client, offset, + value); +} + +static int cbus_write_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 value) +{ + return i2c_smbus_write_byte_data(sii9234->pdata->cbus_client, offset, + value); +} + +static int cbus_read_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 *value) +{ + int ret; + + if (!value) + return -EINVAL; + + ret = i2c_smbus_write_byte(sii9234->pdata->cbus_client, offset); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte(sii9234->pdata->cbus_client); + if (ret < 0) + return ret; + + *value = ret & 0x000000FF; + + return 0; +} + +static int cbus_set_reg(struct sii9234_data *sii9234, unsigned int offset, + u8 mask) +{ + int ret; + u8 value; + + ret = cbus_read_reg(sii9234, offset, &value); + if (ret < 0) + return ret; + + value |= mask; + + return cbus_write_reg(sii9234, offset, value); +} + +static int mhl_wake_toggle(struct sii9234_data *sii9234, + unsigned long high_period, + unsigned long low_period) +{ + int ret; + + /* These bits are not documented. */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL7_REG, (1<<7) | (1<<6)); + if (ret < 0) + return ret; + + usleep_range(high_period * USEC_PER_MSEC, high_period * USEC_PER_MSEC); + + ret = mhl_tx_clear_reg(sii9234, MHL_TX_DISC_CTRL7_REG, (1<<7) | (1<<6)); + if (ret < 0) + return ret; + + usleep_range(low_period * USEC_PER_MSEC, low_period * USEC_PER_MSEC); + + return 0; +} + +static int mhl_send_wake_pulses(struct sii9234_data *sii9234) +{ + int ret; + + ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1, + T_SRC_WAKE_PULSE_WIDTH_1); + if (ret < 0) + return ret; + + ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1, + T_SRC_WAKE_PULSE_WIDTH_2); + if (ret < 0) + return ret; + + ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1, + T_SRC_WAKE_PULSE_WIDTH_1); + if (ret < 0) + return ret; + + ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1, + T_SRC_WAKE_TO_DISCOVER); + if (ret < 0) + return ret; + + return 0; +} +static int sii9234_cbus_reset(struct sii9234_data *sii9234) +{ + int ret; + /* Reset CBUS */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_SRST, 0x03); + if (ret < 0) + return ret; + + usleep_range(2000, 3000); + + ret = mhl_tx_clear_reg(sii9234, MHL_TX_SRST, 0x03); + if (ret < 0) + return ret; + + /* Adjust interrupt mask everytime reset is performed.*/ + ret = cbus_write_reg(sii9234, + CBUS_INT_1_MASK, + MSC_RESP_ABORT_MASK | + MSC_REQ_ABORT_MASK | + MSC_REQ_DONE_MASK | + MSC_MSG_RECD_MASK | + CBUS_DDC_ABORT_MASK); + if (ret < 0) + return ret; + + ret = cbus_write_reg(sii9234, + CBUS_INT_2_MASK, + WRT_STAT_RECD_MASK | + SET_INT_RECD_MASK); + if (ret < 0) + return ret; + + return 0; +} + +static int sii9234_cbus_init(struct sii9234_data *sii9234) +{ + u8 value; + + cbus_write_reg(sii9234, 0x07, 0x32); + cbus_write_reg(sii9234, 0x40, 0x03); + cbus_write_reg(sii9234, 0x42, 0x06); + cbus_write_reg(sii9234, 0x36, 0x0C); + + cbus_write_reg(sii9234, 0x3D, 0xFD); + cbus_write_reg(sii9234, 0x1C, 0x00); + + cbus_write_reg(sii9234, 0x44, 0x02); + + /* Setup our devcap*/ + cbus_write_reg(sii9234, 0x80, 0x04); + cbus_write_reg(sii9234, 0x81, 0x10); + cbus_write_reg(sii9234, 0x82, 0x02); + cbus_write_reg(sii9234, 0x83, 0); + cbus_write_reg(sii9234, 0x84, 0); + cbus_write_reg(sii9234, 0x85, 0x01 | 0x02); + cbus_write_reg(sii9234, 0x86, 0x01); + cbus_write_reg(sii9234, 0x87, 0); + cbus_write_reg(sii9234, 0x88, (1<<2) | (1<<1) | (1<<3) | (1<<7)); + cbus_write_reg(sii9234, 0x89, 0x0F); + cbus_write_reg(sii9234, 0x8A, (1<<0) | (1<<1) | (1<<2)); + cbus_write_reg(sii9234, 0x8B, 0); + cbus_write_reg(sii9234, 0x8C, 0); + cbus_write_reg(sii9234, 0x8D, 16); + cbus_write_reg(sii9234, 0x8E, 0x44); + cbus_write_reg(sii9234, 0x8F, 0); + + cbus_read_reg(sii9234, 0x31, &value); + value |= 0x0C; + cbus_write_reg(sii9234, 0x31, value); + + cbus_read_reg(sii9234, 0x22, &value); + value &= 0x0F; + cbus_write_reg(sii9234, 0x22, value); + + cbus_write_reg(sii9234, 0x30, 0x01); + + return 0; +} + +static int sii9234_power_init(struct sii9234_data *sii9234) +{ + int ret; + + /* Force the SiI9234 into the D0 state. */ + ret = tpi_write_reg(sii9234, TPI_DPD_REG, 0x3F); + if (ret < 0) + return ret; + + /* Enable TxPLL Clock */ + ret = hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_CLK_EN_REG, 0x01); + if (ret < 0) + return ret; + + /* Enable Tx Clock Path & Equalizer*/ + ret = hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_CH_EN_REG, 0x15); + if (ret < 0) + return ret; + + /* Power Up TMDS*/ + ret = mhl_tx_write_reg(sii9234, 0x08, 0x35); + if (ret < 0) + return ret; + + return 0; +} + +static void sii9234_hdmi_init(struct sii9234_data *sii9234) +{ + /* Analog PLL Control + * bits 5:4 = 2b00 as per characterization team. + */ + hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1); + + /* PLL Calrefsel */ + hdmi_rx_write_reg(sii9234, HDMI_RX_PLL_CALREFSEL_REG, 0x03); + + /* VCO Cal */ + hdmi_rx_write_reg(sii9234, HDMI_RX_PLL_VCOCAL_REG, 0x20); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA0_REG, 0x8A); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA1_REG, 0x6A); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA2_REG, 0xAA); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA3_REG, 0xCA); + + /* Auto EQ */ + hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA4_REG, 0xEA); + + /* Manual zone */ + hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_ZONE_CTRL_REG, 0xA0); + + /* PLL Mode Value */ + hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_MODE_CTRL_REG, 0x00); + + mhl_tx_write_reg(sii9234, MHL_TX_TMDS_CCTRL, 0x34); + + hdmi_rx_write_reg(sii9234, 0x45, 0x44); + + /* Rx PLL BW ~ 4MHz */ + hdmi_rx_write_reg(sii9234, 0x31, 0x0A); + + /* Analog PLL Control + * bits 5:4 = 2b00 as per characterization team. + */ + hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1); +} + +static void sii9234_mhl_tx_ctl_int(struct sii9234_data *sii9234) +{ + mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL1_REG, 0xD0); + mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL2_REG, 0xFC); + mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL4_REG, 0xEB); + mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL7_REG, 0x0C); +} + +static void sii9234_power_down(struct sii9234_data *sii9234) +{ + if (sii9234->claimed) + sii9234->pdata->connect(false, NULL); + + sii9234->state = STATE_DISCONNECTED; + sii9234->claimed = false; + + tpi_write_reg(sii9234, TPI_DPD_REG, 0); + + sii9234->pdata->power(0); + sii9234->pdata->enable(0); +} + +/* toggle hpd line low for 100ms */ +static void sii9234_toggle_hpd(struct sii9234_data *sii9234) +{ + mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_EN); + mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_VAL); + msleep(100); + mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_VAL); + mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_EN); +} + +/* Must call with sii9234->lock held */ +static int sii9234_msc_req_locked(struct sii9234_data *sii9234, u8 req_type, + u8 offset) +{ + int ret; + + if (sii9234->state != STATE_ESTABLISHED) + return -ENOENT; + + mutex_unlock(&sii9234->lock); + ret = wait_event_timeout(sii9234->wq, sii9234->msc_ready, + msecs_to_jiffies(2000)); + mutex_lock(&sii9234->lock); + if (!sii9234->msc_ready) + return -EIO; + + mutex_lock(&sii9234->msc_lock); + + init_completion(&sii9234->msc_complete); + + cbus_write_reg(sii9234, CBUS_MSC_OFFSET_REG, offset); + cbus_write_reg(sii9234, CBUS_MSC_COMMAND_START, req_type); + + mutex_unlock(&sii9234->lock); + ret = wait_for_completion_timeout(&sii9234->msc_complete, + msecs_to_jiffies(500)); + mutex_lock(&sii9234->lock); + + mutex_unlock(&sii9234->msc_lock); + + return ret ? 0 : -EIO; +} + +/* Must call with sii9234->lock held */ +static int sii9234_devcap_read_locked(struct sii9234_data *sii9234, u8 offset) +{ + int ret; + u8 val; + + if (offset > 0xf) + return -EINVAL; + + ret = sii9234_msc_req_locked(sii9234, START_READ_DEVCAP, offset); + if (ret < 0) + return ret; + + ret = cbus_read_reg(sii9234, CBUS_MSC_FIRST_DATA_IN, &val); + if (ret < 0) + return ret; + + return val; +} + +static int sii9234_detection_callback(struct otg_id_notifier_block *nb) +{ + struct sii9234_data *sii9234 = container_of(nb, struct sii9234_data, + otg_id_nb); + int ret; + int i; + u8 value; + int handled = OTG_ID_UNHANDLED; + + pr_debug("si9234: detection started\n"); + + mutex_lock(&sii9234->lock); + sii9234->rgnd = RGND_UNKNOWN; + sii9234->state = STATE_DISCONNECTED; + sii9234->rsen = false; + sii9234->msc_ready = false; + + /* Set the board configuration so the SiI9234 has access to the + * external connector. + */ + sii9234->pdata->enable(1); + sii9234->pdata->power(1); + + ret = sii9234_power_init(sii9234); + if (ret < 0) + goto unhandled; + + sii9234_hdmi_init(sii9234); + + sii9234_mhl_tx_ctl_int(sii9234); + + /* Enable HDCP Compliance safety*/ + ret = mhl_tx_write_reg(sii9234, 0x2B, 0x01); + if (ret < 0) + goto unhandled; + + /* CBUS discovery cycle time for each drive and float = 150us*/ + ret = mhl_tx_read_reg(sii9234, MHL_TX_DISC_CTRL1_REG, &value); + if (ret < 0) + goto unhandled; + + value &= ~(1<<2); + value |= (1<<3); + + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG, value); + if (ret < 0) + goto unhandled; + + /* Clear bit 6 (reg_skip_rgnd) */ + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL2_REG, + (1<<7) /* Reserved Bit */ | + 2 << ATT_THRESH_SHIFT | + DEGLITCH_TIME_128MS); + if (ret < 0) + goto unhandled; + + /* Changed from 66 to 65 for 94[1:0] = 01 = 5k reg_cbusmhl_pup_sel */ + /* 1.8V CBUS VTH & GND threshold */ + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL5_REG, 0x75); + if (ret < 0) + goto unhandled; + + /* set bit 2 and 3, which is Initiator Timeout */ + ret = cbus_read_reg(sii9234, CBUS_LINK_CONTROL_2_REG, &value); + if (ret < 0) + goto unhandled; + + value |= 0x0C; + + ret = cbus_write_reg(sii9234, CBUS_LINK_CONTROL_2_REG, value); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL6_REG, 0xA0); + if (ret < 0) + goto unhandled; + + /* RGND & single discovery attempt (RGND blocking) */ + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL6_REG, BLOCK_RGND_INT | + DVRFLT_SEL | SINGLE_ATT); + if (ret < 0) + goto unhandled; + + /* Use VBUS path of discovery state machine*/ + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL8_REG, 0); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL6_REG, USB_ID_OVR); + if (ret < 0) + goto unhandled; + + /* To allow RGND engine to operate correctly. + * When moving the chip from D2 to D0 (power up, init regs) + * the values should be + * 94[1:0] = 01 reg_cbusmhl_pup_sel[1:0] should be set for 5k + * 93[7:6] = 10 reg_cbusdisc_pup_sel[1:0] should be + * set for 10k (default) + * 93[5:4] = 00 reg_cbusidle_pup_sel[1:0] = open (default) + */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL3_REG, 0x86); + if (ret < 0) + goto unhandled; + + /* change from CC to 8C to match 5K*/ + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL4_REG, 0x8C); + if (ret < 0) + goto unhandled; + + /* Configure the interrupt as active high */ + ret = mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, (1<<2) | (1<<1)); + if (ret < 0) + goto unhandled; + + msleep(25); + + ret = mhl_tx_clear_reg(sii9234, MHL_TX_DISC_CTRL6_REG, USB_ID_OVR); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG, 0x27); + if (ret < 0) + goto unhandled; + + /* Reset CBUS */ + ret = sii9234_cbus_reset(sii9234); + if (ret < 0) + goto unhandled; + + sii9234_cbus_init(sii9234); + + /* Enable Auto soft reset on SCDT = 0*/ + ret = mhl_tx_write_reg(sii9234, 0x05, 0x04); + + if (ret < 0) + goto unhandled; + + /* HDMI Transcode mode enable*/ + ret = mhl_tx_write_reg(sii9234, 0x0D, 0x1C); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_write_reg(sii9234, MHL_TX_INTR4_ENABLE_REG, + RGND_READY_MASK | CBUS_LKOUT_MASK | + MHL_DISC_FAIL_MASK | MHL_EST_MASK); + if (ret < 0) + goto unhandled; + + ret = mhl_tx_write_reg(sii9234, MHL_TX_INTR1_ENABLE_REG, + (1<<5) | (1<<6)); + if (ret < 0) + goto unhandled; + + + pr_debug("sii9234: waiting for RGND measurement\n"); + enable_irq(sii9234->irq); + + /* SiI9244 Programmer's Reference Section 2.4.3 + * State : RGND Ready + */ + mutex_unlock(&sii9234->lock); + ret = wait_event_timeout(sii9234->wq, + ((sii9234->rgnd != RGND_UNKNOWN) || + mhl_state_is_error(sii9234->state)), + msecs_to_jiffies(2000)); + + mutex_lock(&sii9234->lock); + if (sii9234->rgnd == RGND_UNKNOWN || mhl_state_is_error(sii9234->state)) + goto unhandled; + + if (sii9234->rgnd != RGND_1K) + goto unhandled; + + mutex_unlock(&sii9234->lock); + + pr_debug("sii9234: waiting for detection\n"); + ret = wait_event_timeout(sii9234->wq, + sii9234->state != STATE_DISCONNECTED, + msecs_to_jiffies(500)); + mutex_lock(&sii9234->lock); + if (sii9234->state == STATE_DISCONNECTED) + goto unhandled; + + if (sii9234->state == STATE_DISCOVERY_FAILED) { + handled = OTG_ID_PROXY_WAIT; + goto unhandled; + } + + if (mhl_state_is_error(sii9234->state)) + goto unhandled; + + mutex_unlock(&sii9234->lock); + wait_event_timeout(sii9234->wq, sii9234->rsen, msecs_to_jiffies(400)); + mutex_lock(&sii9234->lock); + if (!sii9234->rsen) + goto unhandled; + + memset(sii9234->devcap, 0x0, sizeof(sii9234->devcap)); + for (i = 0; i < 16; i++) { + ret = sii9234_devcap_read_locked(sii9234, i); + if (ret < 0) + break; + sii9234->devcap[i] = ret; + } + +#ifdef DEBUG + if (ret >= 0) + print_hex_dump(KERN_DEBUG, "sii9234: devcap = ", DUMP_PREFIX_NONE, + 16, 1, sii9234->devcap, 16, false); +#endif + + /* It's possible for devcap reading to fail but the adapter still + * be connected. Therefore we must keep ownership of the port + * as long as it's still connected. + */ + if (sii9234->state != STATE_ESTABLISHED) + goto unhandled; + + pr_info("si9234: connection established\n"); + + sii9234->claimed = true; + sii9234->pdata->connect(true, ret >= 0 ? sii9234->devcap : NULL); + mutex_unlock(&sii9234->lock); + + return OTG_ID_HANDLED; + +unhandled: + pr_info("sii9234: Detection failed"); + if (sii9234->state == STATE_DISCONNECTED) + pr_cont(" (timeout)"); + else if (sii9234->state == STATE_DISCOVERY_FAILED) + pr_cont(" (discovery failed)"); + else if (sii9234->state == STATE_CBUS_LOCKOUT) + pr_cont(" (cbus_lockout)"); + pr_cont("\n"); + + disable_irq_nosync(sii9234->irq); + + sii9234_power_down(sii9234); + + mutex_unlock(&sii9234->lock); + return handled; +} + +static void sii9234_cancel_callback(struct otg_id_notifier_block *nb) +{ + struct sii9234_data *sii9234 = container_of(nb, struct sii9234_data, + otg_id_nb); + + mutex_lock(&sii9234->lock); + sii9234_power_down(sii9234); + mutex_unlock(&sii9234->lock); +} + +static int sii9234_cbus_irq(struct sii9234_data *sii9234) +{ + u8 cbus_intr1, cbus_intr2; + u8 mhl_intr0, mhl_intr1; + u8 mhl_status0, mhl_status1, mhl_status2, mhl_status3; + + int ret = 0; + + cbus_read_reg(sii9234, CBUS_INT_STATUS_1_REG, &cbus_intr1); + cbus_read_reg(sii9234, CBUS_INT_STATUS_2_REG, &cbus_intr2); + cbus_read_reg(sii9234, CBUS_MHL_INTR_REG_0, &mhl_intr0); + cbus_read_reg(sii9234, CBUS_MHL_INTR_REG_1, &mhl_intr1); + cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_0, &mhl_status0); + cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_1, &mhl_status1); + cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_2, &mhl_status2); + cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_3, &mhl_status3); + + pr_debug("sii9234: cbus_intr %02x %02x\n", cbus_intr1, cbus_intr2); + + if (cbus_intr1 & MSC_RESP_ABORT) + pr_warn("sii9234: msc resp abort\n"); + + if (cbus_intr1 & MSC_REQ_ABORT) + pr_warn("sii9234: msc req abort\n"); + + if (cbus_intr1 & CBUS_DDC_ABORT) + pr_warn("sii9234: ddc abort\n"); + + if (cbus_intr1 & MSC_REQ_DONE) { + pr_debug("sii9234: msc request done\n"); + complete(&sii9234->msc_complete); + } + + if (cbus_intr1 & MSC_MSG_RECD) + pr_debug("sii9234: msc msg received\n"); + + + if (cbus_intr2 & WRT_STAT_RECD) { + pr_debug("sii9234: write stat received\n"); + sii9234->msc_ready = mhl_status0 & MHL_STATUS_DCAP_READY; + } + + if (cbus_intr2 & SET_INT_RECD) { + if (mhl_intr1 & MHL_INT_EDID_CHG) + sii9234_toggle_hpd(sii9234); + } + + if (cbus_intr2 & WRT_BURST_RECD) + pr_debug("sii9234: write burst received\n"); + + cbus_write_reg(sii9234, CBUS_MHL_INTR_REG_0, mhl_intr0); + cbus_write_reg(sii9234, CBUS_MHL_INTR_REG_1, mhl_intr1); + cbus_write_reg(sii9234, CBUS_INT_STATUS_1_REG, cbus_intr1); + cbus_write_reg(sii9234, CBUS_INT_STATUS_2_REG, cbus_intr2); + + return ret; +} + +static irqreturn_t sii9234_irq_thread(int irq, void *data) +{ + struct sii9234_data *sii9234 = data; + int ret; + u8 intr1, intr4, value; + u8 intr1_en, intr4_en; + bool release_otg = false; + + mutex_lock(&sii9234->lock); + mhl_tx_read_reg(sii9234, MHL_TX_INTR1_REG, &intr1); + mhl_tx_read_reg(sii9234, MHL_TX_INTR4_REG, &intr4); + + mhl_tx_read_reg(sii9234, MHL_TX_INTR1_ENABLE_REG, &intr1_en); + mhl_tx_read_reg(sii9234, MHL_TX_INTR4_ENABLE_REG, &intr4_en); + pr_debug("sii9234: irq %02x/%02x %02x/%02x\n", intr1, intr1_en, + intr4, intr4_en); + + if (intr4 & RGND_READY_INT) { + ret = mhl_tx_read_reg(sii9234, MHL_TX_STAT2_REG, &value); + if (ret < 0) { + dev_err(&sii9234->pdata->mhl_tx_client->dev, + "STAT2 reg, err %d\n", ret); + goto err_exit; + } + + switch (value & RGND_INTP_MASK) { + case RGND_INTP_OPEN: + pr_debug("RGND Open\n"); + sii9234->rgnd = RGND_OPEN; + break; + case RGND_INTP_1K: + pr_debug("RGND 1K\n"); + ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG, + 0x25); + + ret = mhl_send_wake_pulses(sii9234); + sii9234->rgnd = RGND_1K; + break; + case RGND_INTP_2K: + pr_debug("RGND 2K\n"); + ret = mhl_send_wake_pulses(sii9234); + sii9234->rgnd = RGND_2K; + break; + case RGND_INTP_SHORT: + pr_debug("RGND Short\n"); + sii9234->rgnd = RGND_SHORT; + break; + }; + } + + if (intr4 & CBUS_LKOUT_INT) { + pr_debug("sii9234: CBUS Lockout Interrupt\n"); + sii9234->state = STATE_CBUS_LOCKOUT; + } + + if (intr4 & MHL_DISC_FAIL_INT) + sii9234->state = STATE_DISCOVERY_FAILED; + + if (intr4 & MHL_EST_INT) { + /* discovery override */ + ret = mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL1_REG, 0x10); + + /* increase DDC translation layer timer (byte mode) */ + cbus_write_reg(sii9234, 0x07, 0x32); + cbus_set_reg(sii9234, 0x44, 1<<1); + + /* Keep the discovery enabled. Need RGND interrupt */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL1_REG, (1<<0)); + + sii9234->state = STATE_ESTABLISHED; + } + + if (intr1 & HPD_CHANGE_INT) { + ret = cbus_read_reg(sii9234, MSC_REQ_ABORT_REASON_REG, &value); + + if (value & SET_HPD_DOWNSTREAM) { + /* Downstream HPD Highi */ + + /* Do we need to send HPD upstream using + * Register 0x79(page0)? Is HPD need to be overriden?? + * TODO: See if we need code for overriding HPD OUT + * as per Page 0,0x79 Register + */ + + /* Enable TMDS */ + ret = mhl_tx_set_reg(sii9234, MHL_TX_TMDS_CCTRL, + (1<<4)); + pr_debug("sii9234: MHL HPD High, enabled TMDS\n"); + + ret = mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, + (1<<4) | (1<<5)); + } else { + /*Downstream HPD Low*/ + + /* Similar to above comments. + * TODO:Do we need to override HPD OUT value + * and do we need to disable TMDS here? + */ + + /* Disable TMDS */ + ret = mhl_tx_clear_reg(sii9234, MHL_TX_TMDS_CCTRL, + (1<<4)); + pr_debug("sii9234 MHL HPD low, disabled TMDS\n"); + ret = mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, + (1<<4) | (1<<5)); + } + } + + if (intr1 & RSEN_CHANGE_INT) { + ret = mhl_tx_read_reg(sii9234, MHL_TX_SYSSTAT_REG, &value); + + sii9234->rsen = value & RSEN_STATUS; + + if (value & RSEN_STATUS) { + pr_info("sii9234: MHL cable connected.. RESN High\n"); + } else { + pr_info("sii9234: RSEN lost\n"); + /* Once RSEN loss is confirmed,we need to check + * based on cable status and chip power status,whether + * it is SINK Loss(HDMI cable not connected, TV Off) + * or MHL cable disconnection + * TODO: Define the below mhl_disconnection() + */ + /* mhl_disconnection(); */ + /* Notify Disconnection to OTG */ + if (sii9234->claimed == true) { + disable_irq_nosync(sii9234->irq); + release_otg = true; + } + sii9234_power_down(sii9234); + } + } + + if (sii9234->state == STATE_ESTABLISHED) { + ret = sii9234_cbus_irq(sii9234); + if (ret < 0) { + if (sii9234->claimed == true) { + disable_irq_nosync(sii9234->irq); + release_otg = true; + } + sii9234_power_down(sii9234); + } + } + +err_exit: + mhl_tx_write_reg(sii9234, MHL_TX_INTR1_REG, intr1); + mhl_tx_write_reg(sii9234, MHL_TX_INTR4_REG, intr4); + + mutex_unlock(&sii9234->lock); + + pr_debug("si9234: wake_up\n"); + wake_up(&sii9234->wq); + + if (release_otg) + otg_id_notify(); + + return IRQ_HANDLED; +} + +static int __devinit sii9234_mhl_tx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct sii9234_data *sii9234; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + sii9234 = kzalloc(sizeof(struct sii9234_data), GFP_KERNEL); + if (!sii9234) { + dev_err(&client->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + sii9234->pdata = client->dev.platform_data; + sii9234->pdata->mhl_tx_client = client; + if (!sii9234->pdata) { + ret = -EINVAL; + goto err_exit1; + } + + i2c_set_clientdata(client, sii9234); + + sii9234->irq = client->irq; + + init_waitqueue_head(&sii9234->wq); + mutex_init(&sii9234->lock); + mutex_init(&sii9234->msc_lock); + + ret = request_threaded_irq(client->irq, NULL, sii9234_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "sii9234", sii9234); + if (ret < 0) + goto err_exit2; + + disable_irq(client->irq); + + sii9234->otg_id_nb.detect = sii9234_detection_callback; + sii9234->otg_id_nb.cancel = sii9234_cancel_callback; + sii9234->otg_id_nb.priority = sii9234->pdata->prio; + + plist_node_init(&sii9234->otg_id_nb.p, sii9234->pdata->prio); + + ret = otg_id_register_notifier(&sii9234->otg_id_nb); + if (ret < 0) { + dev_err(&client->dev, "Unable to register notifier\n"); + goto err_exit2; + } + + return 0; + +err_exit2: +err_exit1: + kfree(sii9234); + return ret; +} + +static int __devinit sii9234_tpi_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sii9234_platform_data *pdata = client->dev.platform_data; + pdata->tpi_client = client; + return 0; +} + +static int __devinit sii9234_hdmi_rx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sii9234_platform_data *pdata = client->dev.platform_data; + pdata->hdmi_rx_client = client; + return 0; +} + +static int __devinit sii9234_cbus_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sii9234_platform_data *pdata = client->dev.platform_data; + pdata->cbus_client = client; + return 0; +} + +static int __devexit sii9234_mhl_tx_remove(struct i2c_client *client) +{ + return 0; +} + +static int __devexit sii9234_tpi_remove(struct i2c_client *client) +{ + return 0; +} + +static int __devexit sii9234_hdmi_rx_remove(struct i2c_client *client) +{ + return 0; +} + +static int __devexit sii9234_cbus_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id sii9234_mhl_tx_id[] = { + {"sii9234_mhl_tx", 0}, + {} +}; + +static const struct i2c_device_id sii9234_tpi_id[] = { + {"sii9234_tpi", 0}, + {} +}; + +static const struct i2c_device_id sii9234_hdmi_rx_id[] = { + {"sii9234_hdmi_rx", 0}, + {} +}; + +static const struct i2c_device_id sii9234_cbus_id[] = { + {"sii9234_cbus", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sii9234_mhl_tx_id); +MODULE_DEVICE_TABLE(i2c, sii9234_tpi_id); +MODULE_DEVICE_TABLE(i2c, sii9234_hdmi_rx_id); +MODULE_DEVICE_TABLE(i2c, sii9234_cbus_id); + +static struct i2c_driver sii9234_mhl_tx_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sii9234_mhl_tx", + }, + .id_table = sii9234_mhl_tx_id, + .probe = sii9234_mhl_tx_i2c_probe, + .remove = __devexit_p(sii9234_mhl_tx_remove), + .command = NULL, +}; + +static struct i2c_driver sii9234_tpi_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sii9234_tpi", + }, + .id_table = sii9234_tpi_id, + .probe = sii9234_tpi_i2c_probe, + .remove = __devexit_p(sii9234_tpi_remove), +}; + +static struct i2c_driver sii9234_hdmi_rx_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sii9234_hdmi_rx", + }, + .id_table = sii9234_hdmi_rx_id, + .probe = sii9234_hdmi_rx_i2c_probe, + .remove = __devexit_p(sii9234_hdmi_rx_remove), +}; + +static struct i2c_driver sii9234_cbus_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "sii9234_cbus", + }, + .id_table = sii9234_cbus_id, + .probe = sii9234_cbus_i2c_probe, + .remove = __devexit_p(sii9234_cbus_remove), +}; + +static int __init sii9234_init(void) +{ + int ret; + + ret = i2c_add_driver(&sii9234_mhl_tx_i2c_driver); + if (ret < 0) + return ret; + + ret = i2c_add_driver(&sii9234_tpi_i2c_driver); + if (ret < 0) + goto err_exit1; + + ret = i2c_add_driver(&sii9234_hdmi_rx_i2c_driver); + if (ret < 0) + goto err_exit2; + + ret = i2c_add_driver(&sii9234_cbus_i2c_driver); + if (ret < 0) + goto err_exit3; + + return 0; + +err_exit3: + i2c_del_driver(&sii9234_hdmi_rx_i2c_driver); +err_exit2: + i2c_del_driver(&sii9234_tpi_i2c_driver); +err_exit1: + i2c_del_driver(&sii9234_mhl_tx_i2c_driver); + return ret; +} + +static void __exit sii9234_exit(void) +{ + i2c_del_driver(&sii9234_cbus_i2c_driver); + i2c_del_driver(&sii9234_hdmi_rx_i2c_driver); + i2c_del_driver(&sii9234_tpi_i2c_driver); + i2c_del_driver(&sii9234_mhl_tx_i2c_driver); +} + +module_init(sii9234_init); +module_exit(sii9234_exit); |