blob: 59c3113dea4048bc314e9d5d9c5af4df3f9fdaae [file] [log] [blame]
/*
* Minnow DSI command mode panel
*
* Copyright (C) 2013-2014 Motorola Mobility LLC.
*
* 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/>.
*/
/*#define PANEL_DEBUG*/
#define PANEL_PERF_TIME
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/backlight.h>
#include <linux/fb.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/regulator/consumer.h>
#include <linux/clk.h>
#include <linux/wakelock.h>
#include <linux/leds.h>
#include <linux/alarmtimer.h>
#include <linux/m4sensorhub.h>
#include <linux/m4sensorhub/MemMapUserSettings.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
#include <video/mipi_display.h>
#include <linux/notifier.h>
#include <linux/wakeup_source_notify.h>
#include "../dss/dss.h"
#include "panel-minnow-common.h"
/* DSI Virtual channel. Hardcoded for now. */
#define TCH 0
#define DCS_READ_NUM_ERRORS 0x05
#define DCS_BRIGHTNESS 0x51
#define DCS_CTRL_DISPLAY 0x53
#define DCS_WRITE_CABC 0x55
#define DCS_READ_CABC 0x56
#define DCS_GET_ID1 0xda
#define DCS_GET_ID2 0xdb
#define DCS_GET_ID3 0xdc
#define DIM_BACKLIGHT_ALS 5
enum minnow_panel_component {
MINNOW_PANEL,
MINNOW_BRIDGE,
MINNOW_COMPONENT_MAX
};
enum minnow_panel_id {
MINNOW_PANEL_CM_220X176,
MINNOW_PANEL_CM_220X220,
MINNOW_PANEL_CM_BRIDGE_320X320,
MINNOW_PANEL_MAX
};
static u8 panel_init_220x176[] = {
/*n, type, data_0, data_1 ... data_n-1*/
1, DCS_WRITE_SYNC, MIPI_DCS_EXIT_SLEEP_MODE,
1, WAIT_MS, 5,
3, DCS_WRITE_SYNC, 0xF0, 0x5A, 0x5A,
3, DCS_WRITE_SYNC, 0xF1, 0x5A, 0x5A,
18, DCS_WRITE_SYNC, 0xF2, 0x16, 0xDC, 0x03, 0x28, 0x28, 0x10, 0x00, 0x60, 0xF8,
0x00, 0x07, 0x02, 0x00, 0x00, 0xDC, 0x28, 0x28,
15, DCS_WRITE_SYNC, 0xF4, 0x0A, 0x00, 0x00, 0x00, 0x77, 0x7F, 0x07, 0x22, 0x2A,
0x43, 0x07, 0x2A, 0x43, 0x07,
11, DCS_WRITE_SYNC, 0xF5, 0x00, 0x50, 0x28, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x00,
10, DCS_WRITE_SYNC, 0xF6, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x04, 0x04, 0x04, 0x07,
5, DCS_WRITE_SYNC, 0xF7, 0x00, 0x00, 0x00, 0x00,
3, DCS_WRITE_SYNC, 0xF8, 0x44, 0x08,
2, DCS_WRITE_SYNC, 0xF9, 0x04,
17, DCS_WRITE_SYNC, 0xFA, 0x0F, 0x0F, 0x1E, 0x23, 0x26, 0x2D, 0x21, 0x2B, 0x33,
0x32, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00,
17, DCS_WRITE_SYNC, 0xFB, 0x0F, 0x0F, 0x1E, 0x23, 0x26, 0x2D, 0x21, 0x2B, 0x33,
0x32, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00,
2, DCS_WRITE_SYNC, 0xF9, 0x02,
17, DCS_WRITE_SYNC, 0xFA, 0x00, 0x00, 0x0A, 0x16, 0x1D, 0x27, 0x1C, 0x30, 0x38,
0x37, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
17, DCS_WRITE_SYNC, 0xFB, 0x00, 0x00, 0x0A, 0x16, 0x1D, 0x27, 0x1C, 0x30, 0x38,
0x37, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
2, DCS_WRITE_SYNC, 0xF9, 0x01,
17, DCS_WRITE_SYNC, 0xFA, 0x00, 0x00, 0x13, 0x14, 0x19, 0x24, 0x1A, 0x31, 0x39,
0x38, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00,
17, DCS_WRITE_SYNC, 0xFB, 0x00, 0x00, 0x13, 0x14, 0x19, 0x24, 0x1A, 0x31, 0x39,
0x38, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00,
3, DCS_WRITE_SYNC, 0xF0, 0x00, 0x00,
3, DCS_WRITE_SYNC, 0xF1, 0x00, 0x00,
2, DCS_WRITE_SYNC, 0x36, 0xD8,
2, DCS_WRITE_SYNC, 0x3A, 0x06,
0
};
static u8 panel_init_220x220[] = {
/*n, type, data_0, data_1 ... data_n-1*/
1, DCS_WRITE_SYNC, MIPI_DCS_EXIT_SLEEP_MODE,
1, WAIT_MS, 5,
3, DCS_WRITE_SYNC, 0xF0, 0x5A, 0x5A,
3, DCS_WRITE_SYNC, 0xF1, 0x5A, 0x5A,
18, DCS_WRITE_SYNC, 0xF2, 0x1C, 0xDC, 0x03, 0x28, 0x28, 0x10, 0x00, 0x60, 0xF8,
0x00, 0x07, 0x02, 0x00, 0x00, 0xDC, 0x28, 0x28,
15, DCS_WRITE_SYNC, 0xF4, 0x0A, 0x00, 0x00, 0x00, 0x77, 0x7F, 0x07, 0x22, 0x2A,
0x43, 0x07, 0x2A, 0x43, 0x07,
11, DCS_WRITE_SYNC, 0xF5, 0x00, 0x50, 0x28, 0x00, 0x00, 0x09, 0x00, 0x00, 0x01,
0x01,
10, DCS_WRITE_SYNC, 0xF6, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x04, 0x04, 0x04, 0x07,
5, DCS_WRITE_SYNC, 0xF7, 0x00, 0x00, 0x00, 0x00,
3, DCS_WRITE_SYNC, 0xF8, 0x44, 0x02,
2, DCS_WRITE_SYNC, 0xF9, 0x04,
17, DCS_WRITE_SYNC, 0xFA, 0x1E, 0x1E, 0x0D, 0x1D, 0x21, 0x2C, 0x23, 0x28, 0x2C,
0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00,
17, DCS_WRITE_SYNC, 0xFB, 0x1E, 0x1E, 0x0D, 0x1D, 0x21, 0x2C, 0x23, 0x28, 0x2C,
0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00,
2, DCS_WRITE_SYNC, 0xF9, 0x02,
17, DCS_WRITE_SYNC, 0xFA, 0x19, 0x18, 0x08, 0x0F, 0x18, 0x26, 0x1E, 0x2C, 0x30,
0x2C, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00,
17, DCS_WRITE_SYNC, 0xFB, 0x19, 0x18, 0x08, 0x0F, 0x18, 0x26, 0x1E, 0x2C, 0x30,
0x2C, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00,
2, DCS_WRITE_SYNC, 0xF9, 0x01,
17, DCS_WRITE_SYNC, 0xFA, 0x19, 0x19, 0x09, 0x0D, 0x12, 0x21, 0x1B, 0x2E, 0x31,
0x2E, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00,
17, DCS_WRITE_SYNC, 0xFB, 0x19, 0x19, 0x09, 0x0D, 0x12, 0x21, 0x1B, 0x2E, 0x31,
0x2E, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00,
3, DCS_WRITE_SYNC, 0xF0, 0x00, 0x00,
3, DCS_WRITE_SYNC, 0xF1, 0x00, 0x00,
2, DCS_WRITE_SYNC, 0x36, 0xD8,
2, DCS_WRITE_SYNC, 0x3A, 0x06,
0
};
static u8 panel_off_common[] = {
/*n, type, data_0, data_1 ... data_n-1*/
1, DCS_WRITE_SYNC, MIPI_DCS_SET_DISPLAY_OFF,
1, DCS_WRITE, MIPI_DCS_ENTER_SLEEP_MODE,
1, WAIT_MS, 20,
0
};
#define BRIDGE_WIDTH 320
#define BRIDGE_HEIGHT 320
#define PANEL_WIDTH BRIDGE_WIDTH
#define PANEL_HEIGHT 290
#define UNUSED_LINES (BRIDGE_HEIGHT-PANEL_HEIGHT)
#define DRIVER_NAME "minnow-panel"
struct minnow_panel_clk_range {
int min;
int max;
};
enum minnow_panel_active_level {
ACTIVE_LOW = 0,
ACTIVE_HIGH,
ACTIVE_MAX
};
struct minnow_panel_hw_reset {
enum minnow_panel_active_level active;
int reset_ms;
int wait_ms;
};
struct minnow_panel_cmd_buf {
int count;
u8 *cmdbuf;
};
struct minnow_panel_attr {
int mode;
int xres;
int yres;
int pixel_clock;
int pixel_format;
int xoffset;
int yoffset;
struct minnow_panel_cmd_buf power_on;
struct minnow_panel_cmd_buf power_off;
struct minnow_panel_clk_range hs;
struct minnow_panel_clk_range lp;
struct minnow_panel_hw_reset panel_reset;
struct minnow_panel_hw_reset bridge_reset;
};
#define INIT_CMD_BUF(type, buf) .power_##type = {\
.count = sizeof(buf), .cmdbuf = (buf) }
static struct minnow_panel_attr panel_attr_table[MINNOW_PANEL_MAX] = {
[MINNOW_PANEL_CM_220X176] = {
.mode = OMAP_DSS_DSI_CMD_MODE,
.xres = 220,
.yres = 176,
.pixel_clock = 4608,
.pixel_format = OMAP_DSS_DSI_FMT_RGB666,
.xoffset = 0x32,
.yoffset = 0,
INIT_CMD_BUF(on, panel_init_220x176),
INIT_CMD_BUF(off, panel_off_common),
.hs = { 100000000, 150000000 },
.lp = { 7000000, 9000000 },
.panel_reset = { ACTIVE_LOW, 1, 5 },
.bridge_reset = { ACTIVE_LOW, 0, 0 },
},
[MINNOW_PANEL_CM_220X220] = {
.mode = OMAP_DSS_DSI_CMD_MODE,
.xres = 220,
.yres = 220,
.pixel_clock = 4608,
.pixel_format = OMAP_DSS_DSI_FMT_RGB666,
.xoffset = 0x32,
.yoffset = 0x4,
INIT_CMD_BUF(on, panel_init_220x220),
INIT_CMD_BUF(off, panel_off_common),
.hs = { 100000000, 150000000 },
.lp = { 7000000, 9000000 },
.panel_reset = { ACTIVE_LOW, 1, 5 },
.bridge_reset = { ACTIVE_LOW, 0, 0 },
},
[MINNOW_PANEL_CM_BRIDGE_320X320] = {
.mode = OMAP_DSS_DSI_CMD_MODE,
.xres = PANEL_WIDTH,
.yres = PANEL_HEIGHT,
.pixel_clock = DIV_ROUND_UP(PANEL_WIDTH *
PANEL_HEIGHT * 45, 1000),
.pixel_format = OMAP_DSS_DSI_FMT_RGB888,
.xoffset = 0,
.yoffset = 0,
INIT_CMD_BUF(on, panel_init_ssd2848_320x320),
INIT_CMD_BUF(off, panel_off_ssd2848_320x320),
.hs = { 104000000, 150000000 },
.lp = { 7000000, 9000000 },
.panel_reset = { ACTIVE_LOW, 5, 10 },
.bridge_reset = { ACTIVE_LOW, 20, 10 }
},
};
#ifdef PANEL_PERF_TIME
#define GET_ELAPSE_TIME(last) jiffies_to_msecs((unsigned long)jiffies-last)
#endif
enum display_state {
DISPLAY_DISABLE = SCREEN_STATUS_NORMAL_OFF,
DISPLAY_ENABLE = SCREEN_STATUS_NORMAL_ON,
#ifdef CONFIG_HAS_AMBIENTMODE
DISPLAY_AMBIENT_OFF = SCREEN_STATUS_AMBIENT_OFF,
DISPLAY_AMBIENT_ON = SCREEN_STATUS_AMBIENT_ON,
#endif
};
struct minnow_panel_data {
struct mutex lock; /* mutex */
/* wake_lock for common function, it should be used in same thread */
struct wake_lock wake_lock;
/* wake_lock for update function, it's used in different thread */
struct wake_lock update_wake_lock;
struct omap_dss_device *dssdev;
/* panel HW configuration from DT or platform data */
int reset_gpio[MINNOW_COMPONENT_MAX];
int ext_te_gpio;
int vio_en_gpio;
struct pinctrl *vio_pctrl;
struct pinctrl_state *vio_state_output;
struct pinctrl_state *vio_state_pulldown;
int mem_en_gpio;
struct minnow_panel_hw_reset hw_reset[MINNOW_COMPONENT_MAX];
struct regulator *regulators[MINNOW_COMPONENT_MAX];
struct clk *clk_in;
bool clk_in_en;
#ifdef CONFIG_PANEL_BACKLIGHT
bool use_dsi_backlight;
struct backlight_device *bldev;
#endif
struct omap_dsi_pin_config pin_config;
struct omap_dss_dsi_config dsi_config;
struct minnow_panel_cmd_buf power_on;
struct minnow_panel_cmd_buf power_off;
u8 *last_init_data;
int id_panel;
int x_offset;
int y_offset;
int xres_um;
int yres_um;
int reset_ms;
int release_ms;
/* runtime variables */
enum display_state state;
enum display_state m4_state;
bool enabled;
bool interactive;
bool output_enabled;
enum minnow_panel_type panel_type;
bool te_enabled;
atomic_t do_update;
int channel;
struct delayed_work te_timeout_work;
#ifdef PANEL_DEBUG
unsigned cabc_mode;
#endif
bool first_enable;
bool skip_first_init;
int panel_retry_count;
int esd_errors;
struct workqueue_struct *workqueue;
struct delayed_work esd_work;
unsigned esd_interval;
#ifdef CONFIG_HAS_AMBIENTMODE
struct timespec esd_start_time;
#endif
bool ulps_enabled;
unsigned ulps_timeout;
struct delayed_work ulps_work;
int total_update;
int total_error;
int total_esd_reset;
#ifdef PANEL_PERF_TIME
unsigned long time_power_on;
unsigned long time_ulps;
unsigned long time_update;
unsigned long time_update_min;
unsigned long time_update_max;
unsigned long last_power_on;
unsigned long last_ulps;
unsigned long last_update;
#endif
int vsync_events_gpio;
struct sysfs_dirent *vsync_events_sysfs;
bool vsync_events_enabled;
ktime_t vsync_events_timestamp;
#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY
bool early_inited;
enum display_state last_state;
struct notifier_block displayenable_nb;
struct work_struct early_init_work;
struct delayed_work early_init_timeout_work;
#endif /* CONFIG_WAKEUP_SOURCE_NOTIFY */
#ifdef CONFIG_HAS_AMBIENTMODE
struct completion resume_completion;
struct work_struct dock_work;
struct work_struct ambient_wake_work;
struct alarm ambient_timeout_alarm;
bool smart_ambient;
int ambient_timeout; /* time out in seconds */
struct work_struct ambient_timeout_work;
bool is_docked;
bool is_gesture_view_on;
#endif
};
#define DECLARE_MPD_FROM_CONTAINER(ptr, member) \
struct minnow_panel_data *mpd = \
container_of(ptr, struct minnow_panel_data, member)
/* panel parameter passed from boot-loader */
static char *def_panel_param;
module_param_named(panel_param, def_panel_param, charp, 0);
static irqreturn_t minnow_panel_te_isr(int irq, void *data);
static void minnow_panel_te_timeout_work_callback(struct work_struct *work);
static int _minnow_panel_enable_te(struct minnow_panel_data *mpd, bool enable);
static int minnow_panel_wake_up_locked(struct minnow_panel_data *mpd);
static void minnow_panel_framedone_cb(int err, void *data);
static int minnow_panel_enable_locked(struct minnow_panel_data *mpd);
static void minnow_panel_disable_locked(struct minnow_panel_data *mpd,
bool fast_power_off);
static int minnow_panel_update_locked(struct minnow_panel_data *mpd);
static void minnow_panel_esd_work(struct work_struct *work);
static void minnow_panel_ulps_work(struct work_struct *work);
static int minnow_panel_enable_mlocked(struct minnow_panel_data *mpd);
static void minnow_panel_disable_mlocked(struct minnow_panel_data *mpd);
static int minnow_panel_change_state_mlocked(struct minnow_panel_data *mpd,
int state);
static void minnow_panel_sync_resume_mlocked(struct minnow_panel_data *mpd)
{
#if defined(CONFIG_HAS_AMBIENTMODE)
/* check if there is already resumed */
if (completion_done(&mpd->resume_completion))
return;
/* wait 500ms for resume completed */
if (wait_for_completion_timeout(&mpd->resume_completion,
msecs_to_jiffies(500)))
return;
WARN(1, "%s: failed sync with resume\n", __func__);
#else
(void)mpd;
#endif
}
#ifdef CONFIG_HAS_AMBIENTMODE
/* the smart ambient feature enabled when
* 1) support_smart_ambient
* 2) and it's not on dock
*/
#define is_smart_ambient_feature_enabled(mpd) \
(mpd->smart_ambient && !mpd->is_docked)
/* the smart ambient timeout enabled when
* 1) support_smart_ambient
* 2) and defined ambient timeout
* 3) and it's not on dock
*/
#define is_smart_ambient_timeout_enabled(mpd) \
(mpd->smart_ambient && mpd->ambient_timeout && !mpd->is_docked)
#endif
#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY
static char *action_to_str(unsigned long action)
{
switch (action) {
case DISPLAY_WAKE_EVENT_POWERKEY:
return "power_key";
case DISPLAY_WAKE_EVENT_TOUCH:
return "touch";
case DISPLAY_WAKE_EVENT_GESTURE:
return "gesture_wrist";
case DISPLAY_WAKE_EVENT_GESTURE_VIEWON:
return "gesture_view_on";
case DISPLAY_WAKE_EVENT_GESTURE_VIEWOFF:
return "gesture_view_off";
case DISPLAY_WAKE_EVENT_DOCKON:
return "dock_on";
case DISPLAY_WAKE_EVENT_DOCKOFF:
return "dock_off";
}
return "unsupported";
}
static int omapdss_displayenable_notify(struct notifier_block *self,
unsigned long action, void *dev)
{
DECLARE_MPD_FROM_CONTAINER(self, displayenable_nb);
/* don't case non-display wakeup event */
if (GET_WAKEUP_EVENT_TYPE(action) != WAKEUP_DISPLAY)
return NOTIFY_OK;
dev_info(&mpd->dssdev->dev, "%s, action is %lu-%s",
__func__, action, action_to_str(action));
switch (action) {
case DISPLAY_WAKE_EVENT_POWERKEY:
case DISPLAY_WAKE_EVENT_TOUCH:
case DISPLAY_WAKE_EVENT_GESTURE:
/* Queue work to early enable the display */
queue_work(mpd->workqueue, &mpd->early_init_work);
break;
case DISPLAY_WAKE_EVENT_GESTURE_VIEWON:
case DISPLAY_WAKE_EVENT_GESTURE_VIEWOFF:
#ifdef CONFIG_HAS_AMBIENTMODE
mpd->is_gesture_view_on =
action == DISPLAY_WAKE_EVENT_GESTURE_VIEWON;
/* Queue work to enable the smart ambient display mode */
if (is_smart_ambient_feature_enabled(mpd))
queue_work(mpd->workqueue, &mpd->ambient_wake_work);
#endif
break;
case DISPLAY_WAKE_EVENT_DOCKON:
case DISPLAY_WAKE_EVENT_DOCKOFF:
#ifdef CONFIG_HAS_AMBIENTMODE
mpd->is_docked = action == DISPLAY_WAKE_EVENT_DOCKON;
/* Queue work to dock the display */
queue_work(mpd->workqueue, &mpd->dock_work);
#endif
break;
default:
dev_err(&mpd->dssdev->dev,
"%s: ignore unknown action(%lu)!\n", __func__, action);
break;
}
return NOTIFY_OK;
}
static void minnow_panel_early_init_func(struct work_struct *work)
{
DECLARE_MPD_FROM_CONTAINER(work, early_init_work);
int r;
mutex_lock(&mpd->lock);
if (!mpd->enabled) {
/* record last state for later switch back */
if (!mpd->early_inited) {
if (mpd->state == DISPLAY_AMBIENT_OFF)
mpd->last_state = DISPLAY_AMBIENT_OFF;
else
mpd->last_state = DISPLAY_DISABLE;
}
r = minnow_panel_change_state_mlocked(mpd, DISPLAY_ENABLE);
if (r) {
dev_err(&mpd->dssdev->dev,
"%s: minnow_panel_enable failed: %d\n",
__func__, r);
} else {
/* it will turn off display if it's not enabled
* by android within 500ms or kernel suspend
*/
mpd->early_inited = true;
queue_delayed_work(mpd->workqueue,
&mpd->early_init_timeout_work,
msecs_to_jiffies(500));
}
}
mutex_unlock(&mpd->lock);
}
static void minnow_panel_early_init_timeout_func(struct work_struct *work)
{
DECLARE_MPD_FROM_CONTAINER(work, early_init_timeout_work.work);
mutex_lock(&mpd->lock);
if (mpd->early_inited) {
/* switch back to last state when early init been called */
minnow_panel_change_state_mlocked(mpd, mpd->last_state);
dev_dbg(&mpd->dssdev->dev, "%s: cancelled previous early"
" initialize works\n", __func__);
}
mutex_unlock(&mpd->lock);
}
#endif /* CONFIG_WAKEUP_SOURCE_NOTIFY */
#ifdef CONFIG_HAS_AMBIENTMODE
static void minnow_panel_dock_func(struct work_struct *work)
{
DECLARE_MPD_FROM_CONTAINER(work, dock_work);
bool update_state = false;
/* to handler DOCK event is for blocking the smart ambient
* timeout when it's on dock, so the only thing is needed
* just update state to DISPLAY_AMBIENT_ON as:
* 1) when ambient timeout (DISPLAY_AMBIENT_OFF), put device
* dock, need wake up it to ambient mode and never timeout
* 2) when it's on ambient mode, leave device from dock, set
* DISPLAY_AMBIENT_ON that will restart ambient timeout
* for all other cases, just ignore this event
*/
mutex_lock(&mpd->lock);
if (mpd->is_docked) {
if (mpd->state == DISPLAY_AMBIENT_OFF)
update_state = true;
} else {
if (mpd->state == DISPLAY_AMBIENT_ON)
update_state = true;
}
if (update_state)
minnow_panel_change_state_mlocked(mpd, DISPLAY_AMBIENT_ON);
mutex_unlock(&mpd->lock);
}
static void minnow_panel_ambient_wake_func(struct work_struct *work)
{
DECLARE_MPD_FROM_CONTAINER(work, ambient_wake_work);
mutex_lock(&mpd->lock);
/* we should only care view gesture when it's in ambient mode */
if ((mpd->state == DISPLAY_AMBIENT_ON) ||
(mpd->state == DISPLAY_AMBIENT_OFF)) {
int state = mpd->is_gesture_view_on
? DISPLAY_AMBIENT_ON : DISPLAY_AMBIENT_OFF;
minnow_panel_change_state_mlocked(mpd, state);
}
mutex_unlock(&mpd->lock);
}
static enum alarmtimer_restart minnow_panel_ambient_alarm_callback(
struct alarm *alarm, ktime_t now)
{
DECLARE_MPD_FROM_CONTAINER(alarm, ambient_timeout_alarm);
dev_dbg(&mpd->dssdev->dev, "%s: turn off display\n", __func__);
/* Queue work to turn off the display */
queue_work(mpd->workqueue, &(mpd->ambient_timeout_work));
return ALARMTIMER_NORESTART;
}
static void minnow_panel_ambient_timeout_func(struct work_struct *work)
{
DECLARE_MPD_FROM_CONTAINER(work, ambient_timeout_work);
mutex_lock(&mpd->lock);
minnow_panel_change_state_mlocked(mpd, DISPLAY_AMBIENT_OFF);
mutex_unlock(&mpd->lock);
}
static void minnow_panel_start_ambient_alarm(struct minnow_panel_data *mpd)
{
alarm_cancel(&mpd->ambient_timeout_alarm);
if (is_smart_ambient_timeout_enabled(mpd))
alarm_start_relative(&mpd->ambient_timeout_alarm,
ktime_set(mpd->ambient_timeout, 0));
}
enum refresh_rate {
REFRESH_RATE_30HZ,
REFRESH_RATE_45HZ,
REFRESH_RATE_60HZ,
};
#define minnow_panel_set_lowest_fps(mpd) \
minnow_panel_set_refresh_rate_mlocked(mpd, REFRESH_RATE_30HZ)
#define minnow_panel_set_default_fps(mpd) \
minnow_panel_set_refresh_rate_mlocked(mpd, REFRESH_RATE_45HZ)
#define minnow_panel_set_dock_fps(mpd) \
minnow_panel_set_refresh_rate_mlocked(mpd, REFRESH_RATE_60HZ)
static int minnow_panel_set_refresh_rate_mlocked(struct minnow_panel_data *mpd,
enum refresh_rate rate)
{
static u8 ssd2848_vtcm_pcfrr[][6] = {
[REFRESH_RATE_30HZ] = {0x20, 0x10, 0x00, 0xEF, 0x00, 0x34},
[REFRESH_RATE_45HZ] = {0x20, 0x10, 0x00, 0x5F, 0x00, 0x1F},
[REFRESH_RATE_60HZ] = {0x20, 0x10, 0x00, 0xF7, 0x00, 0x6C},
};
int r = 0;
if (mpd->panel_type < OTM3201_2_0)
return r;
dsi_bus_lock(mpd->dssdev);
r = minnow_panel_wake_up_locked(mpd);
if (!r)
r = dsi_vc_generic_write(mpd->dssdev, mpd->channel,
ssd2848_vtcm_pcfrr[rate], 6);
dsi_bus_unlock(mpd->dssdev);
if (r)
dev_err(&mpd->dssdev->dev,
"Failed to set refresh rate(%d)\n", rate);
return r;
}
#endif /* CONFIG_HAS_AMBIENTMODE */
#ifdef CONFIG_OMAP2_DSS_DEBUGFS
static void minnow_panel_dump_regs(struct seq_file *s)
{
static struct {char name[8]; int addr; int endreg; } regs[] = {
{"SCM", 0x0000, 0x30},
{"MIPIRX", 0x1000, 0x30},
{"VTCM", 0x2000, 0xB4},
{"VCU", 0x4000, 0x20},
{"GPIO", 0x5000, 0x04},
{"MIPITX", 0x6000, 0x54},
{"TX-DSI0", 0x6080, 0x14},
};
struct omap_dss_output *out = omap_dss_get_output(OMAP_DSS_OUTPUT_DSI1);
struct omap_dss_device *dssdev = out->device;
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
int i, j, r;
mutex_lock(&mpd->lock);
if (!mpd->enabled) {
seq_puts(s, "display is disabled!");
goto exit1;
}
dsi_bus_lock(dssdev);
r = minnow_panel_wake_up_locked(mpd);
if (r) {
seq_printf(s, "display wake up failed(%d)!\n", r);
goto exit;
}
if (mpd->dssdev != dssdev) {
seq_puts(s, "dssdev mis-matched!");
goto exit;
}
if (dsi_vc_set_max_rx_packet_size(dssdev, mpd->channel, 4)) {
seq_puts(s, "failed set max rx_packet_size 4");
goto exit;
}
for (i = 0; i < sizeof(regs)/sizeof(regs[0]); i++) {
seq_printf(s, "%s Registers:\n", regs[i].name);
for (j = 0; j <= regs[i].endreg; j += 4) {
u8 reg[4];
u16 addr = j + regs[i].addr;
seq_printf(s, " %04X: ", addr);
r = dsi_vc_generic_read_2(dssdev, mpd->channel,
addr>>8, addr&0xFF, reg, 4);
if (r)
seq_printf(s, "read failed ret = %d\n", r);
else
seq_printf(s, "%02X%02X%02X%02X\n",
reg[0], reg[1], reg[2], reg[3]);
}
}
dsi_vc_set_max_rx_packet_size(dssdev, mpd->channel, 1);
exit:
dsi_bus_unlock(dssdev);
exit1:
mutex_unlock(&mpd->lock);
}
#endif
static void minnow_panel_delay(int delay_ms)
{
if (delay_ms > 5)
msleep(delay_ms);
else
usleep_range(1000 * delay_ms, 1000 * delay_ms + 100);
}
static int panel_ssd2848_set_retransmit(struct minnow_panel_data *mpd,
int enable)
{
u8 data[2] = {0xFF, enable ? 0x01 : 0x00};
return dsi_vc_generic_write(mpd->dssdev, mpd->channel, data, 2);
}
static int panel_ssd2848_read_reg(struct minnow_panel_data *mpd,
u16 addr, u8 *read)
{
int r;
dsi_vc_set_max_rx_packet_size(mpd->dssdev, mpd->channel, 4);
r = dsi_vc_generic_read_2(mpd->dssdev, mpd->channel,
addr>>8, addr&0xFF, read, 4);
dsi_vc_set_max_rx_packet_size(mpd->dssdev, mpd->channel, 1);
return r;
}
static int panel_otm3201_read_reg(struct minnow_panel_data *mpd,
u8 addr, u8 *read, u8 len)
{
int r;
dsi_vc_set_max_rx_packet_size(mpd->dssdev, mpd->channel, len);
r = dsi_vc_dcs_read(mpd->dssdev, mpd->channel, addr, read, len);
dsi_vc_set_max_rx_packet_size(mpd->dssdev, mpd->channel, 1);
return r;
}
#define WRITE_OTM3201(mpd, cmd) \
dsi_vc_dcs_write(mpd->dssdev, mpd->channel, cmd, sizeof(cmd))
static int panel_otm3201_rewrite_reg(struct minnow_panel_data *mpd,
u8 *data, u8 len, u8 *read)
{
/* retry to write only when it could read back */
int r, retry;
for (retry = 3; retry--; ) {
r = WRITE_OTM3201(mpd, otm3201_eng_mode);
if (r)
break;
r = WRITE_OTM3201(mpd, otm3201_write_mode);
if (r)
break;
r = dsi_vc_dcs_write(mpd->dssdev, mpd->channel, data, len);
if (r)
break;
r = WRITE_OTM3201(mpd, otm3201_read_mode);
if (r)
break;
r = panel_otm3201_read_reg(mpd, data[0], read, len-1);
if (r)
break;
/* special register B5h */
if (data[0] == 0xB5)
read[3] |= data[4]&0x80;
if (!memcmp(read, data+1, len-1))
break;
}
return r;
}
static int minnow_panel_dcs_read_1(struct minnow_panel_data *mpd,
u8 dcs_cmd, u8 *data)
{
int r;
u8 buf[1];
r = dsi_vc_dcs_read(mpd->dssdev, mpd->channel, dcs_cmd, buf, 1);
if (r < 0)
return r;
*data = buf[0];
return 0;
}
static int minnow_panel_dcs_write_0(struct minnow_panel_data *mpd, u8 dcs_cmd)
{
return dsi_vc_dcs_write(mpd->dssdev, mpd->channel, &dcs_cmd, 1);
}
static int minnow_panel_dcs_write_1(struct minnow_panel_data *mpd,
u8 dcs_cmd, u8 param)
{
u8 buf[2];
buf[0] = dcs_cmd;
buf[1] = param;
return dsi_vc_dcs_write(mpd->dssdev, mpd->channel, buf, 2);
}
static int minnow_panel_get_id(struct minnow_panel_data *mpd,
u8 *id1, u8 *id2, u8 *id3)
{
int r;
if (mpd->id_panel == MINNOW_PANEL_CM_BRIDGE_320X320) {
u8 data[4];
r = panel_ssd2848_read_reg(mpd, 0, data);
if (!r) {
*id1 = data[0];
*id2 = data[1];
*id3 = mpd->panel_type;
}
return r;
}
r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID1, id1);
if (r)
return r;
r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID2, id2);
if (r)
return r;
r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID3, id3);
if (r)
return r;
return 0;
}
static int minnow_panel_set_update_window(struct minnow_panel_data *mpd,
u16 x, u16 y, u16 w, u16 h)
{
int r;
u16 x1 = x + mpd->x_offset;
u16 x2 = x + mpd->x_offset + w - 1;
u16 y1 = y + mpd->y_offset;
u16 y2 = y + mpd->y_offset + h - 1;
u8 buf[5];
buf[0] = MIPI_DCS_SET_COLUMN_ADDRESS;
buf[1] = (x1 >> 8) & 0xff;
buf[2] = (x1 >> 0) & 0xff;
buf[3] = (x2 >> 8) & 0xff;
buf[4] = (x2 >> 0) & 0xff;
r = dsi_vc_dcs_write_nosync(mpd->dssdev, mpd->channel,
buf, sizeof(buf));
if (!r) {
buf[0] = MIPI_DCS_SET_PAGE_ADDRESS;
buf[1] = (y1 >> 8) & 0xff;
buf[2] = (y1 >> 0) & 0xff;
buf[3] = (y2 >> 8) & 0xff;
buf[4] = (y2 >> 0) & 0xff;
r = dsi_vc_dcs_write_nosync(mpd->dssdev, mpd->channel,
buf, sizeof(buf));
if (!r)
r = dsi_vc_send_bta_sync(mpd->dssdev, mpd->channel);
}
return r;
}
/* since SSD2848 frame buffer is 320 x 320, but the actual panel is 320 x 290
* it needs clear the unused bottom 30 lines for saving power
*/
static int minnow_panel_clear_bottom_line(struct minnow_panel_data *mpd,
unsigned int delay_ms)
{
int r = 0;
#if UNUSED_LINES
unsigned int last_ms = jiffies_to_msecs(jiffies);
int plen, total;
u8 buf[124]; /* maximum packet size */
memset(buf, 0, sizeof(buf));
buf[0] = MIPI_DCS_WRITE_MEMORY_START;
r = panel_ssd2848_set_retransmit(mpd, false);
if (r)
return r;
omapdss_dsi_vc_enable_hs(mpd->dssdev, mpd->channel, true);
plen = dsi_get_pixel_size(mpd->dssdev->panel.dsi_pix_fmt);
plen = DIV_ROUND_UP(plen, 8);
total = PANEL_WIDTH * UNUSED_LINES * plen;
plen = (sizeof(buf) - 1) / plen * plen;
r = minnow_panel_set_update_window(mpd, 0, PANEL_HEIGHT,
PANEL_WIDTH, UNUSED_LINES);
for (; total && !r; buf[0] = MIPI_DCS_WRITE_MEMORY_CONTINUE) {
if (plen > total)
plen = total;
total -= plen;
r = dsi_vc_dcs_write(mpd->dssdev, mpd->channel, buf, plen+1);
}
if (!r)
r = minnow_panel_set_update_window(mpd, 0, 0,
mpd->dssdev->panel.timings.x_res,
mpd->dssdev->panel.timings.y_res);
omapdss_dsi_vc_enable_hs(mpd->dssdev, mpd->channel, false);
if (r) {
/* waiting for the reset of time */
last_ms = jiffies_to_msecs(jiffies) - last_ms;
if (last_ms < delay_ms)
minnow_panel_delay(delay_ms - last_ms);
}
#else
minnow_panel_delay(delay_ms);
#endif
return r;
}
static int minnow_panel_process_cmd(struct minnow_panel_data *mpd,
u8 cmd, u8 *data, int len, bool retrans)
{
int r = -EINVAL;
if (retrans) {
r = panel_ssd2848_set_retransmit(mpd, true);
if (r)
return r;
}
switch (cmd) {
case DCS_WRITE_SYNC:
case OTM3201_CMD:
r = dsi_vc_dcs_write(mpd->dssdev,
mpd->channel, data, len);
break;
case GENERIC_WRITE_SYNC:
case SSD2848_CMD:
r = dsi_vc_generic_write(mpd->dssdev,
mpd->channel, data, len);
break;
case DCS_WRITE:
r = dsi_vc_dcs_write_nosync(mpd->dssdev,
mpd->channel, data, len);
break;
case GENERIC_WRITE:
r = dsi_vc_generic_write_nosync(mpd->dssdev,
mpd->channel, data, len);
break;
case BTA_SYNC:
r = dsi_vc_send_bta_sync(mpd->dssdev, mpd->channel);
break;
case WAIT_MS:
r = (int)(len == 1 ? (u32)(*data) : *(u16 *)data);
/* It needs to clean bottom line for Solomon+Orise,
* and to save time it uses the waiting time(120 ms) of
* Orise's initialization process
*/
if ((r > 100) &&
(mpd->id_panel == MINNOW_PANEL_CM_BRIDGE_320X320))
r = minnow_panel_clear_bottom_line(mpd, r);
else {
minnow_panel_delay(r);
r = 0;
}
break;
default:
r = -EINVAL;
}
if (retrans && !r)
r = panel_ssd2848_set_retransmit(mpd, false);
return r;
}
static int minnow_panel_detect_type(struct minnow_panel_data *mpd)
{
u8 rev;
int r = panel_otm3201_read_reg(mpd, 0xB0, &rev, sizeof(rev));
if (!r) {
switch (rev) {
case 0x12:
mpd->panel_type = OTM3201_1_0;
break;
case 0x1A:
mpd->panel_type = OTM3201_2_0;
break;
case 0x1C:
default:
mpd->panel_type = OTM3201_2_1;
break;
}
}
return r;
}
static void print_reg_mismatch(struct device *dev, u8 *src, u8 *read, int len)
{
char *pstr, str[100];
int i;
for (i = 0, pstr = str; i < len; i++, pstr += 3)
sprintf(pstr, " %02X", src[i]);
dev_dbg(dev, " Init:%s\n", str);
for (i = 0, pstr = str; i < len; i++, pstr += 3)
sprintf(pstr, " %02X", read[i]);
dev_dbg(dev, " Read:%s\n", str);
}
static int minnow_panel_verify_ssd2848(struct minnow_panel_data *mpd,
u8 *cmdbuf)
{
u8 *data, read[4];
int r = panel_ssd2848_set_retransmit(mpd, false);
for (data = cmdbuf; *data && !r; data += *data+2) {
u16 addr;
if (data[1] != SSD2848_CMD)
continue;
if (data[2] == CMD_VERIFY_REG)
break;
addr = ((u16)data[2]<<8) | data[3];
r = panel_ssd2848_read_reg(mpd, addr, read);
if (r)
break;
if (!memcmp(&read, data+4, 4))
continue;
dev_err(&mpd->dssdev->dev, "Failed verify ssd2848"
" register: %04X\n", addr);
print_reg_mismatch(&mpd->dssdev->dev, data+4, read, 4);
break;
}
return r;
}
static bool _minnow_panel_replace_cmdbuf(u8 *cmdbuf, u8 *data)
{
for (; *cmdbuf; cmdbuf += *cmdbuf+2) {
if ((data[0] != cmdbuf[0]) || (data[1] != cmdbuf[1]))
continue;
if (data[1] == SSD2848_CMD) {
if ((data[2] != cmdbuf[2]) || (data[3] != cmdbuf[3]))
continue;
memcpy(cmdbuf+4, data+4, 4);
return true;
} else if (data[1] == OTM3201_CMD) {
if (data[2] != cmdbuf[2])
continue;
memcpy(cmdbuf+3, data+3, cmdbuf[0]-1);
return true;
}
}
return false;
}
static bool minnow_panel_replace_cmdbuf(u8 *cmdbuf, u8 *data)
{
bool ret = false;
for (; *data; data += *data+2) {
if (_minnow_panel_replace_cmdbuf(cmdbuf, data))
ret = true;
}
return ret;
}
static int minnow_panel_verify_otm3201(struct minnow_panel_data *mpd,
u8 *cmdbuf)
{
u8 *data, addr, read[20];
int r = panel_ssd2848_set_retransmit(mpd, true);
for (data = cmdbuf; *data && !r; data += *data+2) {
if (data[1] != OTM3201_CMD)
continue;
addr = data[2];
if (addr == CMD_VERIFY_REG) {
break;
} else if (addr == 0xF0) {
/* resend command to make sure enable engineer mode */
r = dsi_vc_dcs_write(mpd->dssdev, mpd->channel,
data+2, data[0]);
continue;
} else if (addr == 0xA0) {
/* for initialization, it needs set write mode,
* but for verification, it needs set read mode
*/
r = WRITE_OTM3201(mpd, otm3201_read_mode);
if (!r && (mpd->panel_type == PANEL_INIT)) {
r = minnow_panel_detect_type(mpd);
if (r)
break;
if (mpd->panel_type != OTM3201_1_0)
continue;
/* turn of ESD for panel 1.0 */
mpd->esd_interval = 0;
/* it needs replace some settings for 1.0 */
if (minnow_panel_replace_cmdbuf
(cmdbuf, panel_init_ssd2848_320x320_1)) {
dev_info(&mpd->dssdev->dev,
"Force reset for new settings"
" of panel 1.0!\n");
mpd->panel_retry_count = 0;
mpd->total_error--;
r = -EIO;
break;
}
}
continue;
}
r = panel_otm3201_read_reg(mpd, addr, read, data[0]-1);
if (r)
break;
/* special register B5h */
if (addr == 0xB5)
read[3] |= data[6]&0x80;
if (!memcmp(&read, data+3, data[0]-1))
continue;
/* trying to rewrite register */
r = panel_otm3201_rewrite_reg(mpd, data+2, data[0], read);
if (r) {
print_reg_mismatch(&mpd->dssdev->dev,
data+3, read, data[0]-1);
break;
}
}
if (r)
dev_err(&mpd->dssdev->dev, "Failed verify otm3201"
" register: %02X\n", addr);
else
r = panel_ssd2848_set_retransmit(mpd, false);
return r;
}
static int minnow_panel_check_cmdbuf(struct minnow_panel_data *mpd,
u8 *data, int count)
{
int i, r = 0;
for (i = count; *data && (i > 0); ) {
if (data[1] >= CMD_TYPE_MAX)
break;
i -= ((u32)*data + 2);
data += (*data + 2);
}
/* command data shall end with 0 */
if (*data || (i != 1)) {
dev_err(&mpd->dssdev->dev, "Invalid command data(0x%02x) "
"found at offset %d", *data, count - i);
r = -EINVAL;
}
return r;
}
static int minnow_panel_process_cmdbuf(struct minnow_panel_data *mpd,
struct minnow_panel_cmd_buf *cmd_buf,
bool verify)
{
u8 *data;
int i, r;
bool retrans = false;
/* be safe to check command data every time before sent to driver */
r = minnow_panel_check_cmdbuf(mpd, cmd_buf->cmdbuf, cmd_buf->count);
if (r)
return r;
for (i = 0, data = cmd_buf->cmdbuf; *data; i++, data += *data+2) {
if ((data[1] == SSD2848_CMD) &&
(data[0] == 1) && (data[2] == CMD_VERIFY_REG)) {
if (!verify)
continue;
r = minnow_panel_verify_ssd2848(mpd, cmd_buf->cmdbuf);
} else if ((data[1] == OTM3201_CMD) && (data[0] == 1) &&
(data[2] == CMD_VERIFY_REG)) {
if (!verify)
continue;
r = minnow_panel_verify_otm3201(mpd, cmd_buf->cmdbuf);
} else if (data[1] == SWITCH_TO_PANEL) {
retrans = !!data[2];
} else
r = minnow_panel_process_cmd(mpd, data[1], data+2,
data[0], retrans);
if (r) {
dev_err(&mpd->dssdev->dev, "Failed process initialize"
" command[%d] len=%d type=%d ret=%d\n",
i, data[0], data[1], r);
break;
}
}
return r;
}
static void minnow_panel_queue_esd_work(struct minnow_panel_data *mpd)
{
if (!mpd->esd_interval)
return;
queue_delayed_work(mpd->workqueue, &mpd->esd_work,
msecs_to_jiffies(mpd->esd_interval));
#ifdef CONFIG_HAS_AMBIENTMODE
/* store start time of delayed work */
read_persistent_clock(&mpd->esd_start_time);
#endif
}
static void minnow_panel_cancel_esd_work(struct minnow_panel_data *mpd)
{
cancel_delayed_work(&mpd->esd_work);
}
static void minnow_panel_queue_ulps_work(struct minnow_panel_data *mpd)
{
if (!mpd->ulps_timeout)
return;
queue_delayed_work(mpd->workqueue, &mpd->ulps_work,
msecs_to_jiffies(mpd->ulps_timeout));
}
static void minnow_panel_cancel_ulps_work(struct minnow_panel_data *mpd)
{
cancel_delayed_work(&mpd->ulps_work);
}
static int minnow_panel_dsi_recovery_locked(struct minnow_panel_data *mpd)
{
struct omap_dss_device *dssdev = mpd->dssdev;
int r;
/* true/true for fast disable dsi */
omapdss_dsi_display_disable(mpd->dssdev, true, true);
mpd->ulps_enabled = false;
r = omapdss_dsi_display_enable(dssdev);
if (r) {
dev_err(&dssdev->dev, "DSI recovery failed to enable DSI\n");
goto _ret_r_;
}
omapdss_dsi_vc_enable_hs(dssdev, mpd->channel, true);
r = _minnow_panel_enable_te(mpd, mpd->te_enabled);
/* for some reason, reset DSI may occur "false control" with bridge
* that will cause first command failed, so work around to try resend
* that check if it's still failed or not
*/
if (r)
r = _minnow_panel_enable_te(mpd, mpd->te_enabled);
if (r)
dev_err(&dssdev->dev, "DSI recovery failed to re-init TE");
_ret_r_:
return r;
}
static int minnow_panel_recovery_locked(struct minnow_panel_data *mpd)
{
int r;
dev_err(&mpd->dssdev->dev, "performing LCD reset\n");
mpd->total_error++;
mpd->total_esd_reset++;
minnow_panel_disable_locked(mpd, true);
msleep(20);
r = minnow_panel_enable_locked(mpd);
dev_err(&mpd->dssdev->dev, "LCD reset done(%d)\n", r);
return r;
}
static int minnow_panel_enter_ulps_locked(struct minnow_panel_data *mpd)
{
int r;
if (mpd->ulps_enabled)
return 0;
minnow_panel_cancel_ulps_work(mpd);
r = _minnow_panel_enable_te(mpd, false);
if (r) {
/* try once to recovery DSI */
r = minnow_panel_dsi_recovery_locked(mpd);
if (r)
goto err;
r = _minnow_panel_enable_te(mpd, false);
if (r)
goto err;
}
if (gpio_is_valid(mpd->ext_te_gpio))
disable_irq(gpio_to_irq(mpd->ext_te_gpio));
omapdss_dsi_display_disable(mpd->dssdev, false, true);
mpd->ulps_enabled = true;
dev_dbg(&mpd->dssdev->dev, "entered ULPS mode\n");
#ifdef PANEL_PERF_TIME
mpd->last_ulps = jiffies;
#endif
return 0;
err:
dev_err(&mpd->dssdev->dev, "enter ULPS failed\n");
mpd->ulps_enabled = false;
minnow_panel_queue_ulps_work(mpd);
return r;
}
static int minnow_panel_exit_ulps_locked(struct minnow_panel_data *mpd)
{
struct omap_dss_device *dssdev = mpd->dssdev;
int r;
if (!mpd->ulps_enabled)
return 0;
r = omapdss_dsi_display_enable(dssdev);
if (r) {
/* try once to recovery DSI */
r = minnow_panel_dsi_recovery_locked(mpd);
if (!r)
goto next;
dev_err(&dssdev->dev, "failed to enable DSI\n");
goto err;
}
omapdss_dsi_vc_enable_hs(dssdev, mpd->channel, true);
r = _minnow_panel_enable_te(mpd, mpd->te_enabled);
if (r) {
/* try once to recovery DSI */
r = minnow_panel_dsi_recovery_locked(mpd);
if (r)
goto err;
}
next:
if (gpio_is_valid(mpd->ext_te_gpio))
enable_irq(gpio_to_irq(mpd->ext_te_gpio));
minnow_panel_queue_ulps_work(mpd);
mpd->ulps_enabled = false;
dev_dbg(&dssdev->dev, "exited ULPS mode\n");
#ifdef PANEL_PERF_TIME
mpd->time_ulps += GET_ELAPSE_TIME(mpd->last_ulps);
#endif
return 0;
err:
dev_err(&dssdev->dev, "failed to exit ULPS\n");
return r;
}
static int minnow_panel_wake_up_locked(struct minnow_panel_data *mpd)
{
if (mpd->ulps_enabled)
return minnow_panel_exit_ulps_locked(mpd);
minnow_panel_cancel_ulps_work(mpd);
minnow_panel_queue_ulps_work(mpd);
return 0;
}
#ifdef CONFIG_PANEL_BACKLIGHT
static int minnow_panel_bl_update_status(struct backlight_device *dev)
{
struct omap_dss_device *dssdev = dev_get_drvdata(&dev->dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
int r;
int level;
if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
dev->props.power == FB_BLANK_UNBLANK)
level = dev->props.brightness;
else
level = 0;
dev_dbg(&dssdev->dev, "update brightness to %d\n", level);
mutex_lock(&mpd->lock);
if (mpd->enabled) {
dsi_bus_lock(dssdev);
r = minnow_panel_wake_up_locked(mpd);
if (!r)
r = minnow_panel_dcs_write_1(mpd, DCS_BRIGHTNESS,
level);
dsi_bus_unlock(dssdev);
} else {
r = 0;
}
mutex_unlock(&mpd->lock);
return r;
}
static int minnow_panel_bl_get_intensity(struct backlight_device *dev)
{
if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
dev->props.power == FB_BLANK_UNBLANK)
return dev->props.brightness;
return 0;
}
static const struct backlight_ops minnow_panel_bl_ops = {
.get_brightness = minnow_panel_bl_get_intensity,
.update_status = minnow_panel_bl_update_status,
};
#endif
static void minnow_panel_get_resolution(struct omap_dss_device *dssdev,
u16 *xres, u16 *yres)
{
*xres = dssdev->panel.timings.x_res;
*yres = dssdev->panel.timings.y_res;
}
static void minnow_panel_get_dimensions(struct omap_dss_device *dssdev,
u32 *xres, u32 *yres)
{
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
*xres = mpd->xres_um;
*yres = mpd->yres_um;
}
static ssize_t minnow_panel_errors_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
static struct { u16 addr; const char *name; } dump[] = {
{0x1018, "MIPIRX-Phy Error"},
{0x102C, "MIPIRX-DSI Error"},
{0x1030, "MIPIRX-DSI Error Count"},
{0x608C, "MIPITX-DSI0 Status"},
};
u8 read[6];
int i, r, len = 0;
mutex_lock(&mpd->lock);
len += snprintf(buf+len, PAGE_SIZE-len, "Updates: %d\n",
mpd->total_update);
len += snprintf(buf+len, PAGE_SIZE-len, "Errors: %d\n",
mpd->total_error);
len += snprintf(buf+len, PAGE_SIZE-len, "ESD RST: %d\n",
mpd->total_esd_reset);
dsi_bus_lock(dssdev);
if (!mpd->enabled || (mpd->id_panel != MINNOW_PANEL_CM_BRIDGE_320X320))
goto _ret_;
r = minnow_panel_wake_up_locked(mpd);
if (r) {
len += snprintf(buf+len, PAGE_SIZE-len, "Failed to wakeup!\n");
goto _ret_;
}
for (i = 0; i < sizeof(dump)/sizeof(dump[0]); i++) {
r = panel_ssd2848_read_reg(mpd, dump[i].addr, read+2);
if (r)
len += snprintf(buf+len, PAGE_SIZE-len,
"Failed read register %s\n",
dump[i].name);
else
len += snprintf(buf+len, PAGE_SIZE-len,
"%s:\t%02X%02X%02X%02X\n",
dump[i].name, read[2], read[3],
read[4], read[5]);
if (dump[i].addr != 0x608C)
continue;
/* Cleaning MIPITX-DSI0 Status */
read[0] = 0x60;
read[1] = 0x8C;
dsi_vc_generic_write(mpd->dssdev, mpd->channel, read, 6);
}
_ret_:
dsi_bus_unlock(dssdev);
mutex_unlock(&mpd->lock);
return len;
}
static ssize_t minnow_panel_hw_revision_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
u8 id1, id2, id3;
int r;
mutex_lock(&mpd->lock);
if (mpd->enabled) {
dsi_bus_lock(dssdev);
r = minnow_panel_wake_up_locked(mpd);
if (!r)
r = minnow_panel_get_id(mpd, &id1, &id2, &id3);
dsi_bus_unlock(dssdev);
} else {
r = -ENODEV;
}
mutex_unlock(&mpd->lock);
if (r)
return r;
return snprintf(buf, PAGE_SIZE, "%02x.%02x.%02x\n", id1, id2, id3);
}
#ifdef PANEL_DEBUG
static const char *cabc_modes[] = {
"off", /* used also always when CABC is not supported */
"ui",
"still-image",
"moving-image",
};
static ssize_t show_cabc_mode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
const char *mode_str;
int mode;
mode = mpd->cabc_mode;
mode_str = "unknown";
if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes))
mode_str = cabc_modes[mode];
return snprintf(buf, PAGE_SIZE, "%s\n", mode_str);
}
static ssize_t store_cabc_mode(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
int i;
int r;
for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) {
if (sysfs_streq(cabc_modes[i], buf))
break;
}
if (i == ARRAY_SIZE(cabc_modes))
return -EINVAL;
mutex_lock(&mpd->lock);
if (mpd->enabled) {
dsi_bus_lock(dssdev);
r = minnow_panel_wake_up_locked(mpd);
if (r)
goto err;
r = minnow_panel_dcs_write_1(mpd, DCS_WRITE_CABC, i);
if (r)
goto err;
dsi_bus_unlock(dssdev);
}
mpd->cabc_mode = i;
mutex_unlock(&mpd->lock);
return count;
err:
dsi_bus_unlock(dssdev);
mutex_unlock(&mpd->lock);
return r;
}
static ssize_t show_cabc_available_modes(struct device *dev,
struct device_attribute *attr, char *buf)
{
int i, len;
for (i = 0, len = 0;
len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++)
len += snprintf(&buf[len], PAGE_SIZE-len, "%s%s%s",
i ? " " : "", cabc_modes[i],
i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : "");
return len;
}
#endif
static ssize_t minnow_panel_store_esd_interval(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned long t;
int r;
r = strict_strtoul(buf, 10, &t);
if (r)
return r;
mutex_lock(&mpd->lock);
minnow_panel_cancel_esd_work(mpd);
/* special settings for test purpose */
switch (t) {
case 1: case 2:
/* active panel/bridge reset to force ESD */
t--;
dev_info(&mpd->dssdev->dev, "ESD test to force %s reset\n",
t == MINNOW_PANEL ? "panel" : "bridge");
gpio_set_value(mpd->reset_gpio[t],
mpd->hw_reset[t].active ? 1 : 0);
break;
case 3:
dsi_bus_lock(dssdev);
dev_info(&mpd->dssdev->dev, "ESD test for DSI recovery\n");
r = minnow_panel_exit_ulps_locked(mpd);
dev_info(&mpd->dssdev->dev,
"minnow_panel_exit_ulps_locked = %d\n", r);
r = minnow_panel_dsi_recovery_locked(mpd);
dev_info(&mpd->dssdev->dev,
"minnow_panel_dsi_recovery_locked = %d\n", r);
r = minnow_panel_enter_ulps_locked(mpd);
dev_info(&mpd->dssdev->dev,
"minnow_panel_enter_ulps_locked = %d\n", r);
r = minnow_panel_dsi_recovery_locked(mpd);
dev_info(&mpd->dssdev->dev,
"minnow_panel_dsi_recovery_locked = %d\n", r);
dsi_bus_unlock(dssdev);
break;
case 4:
dsi_bus_lock(dssdev);
dev_info(&mpd->dssdev->dev, "ESD test for panel recovery\n");
minnow_panel_disable_locked(mpd, true);
dev_info(&mpd->dssdev->dev,
"minnow_panel_disable_locked done\n");
msleep(20);
dev_info(&mpd->dssdev->dev,
"minnow_panel_enable_locked start\n");
r = minnow_panel_enable_locked(mpd);
dev_info(&mpd->dssdev->dev,
"minnow_panel_enable_locked = %d\n", r);
if (!r) {
r = minnow_panel_update_locked(mpd);
/* no dsi_bus_unlock when update start successfully */
if (!r)
break;
}
dsi_bus_unlock(dssdev);
default:
mpd->esd_interval = t;
break;
}
if (mpd->enabled)
minnow_panel_queue_esd_work(mpd);
mutex_unlock(&mpd->lock);
return count;
}
static ssize_t minnow_panel_show_esd_interval(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned t;
mutex_lock(&mpd->lock);
t = mpd->esd_interval;
mutex_unlock(&mpd->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static ssize_t minnow_panel_store_ulps(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned long t;
int r;
r = strict_strtoul(buf, 10, &t);
if (r)
return r;
mutex_lock(&mpd->lock);
if (mpd->enabled) {
dsi_bus_lock(dssdev);
if (t)
r = minnow_panel_enter_ulps_locked(mpd);
else
r = minnow_panel_wake_up_locked(mpd);
dsi_bus_unlock(dssdev);
}
mutex_unlock(&mpd->lock);
return r ? r : count;
}
static ssize_t minnow_panel_show_ulps(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned t;
mutex_lock(&mpd->lock);
t = mpd->ulps_enabled;
mutex_unlock(&mpd->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static ssize_t minnow_panel_store_ulps_timeout(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned long t;
int r;
r = strict_strtoul(buf, 10, &t);
if (r)
return r;
mutex_lock(&mpd->lock);
mpd->ulps_timeout = t;
if (mpd->enabled) {
/* minnow_panel_wake_up_locked will restart the timer */
dsi_bus_lock(dssdev);
r = minnow_panel_wake_up_locked(mpd);
dsi_bus_unlock(dssdev);
}
mutex_unlock(&mpd->lock);
return r ? r : count;
}
static ssize_t minnow_panel_show_ulps_timeout(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned t;
mutex_lock(&mpd->lock);
t = mpd->ulps_timeout;
mutex_unlock(&mpd->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
#ifdef PANEL_DEBUG
static ssize_t minnow_panel_store_init_data(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
u8 *data;
int r;
r = minnow_panel_check_cmdbuf(mpd, (u8 *)buf, count);
if (r)
return r;
data = devm_kzalloc(&dssdev->dev, count, GFP_KERNEL);
if (!data)
return -ENOMEM;
memcpy(data, buf, count);
mutex_lock(&mpd->lock);
mpd->power_on.count = count;
mpd->power_on.cmdbuf = data;
if (mpd->last_init_data)
devm_kfree(&dssdev->dev, mpd->last_init_data);
mpd->last_init_data = data;
mutex_unlock(&mpd->lock);
return count;
}
static ssize_t minnow_panel_show_init_data(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
int i, j;
u8 *data;
mutex_lock(&mpd->lock);
data = mpd->power_on.cmdbuf;
mutex_unlock(&mpd->lock);
for (i = 0; i < PAGE_SIZE && *data; ) {
i += snprintf(buf+i, PAGE_SIZE-i,
"%02d %02d:", data[0], data[1]);
for (j = 0; j < *data && i < PAGE_SIZE; j++) {
i += snprintf(buf+i, PAGE_SIZE-i, " %02X", data[2+j]);
}
snprintf(buf+i, PAGE_SIZE-i, "\n");
i++;
data += *data + 2;
}
return i;
}
#endif /* PANEL_DEBUG */
#ifdef CONFIG_HAS_AMBIENTMODE
static ssize_t minnow_panel_show_interactivemode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned t;
mutex_lock(&mpd->lock);
t = mpd->interactive;
mutex_unlock(&mpd->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static ssize_t minnow_panel_store_interactivemode(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned long t;
int r;
r = strict_strtoul(buf, 10, &t);
if (!r) {
bool enable = !!t;
mutex_lock(&mpd->lock);
#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY
/* clean early init timeout as someone handle it also */
if (mpd->early_inited) {
mpd->early_inited = false;
cancel_delayed_work(&mpd->early_init_timeout_work);
}
#endif
if (mpd->interactive != enable) {
int state = mpd->state;
if (enable)
state = DISPLAY_ENABLE;
else if (state != DISPLAY_DISABLE)
state = DISPLAY_AMBIENT_ON;
r = minnow_panel_change_state_mlocked(mpd, state);
if (!r)
mpd->interactive = enable;
}
mutex_unlock(&mpd->lock);
if (r)
dev_err(&dssdev->dev, "%s interactive mode failed %d\n",
enable ? "enable" : "disable", r);
else
dev_dbg(&dssdev->dev, "%s interactive mode succeeded\n",
enable ? "enable" : "disable");
}
return r ? r : count;
}
static ssize_t minnow_panel_show_smartambient(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned t;
t = mpd->smart_ambient;
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static ssize_t minnow_panel_store_smartambient(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned long t;
int r;
bool enable;
r = kstrtoul(buf, 10, &t);
if (!r) {
mutex_lock(&mpd->lock);
enable = !!t;
if (mpd->state != DISPLAY_ENABLE) {
dev_err(&dssdev->dev, "%s failed as display is not enabled\n",
__func__);
r = -EBUSY;
} else if (mpd->smart_ambient != enable) {
mpd->smart_ambient = enable;
}
mutex_unlock(&mpd->lock);
if (r)
dev_err(&dssdev->dev, "setting smartambient_status to %ld failed %d\n",
t, r);
else
dev_dbg(&dssdev->dev, "setting smartambient_status to %ld succeeded\n",
t);
}
return r ? r : count;
}
static ssize_t minnow_panel_show_ambient_timeout(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned int t;
mutex_lock(&mpd->lock);
t = mpd->ambient_timeout;
mutex_unlock(&mpd->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static ssize_t minnow_panel_store_ambient_timeout(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned long t;
int r;
r = kstrtoul(buf, 10, &t);
if (!r) {
mutex_lock(&mpd->lock);
mpd->ambient_timeout = (int)t;
mutex_unlock(&mpd->lock);
}
return r ? r : count;
}
#endif /* CONFIG_HAS_AMBIENTMODE */
#ifdef PANEL_PERF_TIME
static ssize_t minnow_panel_perftime_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
int len = 0;
mutex_lock(&mpd->lock);
if (mpd->enabled) {
mpd->time_power_on += GET_ELAPSE_TIME(mpd->last_power_on);
mpd->last_power_on = jiffies;
}
len += snprintf(buf+len, PAGE_SIZE-len, "Power On: %lu ms\n",
mpd->time_power_on);
len += snprintf(buf+len, PAGE_SIZE-len, "Enter ULPS: %lu ms\n",
mpd->time_ulps);
len += snprintf(buf+len, PAGE_SIZE-len, "Update Frame: %lu ms\n",
mpd->time_update);
len += snprintf(buf+len, PAGE_SIZE-len, " Frame Min: %lu ms\n",
mpd->time_update_min);
len += snprintf(buf+len, PAGE_SIZE-len, " Frame Max: %lu ms\n",
mpd->time_update_max);
len += snprintf(buf+len, PAGE_SIZE-len, " Frame Avg: %lu ms\n",
mpd->time_update / mpd->total_update);
mutex_unlock(&mpd->lock);
return len;
}
#endif
static ssize_t minnow_panel_vsync_events_enabled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
bool vsync_events_enabled;
mutex_lock(&mpd->lock);
vsync_events_enabled = mpd->vsync_events_enabled;
mutex_unlock(&mpd->lock);
return snprintf(buf, PAGE_SIZE, "%d\n", vsync_events_enabled);
}
static int minnow_panel_vsync_events_init(struct minnow_panel_data *mpd)
{
int r = 0;
if (gpio_is_valid(mpd->vsync_events_gpio)) {
r = devm_gpio_request_one(&mpd->dssdev->dev,
mpd->vsync_events_gpio, GPIOF_IN,
"minnow-panel vsync_events");
mpd->vsync_events_sysfs = sysfs_get_dirent(
mpd->dssdev->dev.kobj.sd, NULL, "vsync_events");
}
return r;
}
static irqreturn_t minnow_panel_vsync_events_isr(int irq, void *dev_id)
{
struct minnow_panel_data *mpd = dev_id;
mpd->vsync_events_timestamp = ktime_get();
sysfs_notify_dirent(mpd->vsync_events_sysfs);
return IRQ_HANDLED;
}
static int minnow_panel_enable_vsync_events_mlocked(
struct minnow_panel_data *mpd, bool enabled)
{
int r = 0;
if (!gpio_is_valid(mpd->vsync_events_gpio)) {
dev_err(&mpd->dssdev->dev,
"enable_vsync_events: store: gpio not valid");
r = -EINVAL;
} else if (enabled != mpd->vsync_events_enabled) {
if (enabled) {
r = devm_request_irq(&mpd->dssdev->dev,
gpio_to_irq(mpd->vsync_events_gpio),
minnow_panel_vsync_events_isr,
IRQF_TRIGGER_RISING,
"minnow-panel vsync_events",
mpd);
} else {
devm_free_irq(&mpd->dssdev->dev,
gpio_to_irq(mpd->vsync_events_gpio),
mpd);
}
mpd->vsync_events_enabled = enabled;
}
return r;
}
static ssize_t minnow_panel_vsync_events_enabled_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
unsigned long t;
int r;
r = kstrtoul(buf, 10, &t);
if (!r) {
bool enabled = !!t;
mutex_lock(&mpd->lock);
r = minnow_panel_enable_vsync_events_mlocked(mpd, enabled);
mutex_unlock(&mpd->lock);
}
return r ? r : count;
}
static ssize_t minnow_panel_vsync_events_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev);
return snprintf(buf, PAGE_SIZE, "%llu\n",
ktime_to_ns(mpd->vsync_events_timestamp));
}
static DEVICE_ATTR(errors, S_IRUGO, minnow_panel_errors_show, NULL);
static DEVICE_ATTR(hw_revision, S_IRUGO, minnow_panel_hw_revision_show, NULL);
static DEVICE_ATTR(esd_interval, S_IRUGO | S_IWUSR,
minnow_panel_show_esd_interval,
minnow_panel_store_esd_interval);
static DEVICE_ATTR(ulps, S_IRUGO | S_IWUSR,
minnow_panel_show_ulps, minnow_panel_store_ulps);
static DEVICE_ATTR(ulps_timeout, S_IRUGO | S_IWUSR,
minnow_panel_show_ulps_timeout,
minnow_panel_store_ulps_timeout);
#ifdef PANEL_DEBUG
static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR,
show_cabc_mode, store_cabc_mode);
static DEVICE_ATTR(cabc_available_modes, S_IRUGO,
show_cabc_available_modes, NULL);
static DEVICE_ATTR(init_data, S_IRUGO | S_IWUSR,
minnow_panel_show_init_data,
minnow_panel_store_init_data);
#endif
#ifdef CONFIG_HAS_AMBIENTMODE
static DEVICE_ATTR(interactivemode, S_IRUGO | S_IWUSR,
minnow_panel_show_interactivemode,
minnow_panel_store_interactivemode);
static DEVICE_ATTR(smartambient, S_IRUSR | S_IWUSR,
minnow_panel_show_smartambient,
minnow_panel_store_smartambient);
static DEVICE_ATTR(ambient_timeout, S_IRUGO | S_IWUSR,
minnow_panel_show_ambient_timeout,
minnow_panel_store_ambient_timeout);
#endif
#ifdef PANEL_PERF_TIME
static DEVICE_ATTR(perftime, S_IRUGO, minnow_panel_perftime_show, NULL);
#endif
static DEVICE_ATTR(vsync_events_enabled, S_IRUGO | S_IWUSR,
minnow_panel_vsync_events_enabled_show,
minnow_panel_vsync_events_enabled_store);
static DEVICE_ATTR(vsync_events, S_IRUGO,
minnow_panel_vsync_events_show, NULL);
static struct attribute *minnow_panel_attrs[] = {
&dev_attr_errors.attr,
&dev_attr_hw_revision.attr,
&dev_attr_esd_interval.attr,
&dev_attr_ulps.attr,
&dev_attr_ulps_timeout.attr,
#ifdef PANEL_DEBUG
&dev_attr_cabc_mode.attr,
&dev_attr_cabc_available_modes.attr,
&dev_attr_init_data.attr,
#endif
#ifdef CONFIG_HAS_AMBIENTMODE
&dev_attr_interactivemode.attr,
&dev_attr_smartambient.attr,
&dev_attr_ambient_timeout.attr,
#endif
#ifdef PANEL_PERF_TIME
&dev_attr_perftime.attr,
#endif
&dev_attr_vsync_events_enabled.attr,
&dev_attr_vsync_events.attr,
NULL,
};
static struct attribute_group minnow_panel_attr_group = {
.attrs = minnow_panel_attrs,
};
static void _minnow_panel_hw_active_reset(struct minnow_panel_data *mpd)
{
int i;
if (mpd->reset_ms < 0)
return;
/* reset the device */
for (i = 0; i < MINNOW_COMPONENT_MAX; i++) {
if (!gpio_is_valid(mpd->reset_gpio[i]))
continue;
gpio_set_value(mpd->reset_gpio[i],
mpd->hw_reset[i].active ? 1 : 0);
}
/* wait device reset */
minnow_panel_delay(mpd->reset_ms);
}
static void _minnow_panel_hw_reset(struct minnow_panel_data *mpd)
{
int i;
_minnow_panel_hw_active_reset(mpd);
/* assert reset */
for (i = 0; i < MINNOW_COMPONENT_MAX; i++) {
if (!gpio_is_valid(mpd->reset_gpio[i]))
continue;
gpio_set_value(mpd->reset_gpio[i],
mpd->hw_reset[i].active ? 0 : 1);
}
/* wait after releasing reset */
if (mpd->release_ms > 0)
minnow_panel_delay(mpd->release_ms);
}
static int minnow_panel_set_regulators(struct minnow_panel_data *mpd,
int (*func)(struct regulator *regulator))
{
int i;
for (i = 0; i < MINNOW_COMPONENT_MAX; i++) {
if (!mpd->regulators[i])
continue;
if (func(mpd->regulators[i]))
return -ENODEV;
}
return 0;
}
static void minnow_panel_enable_vio(struct minnow_panel_data *mpd, bool enable)
{
if (enable) {
if (gpio_is_valid(mpd->vio_en_gpio)) {
/* This is workaround to fix unexpected M4 reset issue
* select pulldown mode to enable switch will tuen on
* 1.8v power supply slowly, that will help reduce the
* dip of 1.8v supply
*/
int r = pinctrl_select_state(mpd->vio_pctrl,
mpd->vio_state_pulldown);
if (r)
dev_err(&mpd->dssdev->dev, "failed to activate"
" vio_state_pulldown!");
usleep_range(250, 300);
/* go back to output low mode to keep switch enabled */
gpio_set_value(mpd->vio_en_gpio, 0);
r = pinctrl_select_state(mpd->vio_pctrl,
mpd->vio_state_output);
if (r)
dev_err(&mpd->dssdev->dev, "failed to activate"
" vio_state_output!");
}
if (gpio_is_valid(mpd->mem_en_gpio))
gpio_set_value(mpd->mem_en_gpio, 1);
} else {
if (gpio_is_valid(mpd->mem_en_gpio))
gpio_set_value(mpd->mem_en_gpio, 0);
if (gpio_is_valid(mpd->vio_en_gpio))
gpio_set_value(mpd->vio_en_gpio, 1);
}
}
static int minnow_panel_enable_clkin(struct minnow_panel_data *mpd,
bool enable)
{
int r = 0;
if ((mpd->clk_in_en != enable) && (mpd->clk_in != NULL)) {
if (enable)
r = clk_prepare_enable(mpd->clk_in);
else
clk_disable_unprepare(mpd->clk_in);
if (!r)
mpd->clk_in_en = enable;
}
return r;
}
#define DEBUG_DT
#ifdef DEBUG_DT
#define DTINFO(fmt, ...) \
printk(KERN_INFO "minnow-panel DT: " fmt, ## __VA_ARGS__)
#define DTINFO_PIXFMT(msg, pix) \
{ char *fmt[4] = {"RGB888", "RGB666", "RGB666_PACKED", "RGB565"};\
DTINFO(msg"%s\n", fmt[pix]);\
}
#define DTINFO_ARRAY(msg, a, n, fmt, blen) \
{ int i; char str[blen], *p = str;\
for (i = 0; i < n; i++) {\
sprintf(p, fmt, a[i]);\
p += strlen(p); \
} \
DTINFO(msg"%s\n", str);\
}
#else /* DEBUG_DT */
#define DTINFO(fmt, ...)
#define DTINFO_PIXFMT(msg, pix)
#define DTINFO_ARRAY(msg, a, n, fmt, blen)
#endif
static struct of_device_id minnow_panel_ids[] = {
{ .compatible = "mot,minnow-panel-dsi-cm" },
{ /*sentinel*/ }
};
static int minnow_panel_dt_init(struct minnow_panel_data *mpd)
{
u32 range[2], value = 0;
struct minnow_panel_attr *panel_attr;
struct device_node *dt_node;
char *clkin;
dt_node = of_find_matching_node(NULL, minnow_panel_ids);
if (dt_node == NULL) {
dev_err(&mpd->dssdev->dev, "No dt_node found!\n");
return -ENODEV;
}
/* Save the dt node entry to the device */
mpd->dssdev->dev.of_node = dt_node;
if (of_property_read_u32(dt_node, "id_panel", &value) \
|| (value >= MINNOW_PANEL_MAX)) {
dev_err(&mpd->dssdev->dev, \
"Invalid id_panel = %u!\n", value);
return -EINVAL;
}
mpd->id_panel = value;
DTINFO("id_panel = %d\n", mpd->id_panel);
panel_attr = &panel_attr_table[mpd->id_panel];
mpd->power_on = panel_attr->power_on;
mpd->power_off = panel_attr->power_off;
mpd->dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE |
OMAP_DSS_DISPLAY_CAP_TEAR_ELIM;
mpd->dssdev->panel.timings.x_res = panel_attr->xres;
mpd->dssdev->panel.timings.y_res = panel_attr->yres;
mpd->dssdev->panel.timings.pixel_clock = panel_attr->pixel_clock;
mpd->dssdev->panel.dsi_pix_fmt = panel_attr->pixel_format;
mpd->dsi_config.mode = panel_attr->mode;
mpd->dsi_config.pixel_format = panel_attr->pixel_format;
mpd->dsi_config.hs_clk_min = panel_attr->hs.min;
mpd->dsi_config.hs_clk_max = panel_attr->hs.max;
mpd->dsi_config.lp_clk_min = panel_attr->lp.min;
mpd->dsi_config.lp_clk_max = panel_attr->lp.max;
mpd->x_offset = panel_attr->xoffset;
mpd->y_offset = panel_attr->yoffset;
mpd->hw_reset[MINNOW_PANEL] = panel_attr->panel_reset;
mpd->hw_reset[MINNOW_BRIDGE] = panel_attr->bridge_reset;
mpd->reset_gpio[MINNOW_PANEL] =
of_get_named_gpio(dt_node, "gpio_panel_reset", 0);
DTINFO("gpio_panel_reset = %d\n", mpd->reset_gpio[MINNOW_PANEL]);
mpd->reset_gpio[MINNOW_BRIDGE] =
of_get_named_gpio(dt_node, "gpio_bridge_reset", 0);
DTINFO("gpio_bridge_reset = %d\n",
mpd->reset_gpio[MINNOW_BRIDGE]);
mpd->ext_te_gpio = of_get_named_gpio(dt_node, "gpio_te", 0);
DTINFO("gpio_te = %d\n", mpd->ext_te_gpio);
mpd->vio_en_gpio = of_get_named_gpio(dt_node, "gpio_vio_en", 0);
DTINFO("gpio_vio_en = %d\n", mpd->vio_en_gpio);
if (gpio_is_valid(mpd->vio_en_gpio)) {
mpd->vio_pctrl = devm_pinctrl_get(&mpd->dssdev->dev);
if (IS_ERR(mpd->vio_pctrl)) {
dev_err(&mpd->dssdev->dev, "no vio pinctrl handle\n");
return PTR_ERR(mpd->vio_pctrl);
}
mpd->vio_state_pulldown =
pinctrl_lookup_state(mpd->vio_pctrl, "viopulldown");
if (IS_ERR(mpd->vio_state_pulldown)) {
dev_err(&mpd->dssdev->dev, "no vio pulldown state\n");
return PTR_ERR(mpd->vio_state_pulldown);
}
mpd->vio_state_output =
pinctrl_lookup_state(mpd->vio_pctrl, "viooutput");
if (IS_ERR(mpd->vio_state_output)) {
dev_err(&mpd->dssdev->dev, "no vio output state\n");
return PTR_ERR(mpd->vio_state_output);
}
}
mpd->mem_en_gpio = of_get_named_gpio(dt_node, "gpio_mem_en", 0);
DTINFO("gpio_mem_en = %d\n", mpd->mem_en_gpio);
mpd->vsync_events_gpio = of_get_named_gpio(dt_node,
"gpio_vsync_events", 0);
DTINFO("gpio_vsync_events = %d\n", mpd->vsync_events_gpio);
clkin = (char *)of_get_property(dt_node, "clk_in", NULL);
if (clkin) {
mpd->clk_in = clk_get(NULL, clkin);
if (IS_ERR(mpd->clk_in)) {
int r = PTR_ERR(mpd->clk_in);
dev_err(&mpd->dssdev->dev,
"Failed get external clock %s!\n", clkin);
mpd->clk_in = NULL;
return r;
}
}
mpd->esd_interval = 0;
if (!of_property_read_u32(dt_node, "esd_interval", &value)) {
mpd->esd_interval = value;
DTINFO("esd_interval = %d\n", mpd->esd_interval);
}
#ifdef CONFIG_HAS_AMBIENTMODE
mpd->smart_ambient =
of_property_read_bool(dt_node, "support_smart_ambient");
DTINFO("support_smart_ambient = %d\n", mpd->smart_ambient);
mpd->ambient_timeout = 0;
if (!of_property_read_u32(dt_node, "ambient_timeout", &value)) {
mpd->ambient_timeout = value;
DTINFO("ambient_timeout = %d\n", mpd->ambient_timeout);
}
#endif
/* automatically go to ULPS mode for none-update within 250ms */
mpd->ulps_timeout = 250;
#ifdef CONFIG_PANEL_BACKLIGHT
mpd->use_dsi_backlight = false;
#endif
mpd->pin_config.num_pins = 4;
mpd->pin_config.pins[0] = 0;
mpd->pin_config.pins[1] = 1;
mpd->pin_config.pins[2] = 2;
mpd->pin_config.pins[3] = 3;
if (of_get_property(dt_node, "pins", &value)) {
u32 pins[OMAP_DSS_MAX_DSI_PINS];
u32 num_pins = value / sizeof(u32);
if (!num_pins || (num_pins > OMAP_DSS_MAX_DSI_PINS)) {
dev_err(&mpd->dssdev->dev, \
"Invalid DSI pins count = %u!\n", num_pins);
return -EINVAL;
}
value = 0;
if (!of_property_read_u32_array(dt_node, \
"pins", pins, num_pins)) {
for (; value < num_pins; value++) {
if (pins[value] >= OMAP_DSS_MAX_DSI_PINS)
break;
mpd->pin_config.pins[value]\
= pins[value];
}
}
if (value < num_pins) {
dev_err(&mpd->dssdev->dev, \
"Invalid DSI pins config!\n");
return -EINVAL;
}
mpd->pin_config.num_pins = num_pins;
DTINFO("num_pins = %d\n", \
mpd->pin_config.num_pins);
DTINFO_ARRAY("pins =", mpd->pin_config.pins,\
mpd->pin_config.num_pins, " %u", 64);
}
if (!of_property_read_u32(dt_node, "pixel_clock", &value)) {
if (value < mpd->dssdev->panel.timings.pixel_clock) {
dev_err(&mpd->dssdev->dev, \
"Invalid pixel_clock = %u!\n", value);
return -EINVAL;
}
mpd->dssdev->panel.timings.pixel_clock = value;
DTINFO("pixel_clock = %u\n", \
mpd->dssdev->panel.timings.pixel_clock);
}
if (!of_property_read_u32(dt_node, "pixel_format", &value)) {
switch (value) {
case OMAP_DSS_DSI_FMT_RGB888:
case OMAP_DSS_DSI_FMT_RGB666:
case OMAP_DSS_DSI_FMT_RGB666_PACKED:
case OMAP_DSS_DSI_FMT_RGB565:
break;
default:
dev_err(&mpd->dssdev->dev, \
"Invalid pixel_format = %u!\n", value);
return -EINVAL;
}
mpd->dssdev->panel.dsi_pix_fmt = \
mpd->dsi_config.pixel_format = value;
DTINFO_PIXFMT("pixel_format = ", \
mpd->dssdev->panel.dsi_pix_fmt);
}
if (!of_property_read_u32_array(dt_node, "hs_clk", range, 2)) {
mpd->dsi_config.hs_clk_min = range[0];
mpd->dsi_config.hs_clk_max = range[1];
DTINFO("hs_clk_min = %lu, hs_clk_max = %lu\n", \
mpd->dsi_config.hs_clk_min, \
mpd->dsi_config.hs_clk_max);
}
if (!of_property_read_u32_array(dt_node, "lp_clk", range, 2)) {
mpd->dsi_config.lp_clk_min = range[0];
mpd->dsi_config.lp_clk_max = range[1];
DTINFO("lp_clk_min = %lu, lp_clk_max = %lu\n", \
mpd->dsi_config.lp_clk_min, \
mpd->dsi_config.lp_clk_max);
}
mpd->xres_um = 0;
mpd->yres_um = 0;
if (!of_property_read_u32_array(dt_node, "panel_size_um", range, 2)) {
mpd->xres_um = range[0];
mpd->yres_um = range[1];
DTINFO("physical panel width = %d um, height = %d um\n",
mpd->xres_um, mpd->yres_um);
}
return 0;
}
static int minnow_panel_parse_panel_param(char *param, int *ptype, int *pver)
{
char *p, *start = (char *)param;
int ver, type;
if (!param)
return -EINVAL;
type = simple_strtoul(start, &p, 10);
if (start == p)
return -EINVAL;
if (*p != '#')
return -EINVAL;
start = p + 1;
ver = simple_strtoul(start, &p, 10);
if (start == p)
return -EINVAL;
if (*p != '\0')
return -EINVAL;
*ptype = type;
*pver = ver;
return 0;
}
static int minnow_panel_probe(struct omap_dss_device *dssdev)
{
struct minnow_panel_data *mpd;
#ifdef CONFIG_PANEL_BACKLIGHT
struct backlight_device *bldev = NULL;
#endif
int i, r;
dev_dbg(&dssdev->dev, "probe\n");
mpd = devm_kzalloc(&dssdev->dev, sizeof(*mpd), GFP_KERNEL);
if (!mpd)
return -ENOMEM;
dev_set_drvdata(&dssdev->dev, mpd);
mpd->dssdev = dssdev;
mpd->first_enable = true;
mpd->m4_state = DISPLAY_ENABLE;
r = minnow_panel_dt_init(mpd);
if (r)
return r;
mutex_init(&mpd->lock);
atomic_set(&mpd->do_update, 0);
#ifdef PANEL_PERF_TIME
mpd->time_update_min = (unsigned long)(-1);
mpd->time_update_max = 0;
#endif
/* it will reset bridge/panel if boot-loader does not initialize it */
mpd->skip_first_init = false;
if (minnow_panel_parse_panel_param(def_panel_param, &i, &r)) {
dev_err(&dssdev->dev, "wrong panel parameter %s\n",
def_panel_param);
i = PANEL_INIT;
}
mpd->panel_type = i;
switch (mpd->panel_type) {
case PANEL_INIT:
case PANEL_DUMMY:
dev_info(&dssdev->dev,
"There is not panel id coming from boot-loader\n");
break;
case OTM3201_1_0:
/* turn of ESD for panel 1.0 */
mpd->esd_interval = 0;
/* it needs replace the settings for panel 1.0 */
if (minnow_panel_replace_cmdbuf(mpd->power_on.cmdbuf,
panel_init_ssd2848_320x320_1)){
dev_info(&dssdev->dev, "Replaced for the settings "
"of panel 1.0!\n");
}
case OTM3201_2_0:
case OTM3201_2_1:
if