| /* |
| * 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; |
| mpd->interactive = true; |
| |
| 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: |