lineage_kernel_xcoverpro/drivers/video/fbdev/exynos/dpu20/dsim_drv.c

2009 lines
52 KiB
C
Executable File

/*
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung SoC MIPI-DSIM driver.
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/pm_runtime.h>
#include <linux/of_gpio.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/sec_debug.h>
#include <video/mipi_display.h>
#if defined(CONFIG_CAL_IF)
#include <soc/samsung/cal-if.h>
#endif
#if defined(CONFIG_CPU_IDLE)
#include <soc/samsung/exynos-cpupm.h>
#endif
#if defined(CONFIG_SOC_EXYNOS9610)
#include <dt-bindings/clock/exynos9610.h>
#endif
#include <soc/samsung/exynos-pmu.h>
#if defined(CONFIG_SUPPORT_LEGACY_ION)
#include <linux/exynos_iovmm.h>
#endif
#include <linux/of_reserved_mem.h>
#include "../../../../../mm/internal.h"
#include "decon_board.h"
#include "panels/dd.h"
#include "panels/dsim_panel.h"
#include "decon.h"
#include "dsim.h"
int dsim_log_level = 6;
struct dsim_device *dsim_drvdata[MAX_DSIM_CNT];
EXPORT_SYMBOL(dsim_drvdata);
static char *dsim_state_names[] = {
"INIT",
"ON",
"DOZE",
"ULPS",
"DOZE_SUSPEND",
"OFF",
};
static int dsim_runtime_suspend(struct device *dev);
static int dsim_runtime_resume(struct device *dev);
static void dsim_dump(struct dsim_device *dsim)
{
struct dsim_regs regs;
dsim_info("=== DSIM SFR DUMP ===\n");
dsim_to_regs_param(dsim, &regs);
__dsim_dump(dsim->id, &regs);
/* Show panel status */
call_panel_ops(dsim, dump, dsim);
}
static void dsim_long_data_wr(struct dsim_device *dsim, unsigned long d0, u32 d1)
{
unsigned int data_cnt = 0, payload = 0;
/* in case that data count is more then 4 */
for (data_cnt = 0; data_cnt < d1; data_cnt += 4) {
/*
* after sending 4bytes per one time,
* send remainder data less then 4.
*/
if ((d1 - data_cnt) < 4) {
if ((d1 - data_cnt) == 3) {
payload = *(u8 *)(d0 + data_cnt) |
(*(u8 *)(d0 + (data_cnt + 1))) << 8 |
(*(u8 *)(d0 + (data_cnt + 2))) << 16;
dsim_dbg("count = 3 payload = %x, %x %x %x\n",
payload, *(u8 *)(d0 + data_cnt),
*(u8 *)(d0 + (data_cnt + 1)),
*(u8 *)(d0 + (data_cnt + 2)));
} else if ((d1 - data_cnt) == 2) {
payload = *(u8 *)(d0 + data_cnt) |
(*(u8 *)(d0 + (data_cnt + 1))) << 8;
dsim_dbg("count = 2 payload = %x, %x %x\n", payload,
*(u8 *)(d0 + data_cnt),
*(u8 *)(d0 + (data_cnt + 1)));
} else if ((d1 - data_cnt) == 1) {
payload = *(u8 *)(d0 + data_cnt);
}
dsim_reg_wr_tx_payload(dsim->id, payload);
/* send 4bytes per one time. */
} else {
payload = *(u8 *)(d0 + data_cnt) |
(*(u8 *)(d0 + (data_cnt + 1))) << 8 |
(*(u8 *)(d0 + (data_cnt + 2))) << 16 |
(*(u8 *)(d0 + (data_cnt + 3))) << 24;
dsim_dbg("count = 4 payload = %x, %x %x %x %x\n",
payload, *(u8 *)(d0 + data_cnt),
*(u8 *)(d0 + (data_cnt + 1)),
*(u8 *)(d0 + (data_cnt + 2)),
*(u8 *)(d0 + (data_cnt + 3)));
dsim_reg_wr_tx_payload(dsim->id, payload);
}
}
}
static int dsim_wait_for_cmd_fifo_empty(struct dsim_device *dsim, bool must_wait)
{
int ret = 0;
struct dsim_regs regs;
if (!must_wait) {
/* timer is running, but already command is transferred */
if (dsim_reg_header_fifo_is_empty(dsim->id))
del_timer(&dsim->cmd_timer);
dsim_dbg("%s Doesn't need to wait fifo_completion\n", __func__);
return ret;
} else {
del_timer(&dsim->cmd_timer);
dsim_dbg("%s Waiting for fifo_completion...\n", __func__);
}
if (!wait_for_completion_timeout(&dsim->ph_wr_comp, MIPI_WR_TIMEOUT)) {
if (dsim_reg_header_fifo_is_empty(dsim->id)) {
reinit_completion(&dsim->ph_wr_comp);
dsim_reg_clear_int(dsim->id, DSIM_INTSRC_SFR_PH_FIFO_EMPTY);
return 0;
}
ret = -ETIMEDOUT;
}
if (IS_DSIM_ON_STATE(dsim) && (ret == -ETIMEDOUT)) {
dsim_err("%s have timed out\n", __func__);
dsim_to_regs_param(dsim, &regs);
__dsim_dump(dsim->id, &regs);
}
return ret;
}
/* wait for until SFR fifo is empty */
int dsim_wait_for_cmd_done(struct dsim_device *dsim)
{
int ret = 0;
/* FIXME: hiber only support for DECON0 */
struct decon_device *decon = get_decon_drvdata(0);
decon_hiber_block_exit(decon);
mutex_lock(&dsim->cmd_lock);
ret = dsim_wait_for_cmd_fifo_empty(dsim, true);
mutex_unlock(&dsim->cmd_lock);
decon_hiber_unblock(decon);
return ret;
}
static bool dsim_fifo_empty_needed(struct dsim_device *dsim, unsigned int data_id,
unsigned long data0)
{
/* read case or partial update command */
if (data_id == MIPI_DSI_DCS_READ
|| data0 == MIPI_DCS_SET_COLUMN_ADDRESS
|| data0 == MIPI_DCS_SET_PAGE_ADDRESS) {
dsim_dbg("%s: id:%d, data=%ld\n", __func__, data_id, data0);
return true;
}
/* Check a FIFO level whether writable or not */
if (!dsim_reg_is_writable_fifo_state(dsim->id))
return true;
return false;
}
int dsim_write_data(struct dsim_device *dsim, u32 id, unsigned long d0, u32 d1)
{
int ret = 0;
bool must_wait = true;
struct decon_device *decon = get_decon_drvdata(0);
if (!dsim->priv.lcdconnected)
return 0;
decon_hiber_block_exit(decon);
mutex_lock(&dsim->cmd_lock);
if (!IS_DSIM_ON_STATE(dsim)) {
dsim_err("DSIM is not ready. state(%d)\n", dsim->state);
ret = -EINVAL;
goto err_exit;
}
DPU_EVENT_LOG_CMD(&dsim->sd, id, d0);
dsim_write_data_dump(dsim, id, d0, d1);
reinit_completion(&dsim->ph_wr_comp);
dsim_reg_clear_int(dsim->id, DSIM_INTSRC_SFR_PH_FIFO_EMPTY);
/* Run write-fail dectector */
mod_timer(&dsim->cmd_timer, jiffies + MIPI_WR_TIMEOUT);
switch (id) {
/* short packet types of packet types for command. */
case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
case MIPI_DSI_DCS_SHORT_WRITE:
case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE:
case MIPI_DSI_DSC_PRA:
case MIPI_DSI_COLOR_MODE_OFF:
case MIPI_DSI_COLOR_MODE_ON:
case MIPI_DSI_SHUTDOWN_PERIPHERAL:
case MIPI_DSI_TURN_ON_PERIPHERAL:
dsim_reg_wr_tx_header(dsim->id, id, d0, d1, false);
must_wait = dsim_fifo_empty_needed(dsim, id, d0);
break;
case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
case MIPI_DSI_DCS_READ:
dsim_reg_wr_tx_header(dsim->id, id, d0, d1, true);
must_wait = dsim_fifo_empty_needed(dsim, id, d0);
break;
/* long packet types of packet types for command. */
case MIPI_DSI_GENERIC_LONG_WRITE:
case MIPI_DSI_DCS_LONG_WRITE:
case MIPI_DSI_DSC_PPS:
dsim_long_data_wr(dsim, d0, d1);
dsim_reg_wr_tx_header(dsim->id, id, d1 & 0xff,
(d1 & 0xff00) >> 8, false);
must_wait = dsim_fifo_empty_needed(dsim, id, *(u8 *)d0);
break;
default:
dsim_info("data id %x is not supported.\n", id);
ret = -EINVAL;
}
ret = dsim_wait_for_cmd_fifo_empty(dsim, must_wait);
if (ret < 0)
dsim_err("ID(%2X): DSIM cmd wr timeout 0x%2x\n",
id, (id == MIPI_DSI_GENERIC_LONG_WRITE || id == MIPI_DSI_DCS_LONG_WRITE) ? *(u8 *)d0 : (unsigned int)d0);
err_exit:
mutex_unlock(&dsim->cmd_lock);
decon_hiber_unblock(decon);
return ret;
}
int dsim_read_data(struct dsim_device *dsim, u32 id, u32 addr, u32 cnt, u8 *buf)
{
u32 rx_fifo, rx_size = 0;
int i, j, ret = 0;
u32 rx_fifo_depth = DSIM_RX_FIFO_MAX_DEPTH;
struct decon_device *decon = get_decon_drvdata(0);
struct dsim_regs regs;
if (!dsim->priv.lcdconnected)
return 0;
decon_hiber_block_exit(decon);
if (IS_DSIM_OFF_STATE(dsim)) {
dsim_err("DSIM is not ready. state(%d)\n", dsim->state);
decon_hiber_unblock(decon);
return -EINVAL;
}
reinit_completion(&dsim->rd_comp);
/* Init RX FIFO before read and clear DSIM_INTSRC */
dsim_reg_clear_int(dsim->id, DSIM_INTSRC_RX_DATA_DONE);
/* Set the maximum packet size returned */
dsim_write_data(dsim,
MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE, cnt, 0);
/* Read request */
dsim_write_data(dsim, id, addr, 0);
if (!wait_for_completion_timeout(&dsim->rd_comp, MIPI_RD_TIMEOUT)) {
dsim_err("MIPI DSIM read Timeout! %2X, %2X, %d\n", id, addr, cnt);
return -ETIMEDOUT;
}
mutex_lock(&dsim->cmd_lock);
DPU_EVENT_LOG_CMD(&dsim->sd, id, (char)addr);
do {
rx_fifo = dsim_reg_get_rx_fifo(dsim->id);
/* Parse the RX packet data types */
switch (rx_fifo & 0xff) {
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
ret = dsim_reg_rx_err_handler(dsim->id, rx_fifo);
if (ret < 0) {
dsim_to_regs_param(dsim, &regs);
__dsim_dump(dsim->id, &regs);
goto exit;
}
break;
case MIPI_DSI_RX_END_OF_TRANSMISSION:
dsim_dbg("EoTp was received from LCD module.\n");
break;
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
dsim_dbg("Short Packet was received from LCD module.\n");
for (i = 0; i < cnt; i++)
buf[i] = (rx_fifo >> (8 + i * 8)) & 0xff;
rx_size = cnt;
break;
case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE:
case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE:
dsim_dbg("Long Packet was received from LCD module.\n");
rx_size = (rx_fifo & 0x00ffff00) >> 8;
dsim_dbg("rx fifo : %8x, response : %x, rx_size : %d\n",
rx_fifo, rx_fifo & 0xff, rx_size);
if (rx_size > cnt) {
dsim_err("rx size is invalid, rx_size: %d, cnt: %d\n", rx_size, cnt);
rx_size = cnt;
}
/* Read data from RX packet payload */
for (i = 0; i < rx_size >> 2; i++) {
rx_fifo = dsim_reg_get_rx_fifo(dsim->id);
for (j = 0; j < 4; j++)
buf[(i*4)+j] = (u8)(rx_fifo >> (j * 8)) & 0xff;
}
if (rx_size % 4) {
rx_fifo = dsim_reg_get_rx_fifo(dsim->id);
for (j = 0; j < rx_size % 4; j++)
buf[4 * i + j] =
(u8)(rx_fifo >> (j * 8)) & 0xff;
}
break;
default:
dsim_err("Packet format is invaild. %x\n", rx_fifo);
dsim_to_regs_param(dsim, &regs);
__dsim_dump(dsim->id, &regs);
ret = -EBUSY;
goto exit;
}
} while (!dsim_reg_rx_fifo_is_empty(dsim->id) && --rx_fifo_depth);
ret = rx_size;
if (!rx_fifo_depth) {
dsim_err("Check DPHY values about HS clk.\n");
dsim_to_regs_param(dsim, &regs);
__dsim_dump(dsim->id, &regs);
ret = -EBUSY;
}
exit:
mutex_unlock(&dsim->cmd_lock);
decon_hiber_unblock(decon);
return ret;
}
static void dsim_cmd_fail_detector(unsigned long arg)
{
struct dsim_device *dsim = (struct dsim_device *)arg;
struct decon_device *decon = get_decon_drvdata(0);
struct dsim_regs regs;
decon_hiber_block(decon);
dsim_dbg("%s +\n", __func__);
if (IS_DSIM_OFF_STATE(dsim)) {
dsim_err("%s: DSIM is not ready. state(%d)\n", __func__,
dsim->state);
goto exit;
}
/* If already FIFO empty even though the timer is no pending */
if (!timer_pending(&dsim->cmd_timer)
&& dsim_reg_header_fifo_is_empty(dsim->id)) {
reinit_completion(&dsim->ph_wr_comp);
dsim_reg_clear_int(dsim->id, DSIM_INTSRC_SFR_PH_FIFO_EMPTY);
goto exit;
}
dsim_to_regs_param(dsim, &regs);
__dsim_dump(dsim->id, &regs);
exit:
decon_hiber_unblock(decon);
dsim_dbg("%s -\n", __func__);
return;
}
#if defined(CONFIG_EXYNOS_BTS)
static void dsim_bts_print_info(struct bts_decon_info *info)
{
int i;
for (i = 0; i < BTS_DPP_MAX; ++i) {
if (!info->dpp[i].used)
continue;
dsim_info("\t\tDPP[%d] b(%d) s(%d %d) d(%d %d %d %d) r(%d)\n",
i, info->dpp[i].bpp,
info->dpp[i].src_w, info->dpp[i].src_h,
info->dpp[i].dst.x1, info->dpp[i].dst.x2,
info->dpp[i].dst.y1, info->dpp[i].dst.y2,
info->dpp[i].rotation);
}
}
#endif
static void dsim_underrun_info(struct dsim_device *dsim)
{
#if defined(CONFIG_EXYNOS_BTS)
int i, decon_cnt;
struct decon_device *decon = get_decon_drvdata(0);
unsigned long mif = 0, iint = 0, disp = 0;
dsim_info("\tMIF(%lu), INT(%lu), DISP(%lu)\n",
mif = cal_dfs_get_rate(ACPM_DVFS_MIF),
iint = cal_dfs_get_rate(ACPM_DVFS_INT),
disp = cal_dfs_get_rate(ACPM_DVFS_DISP));
if (decon == NULL)
return;
decon_cnt = decon->dt.decon_cnt;
for (i = 0; i < decon_cnt; ++i) {
decon = get_decon_drvdata(i);
if (decon) {
dsim_info("\tDECON%d: bw(%u %u), disp(%u %u), p(%u)\n",
decon->id,
decon->bts.prev_total_bw,
decon->bts.total_bw,
decon->bts.prev_max_disp_freq,
decon->bts.max_disp_freq,
decon->bts.peak);
dsim_bts_print_info(&decon->bts.bts_info);
decon_abd_save_udr(&decon->abd, mif, iint, disp);
}
}
#endif
}
static irqreturn_t dsim_irq_handler(int irq, void *dev_id)
{
unsigned int int_src;
struct dsim_device *dsim = dev_id;
struct decon_device *decon = get_decon_drvdata(0);
struct dsim_regs regs;
#ifdef CONFIG_EXYNOS_PD
int active;
#endif
u32 vm_line_cnt;
spin_lock(&dsim->slock);
#ifdef CONFIG_EXYNOS_PD
active = pm_runtime_active(dsim->dev);
if (!active) {
dsim_info("dsim power(%d), state(%d)\n", active, dsim->state);
spin_unlock(&dsim->slock);
return IRQ_HANDLED;
}
#endif
int_src = dsim_reg_get_int_and_clear(dsim->id);
if (int_src & DSIM_INTSRC_SFR_PH_FIFO_EMPTY) {
del_timer(&dsim->cmd_timer);
complete(&dsim->ph_wr_comp);
DPU_EVENT_LOG(DPU_EVT_DSIM_PH_FIFO_EMPTY, &dsim->sd, ktime_set(0, 0));
dsim_dbg("dsim%d PH_FIFO_EMPTY irq occurs\n", dsim->id);
}
if (int_src & DSIM_INTSRC_SFR_PL_FIFO_EMPTY)
DPU_EVENT_LOG(DPU_EVT_DSIM_PL_FIFO_EMPTY, &dsim->sd, ktime_set(0, 0));
if (int_src & DSIM_INTSRC_RX_DATA_DONE)
complete(&dsim->rd_comp);
if (int_src & DSIM_INTSRC_FRAME_DONE) {
dsim->continuous_underrun_cnt = 0;
dsim_dbg("dsim%d framedone irq occurs\n", dsim->id);
}
if (int_src & DSIM_INTSRC_ERR_RX_ECC)
dsim_err("RX ECC Multibit error was detected!\n");
if (int_src & DSIM_INTSRC_UNDER_RUN) {
dsim->total_underrun_cnt++;
dsim->continuous_underrun_cnt++;
DPU_EVENT_LOG(DPU_EVT_DSIM_UNDER_RUN, &dsim->sd, ktime_set(0, 0));
dsim_info("dsim%d underrun irq occurs(%d) (%d)\n", dsim->id,
dsim->total_underrun_cnt, dsim->continuous_underrun_cnt);
dsim_underrun_info(dsim);
if (dsim->lcd_info.mode == DECON_VIDEO_MODE) {
vm_line_cnt = dsim_reg_get_vm_line_cnt(dsim->id);
dsim_info("dsim%d underrun vm_line_cnt: (%08x)\n", dsim->id, vm_line_cnt);
if (decon && dsim->continuous_underrun_max > 0 &&
(dsim->continuous_underrun_cnt >= dsim->continuous_underrun_max)) {
decon_dump(decon);
BUG();
}
dsim_to_regs_param(dsim, &regs);
__dsim_dump(dsim->id, &regs);
}
}
if (int_src & DSIM_INTSRC_VT_STATUS) {
dsim_dbg("dsim%d vt_status(vsync) irq occurs\n", dsim->id);
if (decon) {
decon->vsync.timestamp = ktime_get();
wake_up_interruptible_all(&decon->vsync.wait);
}
}
spin_unlock(&dsim->slock);
return IRQ_HANDLED;
}
static void dsim_clocks_info(struct dsim_device *dsim)
{
}
static int dsim_get_clocks(struct dsim_device *dsim)
{
dsim->res.aclk = devm_clk_get(dsim->dev, "aclk");
if (IS_ERR_OR_NULL(dsim->res.aclk)) {
dsim_err("failed to get aclk\n");
return PTR_ERR(dsim->res.aclk);
}
return 0;
}
static int dsim_get_gpios(struct dsim_device *dsim)
{
struct device *dev = dsim->dev;
struct dsim_resources *res = &dsim->res;
dsim_info("%s +\n", __func__);
if (of_get_property(dev->of_node, "gpios", NULL) != NULL) {
/* panel reset */
res->lcd_reset = of_get_gpio(dev->of_node, 0);
if (res->lcd_reset < 0) {
dsim_err("failed to get lcd reset GPIO");
return -ENODEV;
}
res->lcd_power[0] = of_get_gpio(dev->of_node, 1);
if (res->lcd_power[0] < 0) {
res->lcd_power[0] = -1;
dsim_info("This board doesn't support LCD power GPIO");
}
res->lcd_power[1] = of_get_gpio(dev->of_node, 2);
if (res->lcd_power[1] < 0) {
res->lcd_power[1] = -1;
dsim_info("This board doesn't support 2nd LCD power GPIO");
}
res->lcd_power[2] = of_get_gpio(dev->of_node, 3);
if (res->lcd_power[2] < 0) {
res->lcd_power[2] = -1;
dsim_info("This board doesn't support 3rd LCD power GPIO");
}
}
dsim_info("%s -\n", __func__);
return 0;
}
static int dsim_get_regulator(struct dsim_device *dsim)
{
struct device *dev = dsim->dev;
struct dsim_resources *res = &dsim->res;
char *str_regulator_1p8v = NULL;
char *str_regulator_3p3v = NULL;
res->regulator_1p8v = NULL;
res->regulator_3p3v = NULL;
if(!of_property_read_string(dev->of_node, "regulator_1p8v",
(const char **)&str_regulator_1p8v)) {
res->regulator_1p8v = regulator_get(dev, str_regulator_1p8v);
if (IS_ERR(res->regulator_1p8v)) {
dsim_err("%s : dsim regulator 1.8V get failed\n", __func__);
res->regulator_1p8v = NULL;
}
}
if(!of_property_read_string(dev->of_node, "regulator_3p3v",
(const char **)&str_regulator_3p3v)) {
res->regulator_3p3v = regulator_get(dev, str_regulator_3p3v);
if (IS_ERR(res->regulator_3p3v)) {
dsim_err("%s : dsim regulator 3.3V get failed\n", __func__);
res->regulator_3p3v = NULL;
}
}
return 0;
}
int dsim_reset_panel(struct dsim_device *dsim)
{
struct dsim_resources *res = &dsim->res;
int ret;
dsim_dbg("%s +\n", __func__);
run_list(dsim->dev, __func__);
if (res->lcd_reset <= 0)
return 0;
ret = gpio_request_one(res->lcd_reset, GPIOF_OUT_INIT_HIGH, "lcd_reset");
if (ret < 0) {
dsim_err("failed to get LCD reset GPIO\n");
return -EINVAL;
}
usleep_range(5000, 6000);
gpio_set_value(res->lcd_reset, 0);
usleep_range(5000, 6000);
gpio_set_value(res->lcd_reset, 1);
gpio_free(res->lcd_reset);
usleep_range(10000, 11000);
dsim_dbg("%s -\n", __func__);
return 0;
}
int dsim_set_panel_power_early(struct dsim_device *dsim)
{
dsim_info("%s +\n", __func__);
run_list(dsim->dev, __func__);
dsim_info("%s -\n", __func__);
return 0;
}
int dsim_set_panel_power(struct dsim_device *dsim, bool on)
{
struct dsim_resources *res = &dsim->res;
int ret;
dsim_dbg("%s(%d) +\n", __func__, on);
if (on)
run_list(dsim->dev, "dsim_set_panel_power_enable");
else
run_list(dsim->dev, "dsim_set_panel_power_disable");
if (on) {
if (res->lcd_power[0] > 0) {
ret = gpio_request_one(res->lcd_power[0],
GPIOF_OUT_INIT_HIGH, "lcd_power0");
if (ret < 0) {
dsim_err("failed LCD power on\n");
return -EINVAL;
}
gpio_free(res->lcd_power[0]);
usleep_range(10000, 11000);
}
if (res->lcd_power[1] > 0) {
ret = gpio_request_one(res->lcd_power[1],
GPIOF_OUT_INIT_HIGH, "lcd_power1");
if (ret < 0) {
dsim_err("failed 2nd LCD power on\n");
return -EINVAL;
}
gpio_free(res->lcd_power[1]);
usleep_range(10000, 11000);
}
if (res->lcd_power[2] > 0) {
ret = gpio_request_one(res->lcd_power[2],
GPIOF_OUT_INIT_HIGH, "lcd_power2");
if (ret < 0) {
dsim_err("failed 3rd LCD power on\n");
return -EINVAL;
}
gpio_free(res->lcd_power[2]);
usleep_range(10000, 11000);
}
if (res->regulator_1p8v > 0) {
ret = regulator_enable(res->regulator_1p8v);
if (ret) {
dsim_err("%s : dsim regulator 1.8V enable failed\n", __func__);
return ret;
}
usleep_range(5000, 6000);
}
if (res->regulator_3p3v > 0) {
ret = regulator_enable(res->regulator_3p3v);
if (ret) {
dsim_err("%s : dsim regulator 3.3V enable failed\n", __func__);
return ret;
}
}
} else {
if (res->lcd_reset > 0) {
ret = gpio_request_one(res->lcd_reset, GPIOF_OUT_INIT_LOW,
"lcd_reset");
if (ret < 0) {
dsim_err("failed LCD reset off\n");
return -EINVAL;
}
gpio_free(res->lcd_reset);
}
if (res->lcd_power[0] > 0) {
ret = gpio_request_one(res->lcd_power[0],
GPIOF_OUT_INIT_LOW, "lcd_power0");
if (ret < 0) {
dsim_err("failed LCD power off\n");
return -EINVAL;
}
gpio_free(res->lcd_power[0]);
usleep_range(5000, 6000);
}
if (res->lcd_power[1] > 0) {
ret = gpio_request_one(res->lcd_power[1],
GPIOF_OUT_INIT_LOW, "lcd_power1");
if (ret < 0) {
dsim_err("failed 2nd LCD power off\n");
return -EINVAL;
}
gpio_free(res->lcd_power[1]);
usleep_range(5000, 6000);
}
if (res->lcd_power[2] > 0) {
ret = gpio_request_one(res->lcd_power[2],
GPIOF_OUT_INIT_LOW, "lcd_power2");
if (ret < 0) {
dsim_err("failed 3nd LCD power off\n");
return -EINVAL;
}
gpio_free(res->lcd_power[2]);
usleep_range(5000, 6000);
}
if (res->regulator_1p8v > 0) {
ret = regulator_disable(res->regulator_1p8v);
if (ret) {
dsim_err("%s : dsim regulator 1.8V disable failed\n", __func__);
return ret;
}
}
if (res->regulator_3p3v > 0) {
ret = regulator_disable(res->regulator_3p3v);
if (ret) {
dsim_err("%s : dsim regulator 3.3V disable failed\n", __func__);
return ret;
}
}
}
dsim_dbg("%s(%d) -\n", __func__, on);
return 0;
}
static void dsim_phy_status(struct phy *phy)
{
void __iomem *phy_iso_regs;
u32 phy_iso = 0;
/* 1: Isolation bypassed, 0: Isolation enabled */
dsim_dbg("%s, PHY count : %d\n", __func__, phy->power_count);
phy_iso_regs = ioremap(MIPI_PHY_M4S4_CON, 0x10);
phy_iso = readl(phy_iso_regs);
if ((phy_iso & M4S4_TOP_ISO_BYPASS) != M4S4_TOP_ISO_BYPASS) {
dsim_err("Isolation bypass should be set\n");
phy_iso = M4S4_TOP_ISO_BYPASS;
writel(phy_iso, phy_iso_regs);
dsim_err("Isolation bypass was set\n");
}
iounmap(phy_iso_regs);
}
static int _dsim_enable(struct dsim_device *dsim, enum dsim_state state)
{
bool panel_ctrl;
if (IS_DSIM_ON_STATE(dsim)) {
dsim_warn("%s dsim already on(%s)\n",
__func__, dsim_state_names[dsim->state]);
dsim->state = state;
return 0;
}
dsim_dbg("%s %s +\n", __func__, dsim_state_names[dsim->state]);
#if defined(CONFIG_CPU_IDLE)
exynos_update_ip_idle_status(dsim->idle_ip_index, 0);
#endif
pm_runtime_get_sync(dsim->dev);
/* DPHY reset control from DSIM */
dpu_sysreg_select_dphy_rst_control(dsim->res.ss_regs, dsim->id, 1);
/* DPHY power on : iso release */
phy_power_on(dsim->phy);
if (dsim->phy_ex)
phy_power_on(dsim->phy_ex);
/* DPHY status */
dsim_phy_status(dsim->phy);
panel_ctrl = (dsim->state == DSIM_STATE_OFF) ? true : false;
dsim_reg_init(dsim->id, &dsim->lcd_info, &dsim->clks, panel_ctrl);
dsim_reg_start(dsim->id);
dsim->state = state;
enable_irq(dsim->res.irq);
return 0;
}
static int dsim_enable(struct dsim_device *dsim)
{
int ret;
enum dsim_state prev_state = dsim->state;
enum dsim_state next_state = DSIM_STATE_ON;
if (prev_state == next_state) {
dsim_warn("dsim-%d %s already %s state\n", dsim->id,
__func__, dsim_state_names[dsim->state]);
return 0;
}
dsim_info("dsim-%d %s +\n", dsim->id, __func__);
ret = _dsim_enable(dsim, next_state);
if (ret < 0) {
dsim_err("dsim-%d failed to set %s (ret %d)\n",
dsim->id, dsim_state_names[next_state], ret);
goto out;
}
if (prev_state != DSIM_STATE_INIT)
call_panel_ops(dsim, displayon, dsim);
dsim_info("dsim-%d %s - (state:%s -> %s)\n", dsim->id, __func__,
dsim_state_names[prev_state],
dsim_state_names[dsim->state]);
out:
return ret;
}
static int dsim_doze(struct dsim_device *dsim)
{
int ret;
enum dsim_state prev_state = dsim->state;
enum dsim_state next_state = DSIM_STATE_DOZE;
if (prev_state == next_state) {
dsim_warn("dsim-%d %s already %s state\n", dsim->id,
__func__, dsim_state_names[dsim->state]);
return 0;
}
dsim_info("dsim-%d %s +\n", dsim->id, __func__);
ret = _dsim_enable(dsim, next_state);
if (ret < 0) {
dsim_err("dsim-%d failed to set %s (ret %d)\n",
dsim->id, dsim_state_names[next_state], ret);
goto out;
}
if (prev_state != DSIM_STATE_INIT)
call_panel_ops(dsim, doze, dsim);
dsim_info("dsim-%d %s - (state:%s -> %s)\n", dsim->id, __func__,
dsim_state_names[prev_state],
dsim_state_names[dsim->state]);
out:
return ret;
}
static int _dsim_disable(struct dsim_device *dsim, enum dsim_state state)
{
struct dsim_regs regs;
if (IS_DSIM_OFF_STATE(dsim)) {
dsim_warn("%s dsim already off(%s)\n",
__func__, dsim_state_names[dsim->state]);
if (state == DSIM_STATE_OFF)
dsim_set_panel_power(dsim, 0);
dsim->state = state;
return 0;
}
dsim_dbg("%s %s +\n", __func__, dsim_state_names[dsim->state]);
/* Wait for current read & write CMDs. */
mutex_lock(&dsim->cmd_lock);
del_timer(&dsim->cmd_timer);
dsim->state = state;
mutex_unlock(&dsim->cmd_lock);
if (dsim_reg_stop(dsim->id, dsim->data_lane) < 0) {
dsim_to_regs_param(dsim, &regs);
__dsim_dump(dsim->id, &regs);
}
disable_irq(dsim->res.irq);
/* HACK */
phy_power_off(dsim->phy);
if (dsim->phy_ex)
phy_power_off(dsim->phy_ex);
if (state == DSIM_STATE_OFF)
dsim_set_panel_power(dsim, 0);
pm_runtime_put_sync(dsim->dev);
dsim_dbg("%s %s -\n", __func__, dsim_state_names[dsim->state]);
#if defined(CONFIG_CPU_IDLE)
exynos_update_ip_idle_status(dsim->idle_ip_index, 1);
#endif
return 0;
}
static int dsim_disable(struct dsim_device *dsim)
{
int ret;
enum dsim_state prev_state = dsim->state;
enum dsim_state next_state = DSIM_STATE_OFF;
if (prev_state == next_state) {
dsim_warn("dsim-%d %s already %s state\n", dsim->id,
__func__, dsim_state_names[dsim->state]);
return 0;
}
dsim_info("dsim-%d %s +\n", dsim->id, __func__);
if (dsim->lcd_info.mode != DECON_VIDEO_MODE)
call_panel_ops(dsim, suspend, dsim);
ret = _dsim_disable(dsim, next_state);
if (ret < 0) {
dsim_err("dsim-%d failed to set %s (ret %d)\n",
dsim->id, dsim_state_names[next_state], ret);
goto out;
}
dsim_info("dsim-%d %s - (state:%s -> %s)\n", dsim->id, __func__,
dsim_state_names[prev_state],
dsim_state_names[dsim->state]);
out:
return ret;
}
static int dsim_doze_suspend(struct dsim_device *dsim)
{
int ret;
enum dsim_state prev_state = dsim->state;
enum dsim_state next_state = DSIM_STATE_DOZE_SUSPEND;
if (prev_state == next_state) {
dsim_warn("dsim-%d %s already %s state\n", dsim->id,
__func__, dsim_state_names[dsim->state]);
return 0;
}
dsim_info("dsim-%d %s +\n", dsim->id, __func__);
call_panel_ops(dsim, doze_suspend, dsim);
ret = _dsim_disable(dsim, next_state);
if (ret < 0) {
dsim_err("dsim-%d failed to set %s (ret %d)\n",
dsim->id, dsim_state_names[next_state], ret);
goto out;
}
dsim_info("dsim-%d %s - (state:%s -> %s)\n", dsim->id, __func__,
dsim_state_names[prev_state],
dsim_state_names[dsim->state]);
out:
return ret;
}
static int dsim_enter_ulps(struct dsim_device *dsim)
{
int ret = 0;
DPU_EVENT_START();
dsim_dbg("%s +\n", __func__);
if (!IS_DSIM_ON_STATE(dsim)) {
ret = -EBUSY;
goto err;
}
/* Wait for current read & write CMDs. */
mutex_lock(&dsim->cmd_lock);
dsim->state = DSIM_STATE_ULPS;
mutex_unlock(&dsim->cmd_lock);
disable_irq(dsim->res.irq);
ret = dsim_reg_stop_and_enter_ulps(dsim->id, dsim->lcd_info.ddi_type,
dsim->data_lane);
if (ret < 0)
dsim_dump(dsim);
phy_power_off(dsim->phy);
if (dsim->phy_ex)
phy_power_off(dsim->phy_ex);
pm_runtime_put_sync(dsim->dev);
#if defined(CONFIG_CPU_IDLE)
exynos_update_ip_idle_status(dsim->idle_ip_index, 1);
#endif
DPU_EVENT_LOG(DPU_EVT_ENTER_ULPS, &dsim->sd, start);
err:
dsim_dbg("%s -\n", __func__);
return ret;
}
static int dsim_exit_ulps(struct dsim_device *dsim)
{
int ret = 0;
DPU_EVENT_START();
dsim_dbg("%s +\n", __func__);
if (dsim->state != DSIM_STATE_ULPS) {
ret = -EBUSY;
goto err;
}
#if defined(CONFIG_CPU_IDLE)
exynos_update_ip_idle_status(dsim->idle_ip_index, 0);
#endif
pm_runtime_get_sync(dsim->dev);
/* DPHY reset control from DSIM */
dpu_sysreg_select_dphy_rst_control(dsim->res.ss_regs, dsim->id, 1);
/* DPHY power on : iso release */
phy_power_on(dsim->phy);
if (dsim->phy_ex)
phy_power_on(dsim->phy_ex);
/* DPHY status */
dsim_phy_status(dsim->phy);
dsim_reg_init(dsim->id, &dsim->lcd_info, &dsim->clks, false);
ret = dsim_reg_exit_ulps_and_start(dsim->id, dsim->lcd_info.ddi_type,
dsim->data_lane);
if (ret < 0)
dsim_dump(dsim);
enable_irq(dsim->res.irq);
dsim->state = DSIM_STATE_ON;
DPU_EVENT_LOG(DPU_EVT_EXIT_ULPS, &dsim->sd, start);
err:
dsim_dbg("%s -\n", __func__);
return 0;
}
static int dsim_s_stream(struct v4l2_subdev *sd, int enable)
{
struct dsim_device *dsim = container_of(sd, struct dsim_device, sd);
if (enable)
return dsim_enable(dsim);
else
return dsim_disable(dsim);
}
static int dsim_free_fb_resource(struct dsim_device *dsim)
{
dsim_info("%s one to one unmapping: 0x%x\n", __func__, dsim->phys_addr);
/* unmap */
iovmm_unmap_oto(dsim->dev, dsim->phys_addr);
/* unreserve memory */
/* if force upload enabled, DO NOT release rmem */
if (!sec_debug_enter_upload()) {
of_reserved_mem_device_release(dsim->dev);
dsim_info("%s rmem release.\n", __func__);
} else {
dsim_info("%s rmem keep.\n", __func__);
}
/* update state */
dsim->fb_reservation = false;
dsim->phys_addr = 0xdead;
dsim->phys_size = 0;
return 0;
}
static int dsim_acquire_fb_resource(struct dsim_device *dsim)
{
int ret = 0;
u32 res[2];
ret = of_reserved_mem_device_init_by_idx(dsim->dev, dsim->dev->of_node, 0);
if (ret)
dsim_err("failed reserved mem device init: %d\n", ret);
if (!of_property_read_u32_array(dsim->dev->of_node, "fb_reserved", res, 2)) {
dsim->fb_reservation = true;
dsim->phys_addr = res[0];
dsim->phys_size = res[1];
} else {
dsim->fb_reservation = false;
dsim->phys_addr = 0;
dsim->phys_size = 0;
}
dsim_info("%s: fb_reserved: phys_addr(0x%08x) phys_size(0x%08x).\n",
__func__, dsim->phys_addr, dsim->phys_size);
if (dsim->fb_reservation) {
ret = iovmm_map_oto(dsim->dev, dsim->phys_addr, dsim->phys_size);
if (ret)
dsim_err("failed one to one mapping: %d\n", ret);
else
dsim_info("one to one mapping: 0x%x, 0x%x\n",
dsim->phys_addr, dsim->phys_size);
}
return ret;
}
static long dsim_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
struct dsim_device *dsim = container_of(sd, struct dsim_device, sd);
int ret = 0;
switch (cmd) {
case DSIM_IOC_GET_LCD_INFO:
v4l2_set_subdev_hostdata(sd, &dsim->lcd_info);
break;
case DSIM_IOC_ENTER_ULPS:
if ((unsigned long)arg)
ret = dsim_enter_ulps(dsim);
else
ret = dsim_exit_ulps(dsim);
break;
case DSIM_IOC_DUMP:
dsim_dump(dsim);
break;
case DSIM_IOC_GET_WCLK:
v4l2_set_subdev_hostdata(sd, &dsim->clks.word_clk);
break;
case EXYNOS_DPU_GET_ACLK:
return clk_get_rate(dsim->res.aclk);
case DSIM_IOC_FREE_FB_RES:
ret = dsim_free_fb_resource(dsim);
break;
case DSIM_IOC_DOZE:
ret = dsim_doze(dsim);
break;
case DSIM_IOC_DOZE_SUSPEND:
ret = dsim_doze_suspend(dsim);
break;
default:
dsim_err("unsupported ioctl");
ret = -EINVAL;
break;
}
return ret;
}
static const struct v4l2_subdev_core_ops dsim_sd_core_ops = {
.ioctl = dsim_ioctl,
};
static const struct v4l2_subdev_video_ops dsim_sd_video_ops = {
.s_stream = dsim_s_stream,
};
static const struct v4l2_subdev_ops dsim_subdev_ops = {
.core = &dsim_sd_core_ops,
.video = &dsim_sd_video_ops,
};
static void dsim_init_subdev(struct dsim_device *dsim)
{
struct v4l2_subdev *sd = &dsim->sd;
v4l2_subdev_init(sd, &dsim_subdev_ops);
sd->owner = THIS_MODULE;
sd->grp_id = dsim->id;
snprintf(sd->name, sizeof(sd->name), "%s.%d", "dsim-sd", dsim->id);
v4l2_set_subdevdata(sd, dsim);
}
#if defined(CONFIG_EXYNOS_READ_ESD_SOLUTION) && defined(READ_ESD_SOLUTION_TEST)
static ssize_t dsim_esd_test_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int ret;
unsigned long cmd;
struct dsim_device *dsim = dev_get_drvdata(dev);
ret = kstrtoul(buf, 0, &cmd);
if (ret)
return ret;
dsim->esd_test = cmd;
return count;
}
static DEVICE_ATTR(esd_test, 0644, NULL, dsim_esd_test_store);
int dsim_create_esd_test_sysfs(struct dsim_device *dsim)
{
int ret = 0;
ret = device_create_file(dsim->dev, &dev_attr_esd_test);
if (ret)
dsim_err("failed to create command read & write sysfs\n");
return ret;
}
#endif
static int dsim_cmd_sysfs_write(struct dsim_device *dsim, bool on)
{
int ret = 0;
if (on)
ret = dsim_write_data(dsim, MIPI_DSI_DCS_SHORT_WRITE,
MIPI_DCS_SET_DISPLAY_ON, 0);
else
ret = dsim_write_data(dsim, MIPI_DSI_DCS_SHORT_WRITE,
MIPI_DCS_SET_DISPLAY_OFF, 0);
if (ret < 0)
dsim_err("Failed to write test data!\n");
else
dsim_dbg("Succeeded to write test data!\n");
return ret;
}
static int dsim_cmd_sysfs_read(struct dsim_device *dsim)
{
int ret = 0;
unsigned int id;
u8 buf[4];
/* dsim sends the request for the lcd id and gets it buffer */
ret = dsim_read_data(dsim, MIPI_DSI_DCS_READ,
MIPI_DCS_GET_DISPLAY_ID, DSIM_DDI_ID_LEN, buf);
id = *(unsigned int *)buf;
if (ret < 0)
dsim_err("Failed to read panel id!\n");
else
dsim_info("Suceeded to read panel id : 0x%08x\n", id);
return ret;
}
static ssize_t dsim_cmd_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return 0;
}
static ssize_t dsim_cmd_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int ret;
unsigned long cmd;
struct dsim_device *dsim = dev_get_drvdata(dev);
ret = kstrtoul(buf, 0, &cmd);
if (ret)
return ret;
switch (cmd) {
case 1:
ret = dsim_cmd_sysfs_read(dsim);
call_panel_ops(dsim, dump, dsim);
if (ret)
return ret;
break;
case 2:
ret = dsim_cmd_sysfs_write(dsim, true);
dsim_info("Dsim write command, display on!!\n");
if (ret)
return ret;
break;
case 3:
ret = dsim_cmd_sysfs_write(dsim, false);
dsim_info("Dsim write command, display off!!\n");
if (ret)
return ret;
break;
default :
dsim_info("unsupportable command\n");
break;
}
return count;
}
static DEVICE_ATTR(cmd_rw, 0644, dsim_cmd_sysfs_show, dsim_cmd_sysfs_store);
static ssize_t dsim_underrun_max_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
int size = 0;
size = (ssize_t)sprintf(buf, "%d\n", dsim->continuous_underrun_max);
return size;
}
static ssize_t dsim_underrun_max_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
unsigned int cont_underrun_max;
int ret;
ret = kstrtouint(buf, 0, &cont_underrun_max);
if (ret)
return ret;
dsim->continuous_underrun_max = cont_underrun_max;
return count;
}
static DEVICE_ATTR(cont_underrun_max, 0600, dsim_underrun_max_sysfs_show, dsim_underrun_max_sysfs_store);
int dsim_create_cmd_rw_sysfs(struct dsim_device *dsim)
{
int ret = 0;
ret = device_create_file(dsim->dev, &dev_attr_cmd_rw);
if (ret)
dsim_err("failed to create command read & write sysfs\n");
ret = device_create_file(dsim->dev, &dev_attr_cont_underrun_max);
if (ret)
dsim_err("failed to create cont_underrun_max sysfs\n");
return ret;
}
static void dsim_parse_lcd_info(struct dsim_device *dsim)
{
u32 res[14];
struct device_node *node;
unsigned int mres_num = 1;
u32 mres_w[3] = {0, };
u32 mres_h[3] = {0, };
u32 mres_dsc_w[3] = {0, };
u32 mres_dsc_h[3] = {0, };
u32 mres_dsc_en[3] = {0, };
u32 hdr_num = 0;
u32 hdr_type[HDR_CAPA_NUM] = {0, };
u32 hdr_mxl = 0;
u32 hdr_mal = 0;
u32 hdr_mnl = 0;
int k;
u32 udr_max_num = 0;
node = of_parse_phandle(dsim->dev->of_node, "lcd_info", 0);
of_property_read_u32(node, "mode", &dsim->lcd_info.mode);
dsim_info("%s mode\n", dsim->lcd_info.mode ? "command" : "video");
of_property_read_u32_array(node, "resolution", res, 2);
dsim->lcd_info.xres = res[0];
dsim->lcd_info.yres = res[1];
dsim_info("LCD(%s) resolution: xres(%d), yres(%d)\n",
of_node_full_name(node), res[0], res[1]);
of_property_read_u32_array(node, "size", res, 2);
dsim->lcd_info.width = res[0];
dsim->lcd_info.height = res[1];
dsim_dbg("LCD size: width(%d), height(%d)\n", res[0], res[1]);
of_property_read_u32(node, "timing,refresh", &dsim->lcd_info.fps);
dsim_dbg("LCD refresh rate(%d)\n", dsim->lcd_info.fps);
of_property_read_u32_array(node, "timing,h-porch", res, 3);
dsim->lcd_info.hbp = res[0];
dsim->lcd_info.hfp = res[1];
dsim->lcd_info.hsa = res[2];
dsim_dbg("hbp(%d), hfp(%d), hsa(%d)\n", res[0], res[1], res[2]);
of_property_read_u32_array(node, "timing,v-porch", res, 3);
dsim->lcd_info.vbp = res[0];
dsim->lcd_info.vfp = res[1];
dsim->lcd_info.vsa = res[2];
dsim_dbg("vbp(%d), vfp(%d), vsa(%d)\n", res[0], res[1], res[2]);
of_property_read_u32(node, "timing,dsi-hs-clk", &dsim->lcd_info.hs_clk);
dsim->clks.hs_clk = dsim->lcd_info.hs_clk;
dsim_dbg("requested hs clock(%d)\n", dsim->lcd_info.hs_clk);
#if defined(CONFIG_EXYNOS_DSIM_DITHER)
of_property_read_u32_array(node, "timing,pmsk", res, 14);
#else
of_property_read_u32_array(node, "timing,pmsk", res, 4);
#endif
dsim->lcd_info.dphy_pms.p = res[0];
dsim->lcd_info.dphy_pms.m = res[1];
dsim->lcd_info.dphy_pms.s = res[2];
dsim->lcd_info.dphy_pms.k = res[3];
dsim_dbg("p(%d), m(%d), s(%d), k(%d)\n", res[0], res[1], res[2], res[3]);
#if defined(CONFIG_EXYNOS_DSIM_DITHER)
dsim->lcd_info.dphy_pms.mfr = res[4];
dsim->lcd_info.dphy_pms.mrr = res[5];
dsim->lcd_info.dphy_pms.sel_pf = res[6];
dsim->lcd_info.dphy_pms.icp = res[7];
dsim->lcd_info.dphy_pms.afc_enb = res[8];
dsim->lcd_info.dphy_pms.extafc = res[9];
dsim->lcd_info.dphy_pms.feed_en = res[10];
dsim->lcd_info.dphy_pms.fsel = res[11];
dsim->lcd_info.dphy_pms.fout_mask = res[12];
dsim->lcd_info.dphy_pms.rsel = res[13];
dsim_dbg(" mfr(%d), mrr(0x%x), sel_pf(%d), icp(%d)\n",
res[4], res[5], res[6], res[7]);
dsim_dbg(" afc_enb(%d), extafc(%d), feed_en(%d), fsel(%d)\n",
res[8], res[9], res[10], res[11]);
dsim_dbg(" fout_mask(%d), rsel(%d)\n", res[12], res[13]);
#endif
of_property_read_u32(node, "timing,dsi-escape-clk",
&dsim->lcd_info.esc_clk);
dsim->clks.esc_clk = dsim->lcd_info.esc_clk;
dsim_dbg("requested escape clock(%d)\n", dsim->lcd_info.esc_clk);
of_property_read_u32(node, "mic_en", &dsim->lcd_info.mic_enabled);
dsim_info("mic enabled (%d)\n", dsim->lcd_info.mic_enabled);
of_property_read_u32(node, "type_of_ddi", &dsim->lcd_info.ddi_type);
dsim_dbg("ddi type(%d)\n", dsim->lcd_info.ddi_type);
of_property_read_u32(node, "dsc_en", &dsim->lcd_info.dsc_enabled);
dsim_info("dsc is %s\n", dsim->lcd_info.dsc_enabled ? "enabled" : "disabled");
if (dsim->lcd_info.dsc_enabled) {
of_property_read_u32(node, "dsc_cnt", &dsim->lcd_info.dsc_cnt);
dsim_info("dsc count(%d)\n", dsim->lcd_info.dsc_cnt);
of_property_read_u32(node, "dsc_slice_num",
&dsim->lcd_info.dsc_slice_num);
dsim_info("dsc slice count(%d)\n", dsim->lcd_info.dsc_slice_num);
of_property_read_u32(node, "dsc_slice_h",
&dsim->lcd_info.dsc_slice_h);
dsim_info("dsc slice height(%d)\n", dsim->lcd_info.dsc_slice_h);
}
of_property_read_u32(node, "data_lane", &dsim->data_lane_cnt);
dsim_info("using data lane count(%d)\n", dsim->data_lane_cnt);
dsim->lcd_info.data_lane = dsim->data_lane_cnt;
of_property_read_u32(node, "mres_en", &dsim->lcd_info.dt_lcd_mres.mres_en);
dsim_info("mres_en(%d)\n", dsim->lcd_info.dt_lcd_mres.mres_en);
dsim->lcd_info.mres_mode = 0; /* 0=WQHD, 1=FHD, 2=HD */
dsim->lcd_info.dt_lcd_mres.mres_number = mres_num; /* default = 1 */
if (dsim->lcd_info.dt_lcd_mres.mres_en) {
of_property_read_u32(node, "mres_number", &mres_num);
dsim->lcd_info.dt_lcd_mres.mres_number = mres_num;
dsim_info("mres_number(%d)\n", mres_num);
of_property_read_u32_array(node, "mres_width", mres_w, mres_num);
of_property_read_u32_array(node, "mres_height", mres_h, mres_num);
of_property_read_u32_array(node, "mres_dsc_width", mres_dsc_w, mres_num);
of_property_read_u32_array(node, "mres_dsc_height", mres_dsc_h, mres_num);
of_property_read_u32_array(node, "mres_dsc_en", mres_dsc_en, mres_num);
switch (mres_num) {
case 3:
dsim->lcd_info.dt_lcd_mres.res_info[2].width = mres_w[2];
dsim->lcd_info.dt_lcd_mres.res_info[2].height = mres_h[2];
dsim->lcd_info.dt_lcd_mres.res_info[2].dsc_en = mres_dsc_en[2];
dsim->lcd_info.dt_lcd_mres.res_info[2].dsc_width = mres_dsc_w[2];
dsim->lcd_info.dt_lcd_mres.res_info[2].dsc_height = mres_dsc_h[2];
case 2:
dsim->lcd_info.dt_lcd_mres.res_info[1].width = mres_w[1];
dsim->lcd_info.dt_lcd_mres.res_info[1].height = mres_h[1];
dsim->lcd_info.dt_lcd_mres.res_info[1].dsc_en = mres_dsc_en[1];
dsim->lcd_info.dt_lcd_mres.res_info[1].dsc_width = mres_dsc_w[1];
dsim->lcd_info.dt_lcd_mres.res_info[1].dsc_height = mres_dsc_h[1];
case 1:
dsim->lcd_info.dt_lcd_mres.res_info[0].width = mres_w[0];
dsim->lcd_info.dt_lcd_mres.res_info[0].height = mres_h[0];
dsim->lcd_info.dt_lcd_mres.res_info[0].dsc_en = mres_dsc_en[0];
dsim->lcd_info.dt_lcd_mres.res_info[0].dsc_width = mres_dsc_w[0];
dsim->lcd_info.dt_lcd_mres.res_info[0].dsc_height = mres_dsc_h[0];
break;
default:
dsim->lcd_info.dt_lcd_mres.res_info[0].width = dsim->lcd_info.width;
dsim->lcd_info.dt_lcd_mres.res_info[0].height = dsim->lcd_info.height;
dsim_warn("check multi-resolution configurations at DT\n");
break;
}
dsim_info("[LCD multi(%d)-resolution info] 1st(%dx%d), 2nd(%dx%d), 3rd(%dx%d)\n",
mres_num, mres_w[0], mres_h[0],
mres_w[1], mres_h[1], mres_w[2], mres_h[2]);
} else {
dsim->lcd_info.dt_lcd_mres.res_info[0].width = dsim->lcd_info.width;
dsim->lcd_info.dt_lcd_mres.res_info[0].height = dsim->lcd_info.height;
}
if (dsim->lcd_info.mode == DECON_MIPI_COMMAND_MODE) {
of_property_read_u32_array(node, "cmd_underrun_lp_ref",
dsim->lcd_info.cmd_underrun_lp_ref,
dsim->lcd_info.dt_lcd_mres.mres_number);
for (k = 0; k < dsim->lcd_info.dt_lcd_mres.mres_number; k++)
dsim_info("mres[%d] cmd_underrun_lp_ref(%d)\n", k,
dsim->lcd_info.cmd_underrun_lp_ref[k]);
of_property_read_u32(node, "noncontinuous_clklane", &dsim->lcd_info.noncontinuous_clklane);
dsim_info("noncontinuous_clklane(%d)\n", dsim->lcd_info.noncontinuous_clklane);
} else {
of_property_read_u32(node, "vt_compensation",
&dsim->lcd_info.vt_compensation);
dsim_info("vt_compensation(%d)\n", dsim->lcd_info.vt_compensation);
of_property_read_u32(node, "clklane_onoff", &dsim->lcd_info.clklane_onoff);
dsim_info("clklane onoff(%d)\n", dsim->lcd_info.clklane_onoff);
}
if (IS_ENABLED(CONFIG_EXYNOS_WINDOW_UPDATE)) {
if (!of_property_read_u32_array(node, "update_min", res, 2)) {
dsim->lcd_info.update_min_w = res[0];
dsim->lcd_info.update_min_h = res[1];
dsim_info("update_min_w(%d) update_min_h(%d)\n",
dsim->lcd_info.update_min_w, dsim->lcd_info.update_min_h);
} else { /* If values are not difined on DT, Set to full size */
dsim->lcd_info.update_min_w = dsim->lcd_info.xres;
dsim->lcd_info.update_min_h = dsim->lcd_info.yres;
dsim_info("ERR: no update_min in DT!! update_min_w(%d) update_min_h(%d)\n",
dsim->lcd_info.update_min_w, dsim->lcd_info.update_min_h);
}
}
/* HDR info */
of_property_read_u32(node, "hdr_num", &hdr_num);
dsim->lcd_info.dt_lcd_hdr.hdr_num = hdr_num;
dsim_info("hdr_num(%d)\n", hdr_num);
if (hdr_num != 0) {
of_property_read_u32_array(node, "hdr_type", hdr_type, hdr_num);
for (k = 0; k < hdr_num; k++) {
dsim->lcd_info.dt_lcd_hdr.hdr_type[k] = hdr_type[k];
dsim_info("hdr_type[%d] = %d\n", k, hdr_type[k]);
}
of_property_read_u32(node, "hdr_max_luma", &hdr_mxl);
of_property_read_u32(node, "hdr_max_avg_luma", &hdr_mal);
of_property_read_u32(node, "hdr_min_luma", &hdr_mnl);
dsim->lcd_info.dt_lcd_hdr.hdr_max_luma = hdr_mxl;
dsim->lcd_info.dt_lcd_hdr.hdr_max_avg_luma = hdr_mal;
dsim->lcd_info.dt_lcd_hdr.hdr_min_luma = hdr_mnl;
dsim_info("hdr_max_luma(%d), hdr_max_avg_luma(%d), hdr_min_luma(%d)\n",
hdr_mxl, hdr_mal, hdr_mnl);
}
if(!of_property_read_u32(node, "udr_max_num", &udr_max_num)) {
dsim->continuous_underrun_max = udr_max_num;
dsim_info("udr_max_num(%d)\n", udr_max_num);
} else {
dsim->continuous_underrun_max = 0;
dsim_info("udr_max_num not found\n");
}
}
static int dsim_parse_dt(struct dsim_device *dsim, struct device *dev)
{
if (IS_ERR_OR_NULL(dev->of_node)) {
dsim_err("no device tree information\n");
return -EINVAL;
}
dsim->id = of_alias_get_id(dev->of_node, "dsim");
dsim_info("dsim(%d) probe start..\n", dsim->id);
dsim->phy = devm_phy_get(dev, "dsim_dphy");
if (IS_ERR_OR_NULL(dsim->phy)) {
dsim_err("failed to get phy\n");
return PTR_ERR(dsim->phy);
}
dsim->phy_ex = devm_phy_get(dev, "dsim_dphy_extra");
if (IS_ERR_OR_NULL(dsim->phy_ex)) {
dsim_err("failed to get extra phy. It's not mandatary.\n");
dsim->phy_ex = NULL;
}
dsim->dev = dev;
dsim_get_gpios(dsim);
dsim_get_regulator(dsim);
dsim_parse_lcd_info(dsim);
return 0;
}
static void dsim_register_panel(struct dsim_device *dsim)
{
#if IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_S6E3HA2K)
dsim->panel_ops = &s6e3ha2k_mipi_lcd_driver;
#elif IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_S6E3HF4)
dsim->panel_ops = &s6e3hf4_mipi_lcd_driver;
#elif IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_S6E3HA6)
dsim->panel_ops = &s6e3ha6_mipi_lcd_driver;
#elif IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_S6E3HA8)
dsim->panel_ops = &s6e3ha8_mipi_lcd_driver;
#elif IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_S6E3AA2)
dsim->panel_ops = &s6e3aa2_mipi_lcd_driver;
#elif IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_S6E3FA0)
dsim->panel_ops = &s6e3fa0_mipi_lcd_driver;
#elif IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_S6E3FA7)
dsim->panel_ops = &s6e3fa7_mipi_lcd_driver;
#elif IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_EA8076)
dsim->panel_ops = &ea8076_mipi_lcd_driver;
#elif IS_ENABLED(CONFIG_EXYNOS_DECON_LCD_EMUL_DISP)
dsim->panel_ops = &emul_disp_mipi_lcd_driver;
#else
dsim->panel_ops = mipi_lcd_driver;
#endif
}
static int dsim_get_data_lanes(struct dsim_device *dsim)
{
int i;
if (dsim->data_lane_cnt > MAX_DSIM_DATALANE_CNT) {
dsim_err("%d data lane couldn't be supported\n",
dsim->data_lane_cnt);
return -EINVAL;
}
dsim->data_lane = DSIM_LANE_CLOCK;
for (i = 1; i < dsim->data_lane_cnt + 1; ++i)
dsim->data_lane |= 1 << i;
dsim_info("%s: lanes(0x%x)\n", __func__, dsim->data_lane);
return 0;
}
static int dsim_init_resources(struct dsim_device *dsim, struct platform_device *pdev)
{
struct resource *res;
int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dsim_err("failed to get mem resource\n");
return -ENOENT;
}
dsim_info("res: start(0x%x), end(0x%x)\n", (u32)res->start, (u32)res->end);
dsim->res.regs = devm_ioremap_resource(dsim->dev, res);
if (!dsim->res.regs) {
dsim_err("failed to remap DSIM SFR region\n");
return -EINVAL;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res) {
dsim_info("no 2nd mem resource\n");
dsim->res.phy_regs = NULL;
} else {
dsim_info("dphy res: start(0x%x), end(0x%x)\n",
(u32)res->start, (u32)res->end);
dsim->res.phy_regs = devm_ioremap_resource(dsim->dev, res);
if (!dsim->res.phy_regs) {
dsim_err("failed to remap DSIM DPHY SFR region\n");
return -EINVAL;
}
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
if (!res) {
dsim_info("no extra dphy resource\n");
dsim->res.phy_regs_ex = NULL;
} else {
dsim_info("dphy_extra res: start(0x%x), end(0x%x)\n",
(u32)res->start, (u32)res->end);
dsim->res.phy_regs_ex = devm_ioremap_resource(dsim->dev, res);
if (!dsim->res.phy_regs_ex) {
dsim_err("failed to remap DSIM DPHY(EXTRA) SFR region\n");
return -EINVAL;
}
}
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dsim_err("failed to get irq resource\n");
return -ENOENT;
}
dsim->res.irq = res->start;
ret = devm_request_irq(dsim->dev, res->start,
dsim_irq_handler, 0, pdev->name, dsim);
if (ret) {
dsim_err("failed to install DSIM irq\n");
return -EINVAL;
}
disable_irq(dsim->res.irq);
dsim->res.ss_regs = dpu_get_sysreg_addr();
if (IS_ERR_OR_NULL(dsim->res.ss_regs)) {
dsim_err("failed to get sysreg addr\n");
return -EINVAL;
}
return 0;
}
static int dsim_probe(struct platform_device *pdev)
{
int ret = 0;
struct device *dev = &pdev->dev;
struct dsim_device *dsim = NULL;
dsim = devm_kzalloc(dev, sizeof(struct dsim_device), GFP_KERNEL);
if (!dsim) {
dsim_err("failed to allocate dsim device.\n");
ret = -ENOMEM;
goto err;
}
#if !defined(CONFIG_SUPPORT_LEGACY_ION)
dma_set_mask(dev, DMA_BIT_MASK(36));
#endif
ret = dsim_parse_dt(dsim, dev);
if (ret)
goto err_dt;
dsim_drvdata[dsim->id] = dsim;
ret = dsim_get_clocks(dsim);
if (ret)
goto err_dt;
spin_lock_init(&dsim->slock);
mutex_init(&dsim->cmd_lock);
init_completion(&dsim->ph_wr_comp);
init_completion(&dsim->rd_comp);
ret = dsim_init_resources(dsim, pdev);
if (ret)
goto err_dt;
dsim_init_subdev(dsim);
platform_set_drvdata(pdev, dsim);
dsim_register_panel(dsim);
setup_timer(&dsim->cmd_timer, dsim_cmd_fail_detector,
(unsigned long)dsim);
#if defined(CONFIG_CPU_IDLE)
dsim->idle_ip_index = exynos_get_idle_ip_index(dev_name(&pdev->dev));
dsim_info("dsim idle_ip_index[%d]\n", dsim->idle_ip_index);
if (dsim->idle_ip_index < 0)
dsim_warn("idle ip index is not provided for dsim\n");
exynos_update_ip_idle_status(dsim->idle_ip_index, 0);
#endif
if (dsim->lcd_info.mode == DECON_VIDEO_MODE)
dsim_acquire_fb_resource(dsim);
pm_runtime_enable(dev);
ret = dsim_get_data_lanes(dsim);
if (ret)
goto err_dt;
phy_init(dsim->phy);
if (dsim->phy_ex)
phy_init(dsim->phy_ex);
dsim->state = DSIM_STATE_INIT;
dsim_enable(dsim);
ret = iovmm_activate(dev);
if (ret) {
dsim_err("failed to activate iovmm\n");
goto err_dt;
}
iovmm_set_fault_handler(dev, dpu_sysmmu_fault_handler, NULL);
dsim_set_panel_power(dsim, 1);
/* TODO: If you want to enable DSIM BIST mode. you must turn on LCD here */
#if !defined(BRINGUP_DSIM_BIST)
call_panel_ops(dsim, probe, dsim);
#else
/* TODO: This is for dsim BIST mode in zebu emulator. only for test*/
call_panel_ops(dsim, displayon, dsim);
dsim_reg_set_bist(dsim->id, true);
#endif
/* for debug */
/* dsim_dump(dsim); */
dsim_clocks_info(dsim);
dsim_create_cmd_rw_sysfs(dsim);
#if defined(CONFIG_EXYNOS_READ_ESD_SOLUTION)
dsim->esd_recovering = false;
#if defined(READ_ESD_SOLUTION_TEST)
dsim_create_esd_test_sysfs(dsim);
#endif
#endif
#ifdef DPHY_LOOP
dsim_reg_set_dphy_loop_back_test(dsim->id);
#endif
dsim_info("dsim%d driver(%s mode) has been probed.\n", dsim->id,
dsim->lcd_info.mode == DECON_MIPI_COMMAND_MODE ? "cmd" : "video");
return 0;
err_dt:
kfree(dsim);
err:
return ret;
}
static int dsim_remove(struct platform_device *pdev)
{
struct dsim_device *dsim = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
mutex_destroy(&dsim->cmd_lock);
dsim_info("dsim%d driver removed\n", dsim->id);
return 0;
}
static void dsim_shutdown(struct platform_device *pdev)
{
#if 0
struct dsim_device *dsim = platform_get_drvdata(pdev);
DPU_EVENT_LOG(DPU_EVT_DSIM_SHUTDOWN, &dsim->sd, ktime_set(0, 0));
dsim_info("%s + state:%d\n", __func__, dsim->state);
dsim_disable(dsim);
#endif
dsim_info("%s -\n", __func__);
}
static int dsim_runtime_suspend(struct device *dev)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
DPU_EVENT_LOG(DPU_EVT_DSIM_SUSPEND, &dsim->sd, ktime_set(0, 0));
dsim_dbg("%s +\n", __func__);
clk_disable_unprepare(dsim->res.aclk);
dsim_dbg("%s -\n", __func__);
return 0;
}
static int dsim_runtime_resume(struct device *dev)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
DPU_EVENT_LOG(DPU_EVT_DSIM_RESUME, &dsim->sd, ktime_set(0, 0));
dsim_dbg("%s: +\n", __func__);
clk_prepare_enable(dsim->res.aclk);
dsim_dbg("%s -\n", __func__);
return 0;
}
static const struct of_device_id dsim_of_match[] = {
{ .compatible = "samsung,exynos9-dsim" },
{},
};
MODULE_DEVICE_TABLE(of, dsim_of_match);
static const struct dev_pm_ops dsim_pm_ops = {
.runtime_suspend = dsim_runtime_suspend,
.runtime_resume = dsim_runtime_resume,
};
static struct platform_driver dsim_driver __refdata = {
.probe = dsim_probe,
.remove = dsim_remove,
.shutdown = dsim_shutdown,
.driver = {
.name = DSIM_MODULE_NAME,
.owner = THIS_MODULE,
.pm = &dsim_pm_ops,
.of_match_table = of_match_ptr(dsim_of_match),
.suppress_bind_attrs = true,
}
};
static int __init dsim_init(void)
{
int ret = platform_driver_register(&dsim_driver);
if (ret)
pr_err("dsim driver register failed\n");
return ret;
}
late_initcall(dsim_init);
static void __exit dsim_exit(void)
{
platform_driver_unregister(&dsim_driver);
}
module_exit(dsim_exit);
static int rmem_device_init(struct reserved_mem *rmem, struct device *dev)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
dsim->phys_addr = rmem->base;
dsim->phys_size = rmem->size;
return 0;
}
/* of_reserved_mem_device_release(dev) when reserved memory is no logner required */
static void rmem_device_release(struct reserved_mem *rmem, struct device *dev)
{
struct page *first = phys_to_page(PAGE_ALIGN(rmem->base));
struct page *last = phys_to_page((rmem->base + rmem->size) & PAGE_MASK);
struct page *page;
pr_info("%s: base=%pa, size=%pa, first=%pa, last=%pa\n",
__func__, &rmem->base, &rmem->size, first, last);
for (page = first; page != last; page++) {
__ClearPageReserved(page);
set_page_count(page, 1);
__free_pages(page, 0);
adjust_managed_page_count(page, 1);
}
}
static const struct reserved_mem_ops rmem_ops = {
.device_init = rmem_device_init,
.device_release = rmem_device_release,
};
static int __init fb_rmem_setup(struct reserved_mem *rmem)
{
pr_info("%s: base=%pa, size=%pa\n", __func__, &rmem->base, &rmem->size);
rmem->ops = &rmem_ops;
return 0;
}
RESERVEDMEM_OF_DECLARE(fb_rmem, "exynos,fb_rmem", fb_rmem_setup);
MODULE_AUTHOR("Yeongran Shin <yr613.shin@samsung.com>");
MODULE_DESCRIPTION("Samusung EXYNOS DSIM driver");
MODULE_LICENSE("GPL");