blob: 6a1e43501df55be7cedeafec49f2a0c4da9d7165 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/* exynos_drm_dsi.h
*
* Copyright (c) 2018 Samsung Electronics Co., Ltd.
* Authors:
* Donghwa Lee <dh09.lee@samsung.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <asm/unaligned.h>
#include <drm/drm_of.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_panel.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_modes.h>
#include <drm/drm_vblank.h>
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/irq.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_graph.h>
#include <linux/phy/phy.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/component.h>
#include <linux/iommu.h>
#include <video/mipi_display.h>
#if defined(CONFIG_CPU_IDLE)
#include <soc/google/exynos-cpupm.h>
#endif
#if IS_ENABLED(CONFIG_ARM_EXYNOS_DEVFREQ)
#include <soc/google/exynos-devfreq.h>
#if defined(CONFIG_SOC_GS101)
#include <dt-bindings/soc/google/gs101-devfreq.h>
#elif defined(CONFIG_SOC_GS201)
#include <dt-bindings/soc/google/gs201-devfreq.h>
#endif
#endif
#include <regs-dsim.h>
#include <trace/dpu_trace.h>
#include "exynos_drm_connector.h"
#include "exynos_drm_crtc.h"
#include "exynos_drm_decon.h"
#include "exynos_drm_dsim.h"
struct dsim_device *dsim_drvdata[MAX_DSI_CNT];
#define PANEL_DRV_LEN 64
#define RETRY_READ_FIFO_MAX 10
static char panel_name[PANEL_DRV_LEN];
module_param_string(panel_name, panel_name, sizeof(panel_name), 0644);
MODULE_PARM_DESC(panel_name, "preferred panel name");
static char sec_panel_name[PANEL_DRV_LEN];
module_param_string(sec_panel_name, sec_panel_name, sizeof(sec_panel_name), 0644);
MODULE_PARM_DESC(sec_panel_name, "secondary panel name");
enum panel_priority_index {
PANEL_PRIORITY_PRI_IDX = 0,
PANEL_PRIORITY_SEC_IDX = 1,
};
static u8 panel_usage = {0};
#define dsim_info(dsim, fmt, ...) \
pr_info("%s[%d]: "fmt, dsim->dev->driver->name, dsim->id, ##__VA_ARGS__)
#define dsim_warn(dsim, fmt, ...) \
pr_warn("%s[%d]: "fmt, dsim->dev->driver->name, dsim->id, ##__VA_ARGS__)
#define dsim_err(dsim, fmt, ...) \
pr_err("%s[%d]: "fmt, dsim->dev->driver->name, dsim->id, ##__VA_ARGS__)
#define dsim_debug(dsim, fmt, ...) \
pr_debug("%s[%d]: "fmt, dsim->dev->driver->name, dsim->id, ##__VA_ARGS__)
#define host_to_dsi(host) container_of(host, struct dsim_device, dsi_host)
#define DSIM_ESCAPE_CLK_20MHZ 20
//#define DSIM_BIST
#define DEFAULT_TE_IDLE_US 1000
#define DEFAULT_TE_VARIATION 1
static const struct of_device_id dsim_of_match[] = {
{ .compatible = "samsung,exynos-dsim",
.data = NULL },
{ }
};
MODULE_DEVICE_TABLE(of, dsim_of_match);
static int dsim_calc_underrun(const struct dsim_device *dsim, uint32_t hs_clock_mhz,
uint32_t *underrun);
static struct drm_crtc *drm_encoder_get_new_crtc(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
struct drm_connector *connector;
const struct drm_connector_state *conn_state;
connector = drm_atomic_get_new_connector_for_encoder(state, encoder);
if (!connector)
return NULL;
conn_state = drm_atomic_get_new_connector_state(state, connector);
if (!conn_state)
return NULL;
return conn_state->crtc;
}
static struct drm_crtc *drm_encoder_get_old_crtc(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
struct drm_connector *connector;
const struct drm_connector_state *conn_state;
connector = drm_atomic_get_old_connector_for_encoder(state, encoder);
if (!connector)
return NULL;
conn_state = drm_atomic_get_old_connector_state(state, connector);
if (!conn_state)
return NULL;
return conn_state->crtc;
}
static void dsim_dump(struct dsim_device *dsim)
{
struct dsim_regs regs;
dsim_info(dsim, "=== DSIM SFR DUMP ===\n");
if (dsim->state != DSIM_STATE_HSCLKEN)
return;
regs.regs = dsim->res.regs;
regs.ss_regs = dsim->res.ss_reg_base;
regs.phy_regs = dsim->res.phy_regs;
regs.phy_regs_ex = dsim->res.phy_regs_ex;
__dsim_dump(dsim->id, &regs);
}
static int dsim_phy_power_on(struct dsim_device *dsim)
{
int ret;
dsim_debug(dsim, "%s +\n", __func__);
if (IS_ENABLED(CONFIG_BOARD_EMULATOR))
return 0;
ret = phy_power_on(dsim->res.phy);
if (ret) {
dsim_err(dsim, "failed to enable dphy(%d)\n", ret);
return ret;
}
if (dsim->res.phy_ex) {
ret = phy_power_on(dsim->res.phy_ex);
if (ret) {
dsim_err(dsim, "failed to enable ext dphy(%d)\n", ret);
return ret;
}
}
dsim_debug(dsim, "%s -\n", __func__);
return 0;
}
static int dsim_phy_power_off(struct dsim_device *dsim)
{
int ret;
dsim_debug(dsim, "%s +\n", __func__);
if (IS_ENABLED(CONFIG_BOARD_EMULATOR))
return 0;
ret = phy_power_off(dsim->res.phy);
if (ret) {
dsim_err(dsim, "failed to disable dphy(%d)\n", ret);
return ret;
}
if (dsim->res.phy_ex) {
ret = phy_power_off(dsim->res.phy_ex);
if (ret) {
dsim_err(dsim, "failed to disable ext dphy(%d)\n", ret);
return ret;
}
}
dsim_debug(dsim, "%s -\n", __func__);
return 0;
}
static void _dsim_exit_ulps_locked(struct dsim_device *dsim)
{
const struct decon_device *decon = dsim_get_decon(dsim);
WARN_ON(!mutex_is_locked(&dsim->state_lock));
if (dsim->state != DSIM_STATE_ULPS)
return;
dsim_debug(dsim, "+\n");
DPU_ATRACE_BEGIN(__func__);
#if defined(CONFIG_CPU_IDLE)
exynos_update_ip_idle_status(dsim->idle_ip_index, 0);
#endif
dsim_phy_power_on(dsim);
dsim_reg_init(dsim->id, &dsim->config, &dsim->clk_param, false);
dsim_reg_exit_ulps_and_start(dsim->id, 0, 0x1F);
dsim->state = DSIM_STATE_HSCLKEN;
enable_irq(dsim->irq);
dsim_debug(dsim, "-\n");
if (decon)
DPU_EVENT_LOG(DPU_EVT_DSIM_ULPS_EXIT, decon->id, dsim);
DPU_ATRACE_END(__func__);
}
static void dsim_set_te_pinctrl(struct dsim_device *dsim, bool en)
{
int ret;
if (!dsim->hw_trigger || !dsim->te_on || !dsim->te_off)
return;
ret = pinctrl_select_state(dsim->pinctrl, en ? dsim->te_on : dsim->te_off);
if (ret)
dsim_err(dsim, "failed to control decon TE(%d)\n", en);
}
static void _dsim_enable(struct dsim_device *dsim)
{
const struct decon_device *decon = dsim_get_decon(dsim);
struct dsim_device *sec_dsi;
pm_runtime_get_sync(dsim->dev);
mutex_lock(&dsim->state_lock);
if (dsim->state == DSIM_STATE_HSCLKEN) {
mutex_unlock(&dsim->state_lock);
pm_runtime_put_sync(dsim->dev);
dsim_info(dsim, "already enabled(%d)\n", dsim->state);
return;
}
dsim_debug(dsim, "%s +\n", __func__);
#if defined(CONFIG_CPU_IDLE)
exynos_update_ip_idle_status(dsim->idle_ip_index, 0);
#endif
dsim_phy_power_on(dsim);
dsim_reg_init(dsim->id, &dsim->config, &dsim->clk_param, true);
dsim_reg_start(dsim->id);
/* TODO: dsi start: enable irq, sfr configuration */
dsim->state = DSIM_STATE_HSCLKEN;
enable_irq(dsim->irq);
mutex_unlock(&dsim->state_lock);
#if defined(DSIM_BIST)
dsim_reg_set_bist(dsim->id, true, DSIM_GRAY_GRADATION);
dsim_dump(dsim);
#endif
if (decon)
DPU_EVENT_LOG(DPU_EVT_DSIM_ENABLED, decon->id, dsim);
dsim_debug(dsim, "%s -\n", __func__);
if (dsim->dual_dsi == DSIM_DUAL_DSI_MAIN) {
sec_dsi = exynos_get_dual_dsi(DSIM_DUAL_DSI_SEC);
if (sec_dsi)
_dsim_enable(sec_dsi);
else
dsim_err(dsim, "could not get secondary dsi\n");
}
}
static void dsim_encoder_enable(struct drm_encoder *encoder, struct drm_atomic_state *state)
{
struct dsim_device *dsim = encoder_to_dsim(encoder);
struct drm_crtc *crtc = drm_encoder_get_new_crtc(encoder, state);
struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
const struct decon_device *decon = to_exynos_crtc(crtc)->ctx;
struct device *supplier = decon->dev;
dsim_debug(dsim, "current state: %d\n", dsim->state);
if (dsim->dev_link && (dsim->dev_link->supplier != supplier)) {
device_link_del(dsim->dev_link);
dsim->dev_link = NULL;
}
if (!dsim->dev_link) {
const u32 dl_flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER;
dsim->dev_link = device_link_add(dsim->dev, supplier, dl_flags);
if (WARN(!dsim->dev_link, "unable to create dev link between decon/dsim\n"))
return;
}
if (dsim->state == DSIM_STATE_SUSPEND) {
_dsim_enable(dsim);
dsim_set_te_pinctrl(dsim, 1);
} else if (dsim->state == DSIM_STATE_BYPASS) {
pm_runtime_set_suspended(dsim->dev);
dsim->state = DSIM_STATE_SUSPEND;
_dsim_enable(dsim);
} else if (old_crtc_state->self_refresh_active) {
/* get extra ref count dropped when going into self refresh */
pm_runtime_get_sync(dsim->dev);
} else {
WARN(1, "unknown dsim state (%d)\n", dsim->state);
}
}
static void _dsim_enter_ulps_locked(struct dsim_device *dsim)
{
const struct decon_device *decon = dsim_get_decon(dsim);
WARN_ON(!mutex_is_locked(&dsim->state_lock));
DPU_ATRACE_BEGIN(__func__);
dsim_debug(dsim, "+\n");
/* Wait for current read & write CMDs. */
mutex_lock(&dsim->cmd_lock);
if (WARN_ON(dsim_reg_has_pend_cmd(dsim->id)))
dsim_dump(dsim);
dsim->state = DSIM_STATE_ULPS;
mutex_unlock(&dsim->cmd_lock);
disable_irq(dsim->irq);
dsim_reg_stop_and_enter_ulps(dsim->id, 0, 0x1F);
dsim_phy_power_off(dsim);
#if defined(CONFIG_CPU_IDLE)
exynos_update_ip_idle_status(dsim->idle_ip_index, 1);
#endif
if (decon)
DPU_EVENT_LOG(DPU_EVT_DSIM_ULPS_ENTER, decon->id, dsim);
dsim_debug(dsim, "-\n");
DPU_ATRACE_END(__func__);
}
static void _dsim_disable(struct dsim_device *dsim)
{
const struct decon_device *decon = dsim_get_decon(dsim);
struct dsim_device *sec_dsi;
if (dsim->dual_dsi == DSIM_DUAL_DSI_MAIN) {
sec_dsi = exynos_get_dual_dsi(DSIM_DUAL_DSI_SEC);
if (sec_dsi)
_dsim_disable(sec_dsi);
else
dsim_err(dsim, "could not get secondary dsi\n");
}
dsim_debug(dsim, "+\n");
mutex_lock(&dsim->state_lock);
if (dsim->state == DSIM_STATE_SUSPEND) {
mutex_unlock(&dsim->state_lock);
dsim_info(dsim, "already disabled(%d)\n", dsim->state);
return;
}
/* Wait for current read & write CMDs. */
mutex_lock(&dsim->cmd_lock);
/* TODO: 0x1F will be changed */
dsim_reg_stop(dsim->id, 0x1F);
disable_irq(dsim->irq);
dsim->state = DSIM_STATE_SUSPEND;
WARN_ON(dsim_reg_has_pend_cmd(dsim->id));
mutex_unlock(&dsim->cmd_lock);
mutex_unlock(&dsim->state_lock);
dsim_phy_power_off(dsim);
#if defined(CONFIG_CPU_IDLE)
exynos_update_ip_idle_status(dsim->idle_ip_index, 1);
#endif
pm_runtime_put_sync(dsim->dev);
if (decon)
DPU_EVENT_LOG(DPU_EVT_DSIM_DISABLED, decon->id, dsim);
dsim_debug(dsim, "-\n");
}
static void dsim_encoder_disable(struct drm_encoder *encoder, struct drm_atomic_state *state)
{
struct dsim_device *dsim = encoder_to_dsim(encoder);
struct drm_crtc *crtc = drm_encoder_get_old_crtc(encoder, state);
bool self_refresh_active = false;
bool was_in_self_refresh = false;
if (crtc) {
const struct drm_crtc_state *old_crtc_state =
drm_atomic_get_old_crtc_state(state, crtc);
const struct drm_crtc_state *new_crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
if (old_crtc_state && old_crtc_state->self_refresh_active)
was_in_self_refresh = true;
if (new_crtc_state && new_crtc_state->self_refresh_active)
self_refresh_active = true;
}
dsim_debug(dsim, "state: %d self_refresh: %d->%d\n", dsim->state,
was_in_self_refresh, self_refresh_active);
DPU_ATRACE_BEGIN(__func__);
if (self_refresh_active) {
const struct exynos_drm_crtc_state *exynos_crtc_state =
to_exynos_crtc_state(crtc->state);
if (was_in_self_refresh) {
WARN(1, "already in self refresh state. state:%d\n", dsim->state);
} else if (exynos_crtc_state->bypass) {
/* in bypass mode dsim should get fully disabled */
_dsim_disable(dsim);
dsim->state = DSIM_STATE_BYPASS;
} else {
/* during regular self refresh just need to go into runtime idle */
pm_runtime_put_sync(dsim->dev);
}
} else {
if (was_in_self_refresh) {
/* get extra ref count dropped when going into self refresh */
pm_runtime_get_sync(dsim->dev);
dsim_debug(dsim, "disable right after self refresh. state:%d\n",
dsim->state);
}
_dsim_disable(dsim);
dsim_set_te_pinctrl(dsim, 0);
}
DPU_ATRACE_END(__func__);
}
static void dsim_modes_release(struct dsim_pll_params *pll_params)
{
if (pll_params->params) {
unsigned int i;
for (i = 0; i < pll_params->num_modes; i++)
kfree(pll_params->params[i]);
kfree(pll_params->params);
}
kfree(pll_params->features);
kfree(pll_params);
}
static struct dsim_pll_param *
dsim_get_clock_mode(const struct dsim_device *dsim,
const struct drm_display_mode *mode)
{
int i;
const struct dsim_pll_params *pll_params = dsim->pll_params;
const size_t mlen = strnlen(mode->name, DRM_DISPLAY_MODE_LEN);
struct dsim_pll_param *ret = NULL;
size_t plen;
for (i = 0; i < pll_params->num_modes; i++) {
struct dsim_pll_param *p = pll_params->params[i];
plen = strnlen(p->name, DRM_DISPLAY_MODE_LEN);
if (!strncmp(mode->name, p->name, plen)) {
ret = p;
/*
* if it's not exact match, continue looking for exact
* match, use this as a fallback
*/
if (plen == mlen)
break;
}
}
return ret;
}
static void dsim_update_clock_config(struct dsim_device *dsim,
const struct dsim_pll_param *p)
{
dsim->config.dphy_pms.p = p->p;
dsim->config.dphy_pms.m = p->m;
dsim->config.dphy_pms.s = p->s;
dsim->config.dphy_pms.k = p->k;
dsim->config.dphy_pms.mfr = p->mfr;
dsim->config.dphy_pms.mrr = p->mrr;
dsim->config.dphy_pms.sel_pf = p->sel_pf;
dsim->config.dphy_pms.icp = p->icp;
dsim->config.dphy_pms.afc_enb = p->afc_enb;
dsim->config.dphy_pms.extafc = p->extafc;
dsim->config.dphy_pms.feed_en = p->feed_en;
dsim->config.dphy_pms.fsel = p->fsel;
dsim->config.dphy_pms.fout_mask = p->fout_mask;
dsim->config.dphy_pms.rsel = p->rsel;
dsim->config.dphy_pms.dither_en = p->dither_en;
dsim->clk_param.hs_clk = p->pll_freq;
dsim->clk_param.esc_clk = p->esc_freq;
dsim_debug(dsim, "found proper pll parameter\n");
dsim_debug(dsim, "\t%s(p:0x%x,m:0x%x,s:0x%x,k:0x%x)\n", p->name,
dsim->config.dphy_pms.p, dsim->config.dphy_pms.m,
dsim->config.dphy_pms.s, dsim->config.dphy_pms.k);
dsim_debug(dsim, "\t%s(hs:%d,esc:%d)\n", p->name, dsim->clk_param.hs_clk,
dsim->clk_param.esc_clk);
if (p->cmd_underrun_cnt) {
dsim->config.cmd_underrun_cnt[0] = p->cmd_underrun_cnt;
} else {
dsim_warn(dsim, "cmd_underrun_cnt is not set correctly\n");
WARN_ON(1);
}
dsim_debug(dsim, "\tunderrun_lp_ref 0x%x\n", dsim->config.cmd_underrun_cnt[0]);
}
static int dsim_set_clock_mode(struct dsim_device *dsim,
const struct drm_display_mode *mode)
{
struct dsim_pll_param *p = dsim_get_clock_mode(dsim, mode);
uint32_t underrun_cnt;
if (!p)
return -ENOENT;
if (!dsim_calc_underrun(dsim, p->pll_freq, &underrun_cnt))
p->cmd_underrun_cnt = underrun_cnt;
dsim_update_clock_config(dsim, p);
dsim->current_pll_param = p;
return 0;
}
static int dsim_of_parse_modes(struct device_node *entry,
struct dsim_pll_param *pll_param)
{
u32 res[14];
int cnt;
memset(pll_param, 0, sizeof(*pll_param));
of_property_read_string(entry, "mode-name",
(const char **)&pll_param->name);
cnt = of_property_count_u32_elems(entry, "pmsk");
if (cnt != 4 && cnt != 14) {
pr_err("mode %s has wrong pmsk elements number %d\n",
pll_param->name, cnt);
return -EINVAL;
}
/* TODO: how dsi dither handle ? */
of_property_read_u32_array(entry, "pmsk", res, cnt);
pll_param->dither_en = false;
pll_param->p = res[0];
pll_param->m = res[1];
pll_param->s = res[2];
pll_param->k = res[3];
if (cnt == 14) {
pll_param->mfr = res[4];
pll_param->mrr = res[5];
pll_param->sel_pf = res[6];
pll_param->icp = res[7];
pll_param->afc_enb = res[8];
pll_param->extafc = res[9];
pll_param->feed_en = res[10];
pll_param->fsel = res[11];
pll_param->fout_mask = res[12];
pll_param->rsel = res[13];
pll_param->dither_en = true;
}
of_property_read_u32(entry, "hs-clk", &pll_param->pll_freq);
of_property_read_u32(entry, "esc-clk", &pll_param->esc_freq);
of_property_read_u32(entry, "cmd_underrun_cnt",
&pll_param->cmd_underrun_cnt);
return 0;
}
static struct dsim_pll_features *dsim_of_get_pll_features(
struct dsim_device *dsim, struct device_node *np)
{
u64 range64[2];
u32 range32[2];
struct dsim_pll_features *pll_features;
pll_features = kzalloc(sizeof(*pll_features), GFP_KERNEL);
if (!pll_features)
return NULL;
if (of_property_read_u64(np, "pll-input", &pll_features->finput) < 0) {
dsim_err(dsim, "%s failed to get pll-input\n", __func__);
goto read_node_fail;
}
if (of_property_read_u64(np, "pll-optimum",
&pll_features->foptimum) < 0) {
dsim_err(dsim, "%s failed to get pll-optimum\n", __func__);
goto read_node_fail;
}
if (of_property_read_u64_array(np, "pll-out-range", range64, 2) < 0) {
dsim_err(dsim, "%s failed to get pll-out-range\n", __func__);
goto read_node_fail;
}
pll_features->fout_min = range64[0];
pll_features->fout_max = range64[1];
if (of_property_read_u64_array(np, "pll-vco-range", range64, 2) < 0) {
dsim_err(dsim, "%s failed to get pll-vco-range\n", __func__);
goto read_node_fail;
}
pll_features->fvco_min = range64[0];
pll_features->fvco_max = range64[1];
if (of_property_read_u32_array(np, "p-range", range32, 2) < 0) {
dsim_err(dsim, "%s failed to get p-range\n", __func__);
goto read_node_fail;
}
pll_features->p_min = range32[0];
pll_features->p_max = range32[1];
if (of_property_read_u32_array(np, "m-range", range32, 2) < 0) {
dsim_err(dsim, "%s failed to get m-range\n", __func__);
goto read_node_fail;
}
pll_features->m_min = range32[0];
pll_features->m_max = range32[1];
if (of_property_read_u32_array(np, "s-range", range32, 2) < 0) {
dsim_err(dsim, "%s failed to get s-range\n", __func__);
goto read_node_fail;
}
pll_features->s_min = range32[0];
pll_features->s_max = range32[1];
if (of_property_read_u32(np, "k-bits", &pll_features->k_bits) < 0) {
dsim_err(dsim, "%s failed to get k-bits\n", __func__);
goto read_node_fail;
}
dsim_debug(dsim, "pll features: input %llu, optimum%llu\n",
pll_features->finput, pll_features->foptimum);
dsim_debug(dsim, "pll features: output(%llu, %llu)\n",
pll_features->fout_min, pll_features->fout_max);
dsim_debug(dsim, "pll features: vco (%llu, %llu)\n",
pll_features->fvco_min, pll_features->fout_max);
dsim_debug(dsim, "pll limits: p(%u, %u), m(%u, %u), s(%u, %u), k(%u)\n",
pll_features->p_min, pll_features->p_max,
pll_features->m_min, pll_features->m_max,
pll_features->s_min, pll_features->s_max,
pll_features->k_bits);
return pll_features;
read_node_fail:
kfree(pll_features);
return NULL;
}
static struct dsim_pll_params *dsim_of_get_clock_mode(struct dsim_device *dsim)
{
struct device *dev = dsim->dev;
struct device_node *np, *mode_np, *entry;
struct dsim_pll_params *pll_params;
np = of_parse_phandle(dev->of_node, "dsim_mode", 0);
if (!np) {
dsim_err(dsim, "could not get dsi modes\n");
return NULL;
}
mode_np = of_get_child_by_name(np, "dsim-modes");
if (!mode_np) {
dsim_err(dsim, "%pOF: could not find dsim-modes node\n", np);
goto getnode_fail;
}
pll_params = kzalloc(sizeof(*pll_params), GFP_KERNEL);
if (!pll_params)
goto getmode_fail;
entry = of_get_next_child(mode_np, NULL);
if (!entry) {
dsim_err(dsim, "could not find child node of dsim-modes");
goto getchild_fail;
}
pll_params->num_modes = of_get_child_count(mode_np);
if (pll_params->num_modes == 0) {
dsim_err(dsim, "%pOF: no modes specified\n", np);
goto getchild_fail;
}
pll_params->params = kzalloc(sizeof(struct dsim_pll_param *) *
pll_params->num_modes, GFP_KERNEL);
if (!pll_params->params)
goto getchild_fail;
pll_params->num_modes = 0;
for_each_child_of_node(mode_np, entry) {
struct dsim_pll_param *pll_param;
pll_param = kzalloc(sizeof(*pll_param), GFP_KERNEL);
if (!pll_param)
goto getpll_fail;
if (dsim_of_parse_modes(entry, pll_param) < 0) {
kfree(pll_param);
continue;
}
pll_params->params[pll_params->num_modes] = pll_param;
pll_params->num_modes++;
}
pll_params->features = dsim_of_get_pll_features(dsim, np);
of_node_put(np);
of_node_put(mode_np);
of_node_put(entry);
return pll_params;
getpll_fail:
of_node_put(entry);
getchild_fail:
dsim_modes_release(pll_params);
getmode_fail:
of_node_put(mode_np);
getnode_fail:
of_node_put(np);
return NULL;
}
static void dsim_restart(struct dsim_device *dsim)
{
mutex_lock(&dsim->cmd_lock);
dsim_reg_stop(dsim->id, 0x1F);
disable_irq(dsim->irq);
dsim_reg_init(dsim->id, &dsim->config, &dsim->clk_param, true);
dsim_reg_start(dsim->id);
enable_irq(dsim->irq);
mutex_unlock(&dsim->cmd_lock);
}
#ifdef CONFIG_DEBUG_FS
static int dsim_of_parse_diag(struct device_node *np, struct dsim_dphy_diag *diag)
{
int count;
u8 bit_range[2];
const char *reg_base = NULL;
of_property_read_string(np, "reg-base", &reg_base);
if (!strcmp(reg_base, "dphy")) {
diag->reg_base = REGS_DSIM_PHY;
} else if (!strcmp(reg_base, "dphy-extra")) {
diag->reg_base = REGS_DSIM_PHY_BIAS;
} else {
pr_err("%s: invalid reg-base: %s\n", __func__, reg_base);
return -EINVAL;
}
of_property_read_string(np, "diag-name", &diag->name);
if (!diag->name || !diag->name[0]) {
pr_err("%s: empty diag-name\n", __func__);
return -EINVAL;
}
of_property_read_string(np, "desc", &diag->desc);
of_property_read_string(np, "help", &diag->help);
count = of_property_count_u16_elems(np, "reg-offset");
if (count <= 0 || count > MAX_DIAG_REG_NUM) {
pr_err("%s: wrong number of reg-offset: %d\n", __func__, count);
return -ERANGE;
}
if (of_property_read_u16_array(np, "reg-offset", diag->reg_offset, count) < 0) {
pr_err("%s: failed to read reg-offset\n", __func__);
return -EINVAL;
}
diag->num_reg = count;
if (of_property_read_u8_array(np, "bit-range", bit_range, 2) < 0) {
pr_err("%s: failed to read bit-range\n", __func__);
return -EINVAL;
}
if (bit_range[0] >= 32 || bit_range[1] >= 32) {
pr_err("%s: invalid bit range %d, %d\n", __func__, bit_range[0], bit_range[1]);
return -EINVAL;
}
if (bit_range[0] < bit_range[1]) {
diag->bit_start = bit_range[0];
diag->bit_end = bit_range[1];
} else {
diag->bit_start = bit_range[1];
diag->bit_end = bit_range[0];
}
diag->read_only = of_property_read_bool(np, "read_only");
return 0;
}
static void dsim_of_get_pll_diags(struct dsim_device *dsim)
{
struct device_node *np, *entry;
struct device *dev = dsim->dev;
uint32_t index = 0;
np = of_parse_phandle(dev->of_node, "dphy_diag", 0);
dsim->config.num_dphy_diags = of_get_child_count(np);
if (dsim->config.num_dphy_diags == 0) {
goto nochild;
}
dsim->config.dphy_diags = devm_kzalloc(dsim->dev, sizeof(struct dsim_dphy_diag) *
dsim->config.num_dphy_diags, GFP_KERNEL);
if (!dsim->config.dphy_diags) {
dsim_warn(dsim, "%s: no memory for %u diag items\n",
__func__, dsim->config.num_dphy_diags);
dsim->config.num_dphy_diags = 0;
goto nochild;
}
for_each_child_of_node(np, entry) {
if (index >= dsim->config.num_dphy_diags) {
dsim_warn(dsim, "%s: diag parsing error with unexpected index %u\n",
__func__, index);
goto get_diag_fail;
}
if (dsim_of_parse_diag(entry, &dsim->config.dphy_diags[index]) < 0) {
dsim_warn(dsim, "%s: diag parsing error for item %u\n",
__func__, index);
goto get_diag_fail;
}
++index;
}
return;
get_diag_fail:
dsim->config.num_dphy_diags = 0;
devm_kfree(dsim->dev, dsim->config.dphy_diags);
dsim->config.dphy_diags = NULL;
nochild:
return;
}
int dsim_dphy_diag_get_reg(struct dsim_device *dsim,
struct dsim_dphy_diag *diag, uint32_t *vals)
{
int ret;
uint32_t ix, mask, val;
ret = dsim_dphy_diag_mask_from_range(diag->bit_start, diag->bit_end,
&mask);
if (ret < 0)
return ret;
mutex_lock(&dsim->state_lock);
if (dsim->state != DSIM_STATE_HSCLKEN) {
ret = -ENODEV;
goto out;
}
for (ix = 0; ix < diag->num_reg; ++ix) {
if (diag->reg_base == REGS_DSIM_PHY_BIAS)
val = diag_dsim_dphy_extra_reg_read_mask(
dsim->id, diag->reg_offset[ix], mask);
else if (diag->reg_base == REGS_DSIM_PHY)
val = diag_dsim_dphy_reg_read_mask(
dsim->id, diag->reg_offset[ix], mask);
else {
pr_err("%s: invalid reg_base %u\n", __func__,
diag->reg_base);
goto out;
}
vals[ix] = val >> diag->bit_start;
}
out:
mutex_unlock(&dsim->state_lock);
return ret;
}
int dsim_dphy_diag_set_reg(struct dsim_device *dsim,
struct dsim_dphy_diag *diag, uint32_t val)
{
int ret;
u32 mask;
ret = dsim_dphy_diag_mask_from_range(diag->bit_start, diag->bit_end,
&mask);
if (ret < 0)
return ret;
diag->override = true;
diag->user_value = (val << diag->bit_start) & mask;
mutex_lock(&dsim->state_lock);
if (dsim->state != DSIM_STATE_HSCLKEN)
goto out;
/* restart dsim to apply new config */
dsim_restart(dsim);
out:
mutex_unlock(&dsim->state_lock);
return ret;
}
#else
static void dsim_of_get_pll_diags(struct dsim_device * /* dsim */)
{
}
#endif
static void dsim_update_config_for_mode(struct dsim_reg_config *config,
const struct drm_display_mode *mode,
const struct exynos_display_mode *exynos_mode)
{
struct dpu_panel_timing *p_timing = &config->p_timing;
struct videomode vm;
drm_display_mode_to_videomode(mode, &vm);
p_timing->vactive = vm.vactive;
p_timing->vfp = vm.vfront_porch;
p_timing->vbp = vm.vback_porch;
p_timing->vsa = vm.vsync_len;
p_timing->hactive = vm.hactive;
if (config->dual_dsi)
p_timing->hactive /= 2;
p_timing->hfp = vm.hfront_porch;
p_timing->hbp = vm.hback_porch;
p_timing->hsa = vm.hsync_len;
p_timing->vrefresh = drm_mode_vrefresh(mode);
if (exynos_mode->underrun_param) {
p_timing->te_idle_us = exynos_mode->underrun_param->te_idle_us;
p_timing->te_var = exynos_mode->underrun_param->te_var;
} else {
p_timing->te_idle_us = DEFAULT_TE_IDLE_US;
p_timing->te_var = DEFAULT_TE_VARIATION;
pr_debug("%s: underrun_param for mode " DRM_MODE_FMT
" not specified", __func__, DRM_MODE_ARG(mode));
}
/* TODO: This hard coded information will be defined in device tree */
config->mres_mode = 0;
config->mode = (exynos_mode->mode_flags & MIPI_DSI_MODE_VIDEO) ?
DSIM_VIDEO_MODE : DSIM_COMMAND_MODE;
config->bpp = exynos_mode->bpc * 3;
config->dsc.enabled = exynos_mode->dsc.enabled;
if (config->dsc.enabled) {
config->dsc.dsc_count = exynos_mode->dsc.dsc_count;
config->dsc.slice_count = exynos_mode->dsc.slice_count;
config->dsc.slice_height = exynos_mode->dsc.slice_height;
config->dsc.slice_width = DIV_ROUND_UP(
config->p_timing.hactive,
config->dsc.slice_count);
}
}
static void dsim_set_display_mode(struct dsim_device *dsim,
const struct drm_display_mode *mode,
const struct exynos_display_mode *exynos_mode)
{
struct dsim_device *sec_dsi;
if (!dsim->dsi_device)
return;
mutex_lock(&dsim->state_lock);
dsim->config.data_lane_cnt = dsim->dsi_device->lanes;
dsim->hw_trigger = !exynos_mode->sw_trigger;
dsim->config.dual_dsi = dsim->dual_dsi;
dsim_update_config_for_mode(&dsim->config, mode, exynos_mode);
dsim_set_clock_mode(dsim, mode);
if (dsim->state == DSIM_STATE_HSCLKEN)
dsim_reg_set_vrr_config(dsim->id, &dsim->config, &dsim->clk_param);
dsim_debug(dsim, "dsim mode %s dsc is %s [%d %d %d %d]\n",
dsim->config.mode == DSIM_VIDEO_MODE ? "video" : "cmd",
dsim->config.dsc.enabled ? "enabled" : "disabled",
dsim->config.dsc.dsc_count,
dsim->config.dsc.slice_count,
dsim->config.dsc.slice_width,
dsim->config.dsc.slice_height);
mutex_unlock(&dsim->state_lock);
if (dsim->dual_dsi == DSIM_DUAL_DSI_MAIN) {
sec_dsi = exynos_get_dual_dsi(DSIM_DUAL_DSI_SEC);
if (sec_dsi) {
sec_dsi->dsi_device = dsim->dsi_device;
dsim_set_display_mode(sec_dsi, mode, exynos_mode);
} else
dsim_err(dsim, "could not get main dsi\n");
}
}
static void dsim_atomic_mode_set(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct dsim_device *dsim = encoder_to_dsim(encoder);
const struct exynos_drm_connector_state *exynos_conn_state =
to_exynos_connector_state(conn_state);
dsim_set_display_mode(dsim, &crtc_state->adjusted_mode, &exynos_conn_state->exynos_mode);
}
static enum drm_mode_status dsim_mode_valid(struct drm_encoder *encoder,
const struct drm_display_mode *mode)
{
struct dsim_device *dsim = encoder_to_dsim(encoder);
if (!dsim_get_clock_mode(dsim, mode))
return MODE_NOMODE;
return MODE_OK;
}
/*
* Check whether mode change can happean seamlessly from dsim perspective.
* Seamless mode switch from dsim perspective can only happen if there's no
* need to change dsim configuration.
*/
static bool dsim_mode_is_seamless(const struct dsim_device *dsim,
const struct drm_display_mode *mode,
const struct exynos_display_mode *exynos_mode)
{
struct dsim_reg_config new_config = dsim->config;
if (dsim->current_pll_param != dsim_get_clock_mode(dsim, mode)) {
dsim_debug(dsim, "clock mode change not allowed seamlessly\n");
return false;
}
dsim_update_config_for_mode(&new_config, mode, exynos_mode);
if (dsim->config.mode != new_config.mode) {
dsim_debug(dsim, "op mode change not allowed seamlessly\n");
return false;
}
if (memcmp(&dsim->config.dsc, &new_config.dsc, sizeof(new_config.dsc))) {
dsim_debug(dsim, "dsc change not allowed seamlessly\n");
return false;
}
return true;
}
static int dsim_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *connector_state)
{
struct drm_display_mode *mode;
const struct dsim_device *dsim = encoder_to_dsim(encoder);
struct exynos_drm_connector_state *exynos_conn_state;
if (crtc_state->mode_changed) {
if (!is_exynos_drm_connector(connector_state->connector)) {
dsim_warn(dsim, "%s: mode set is only supported w/exynos connector\n",
__func__);
return -EINVAL;
}
exynos_conn_state = to_exynos_connector_state(connector_state);
mode = &crtc_state->adjusted_mode;
if (exynos_conn_state->seamless_possible &&
!dsim_mode_is_seamless(dsim, mode, &exynos_conn_state->exynos_mode)) {
dsim_warn(dsim, "%s: seamless mode switch not supported for %s\n",
__func__, mode->name);
exynos_conn_state->seamless_possible = false;
}
if (!exynos_conn_state->exynos_mode.sw_trigger) {
if (!dsim->pinctrl) {
dsim_err(dsim, "TE error: pinctrl not found\n");
return -EINVAL;
} else if ((dsim->te_gpio < 0) ||
(dsim->te_from >= MAX_DECON_TE_FROM_DDI)) {
dsim_err(dsim, "invalid TE config for hw trigger mode\n");
return -EINVAL;
}
exynos_conn_state->te_from = dsim->te_from;
exynos_conn_state->te_gpio = dsim->te_gpio;
}
}
return 0;
}
static const struct drm_encoder_helper_funcs dsim_encoder_helper_funcs = {
.mode_valid = dsim_mode_valid,
.atomic_mode_set = dsim_atomic_mode_set,
.atomic_enable = dsim_encoder_enable,
.atomic_disable = dsim_encoder_disable,
.atomic_check = dsim_atomic_check,
};
#ifdef CONFIG_DEBUG_FS
static int dsim_encoder_late_register(struct drm_encoder *encoder)
{
struct dsim_device *dsim = encoder_to_dsim(encoder);
dsim_diag_create_debugfs(dsim);
return 0;
}
static void dsim_encoder_early_unregister(struct drm_encoder *encoder)
{
struct dsim_device *dsim = encoder_to_dsim(encoder);
dsim_diag_remove_debugfs(dsim);
}
#else
static int dsim_encoder_late_register(struct drm_encoder * /* encoder */)
{
return 0;
}
static void dsim_encoder_early_unregister(struct drm_encoder * /*encoder */)
{
}
#endif
static const struct drm_encoder_funcs dsim_encoder_funcs = {
.destroy = drm_encoder_cleanup,
.late_register = dsim_encoder_late_register,
.early_unregister = dsim_encoder_early_unregister,
};
static int dsim_add_mipi_dsi_device(struct dsim_device *dsim,
const char *pname, enum panel_priority_index idx)
{
struct mipi_dsi_device_info info = {
.node = NULL,
};
struct device_node *node;
const char *name;
const char *dual_dsi;
dsim_debug(dsim, "preferred panel is %s\n", pname);
for_each_available_child_of_node(dsim->dsi_host.dev->of_node, node) {
bool found;
/*
* panel w/ reg node will be added in mipi_dsi_host_register,
* abort panel detection in that case
*/
if (of_find_property(node, "reg", NULL)) {
if (info.node)
of_node_put(info.node);
return -ENODEV;
}
/*
* We already detected panel we want but keep iterating
* in case there are devices with reg property
*/
if (info.node)
continue;
if (of_property_read_u32(node, "channel", &info.channel))
continue;
name = of_get_property(node, "label", NULL);
if (!name)
continue;
/* if panel name is not specified pick the first device found */
found = !strncmp(name, pname, PANEL_DRV_LEN);
if (pname[0] == '\0' || found) {
/*
* The default is primary panel. If not, add priority
* index in the panel name, i.e. "priority-index:panel-name"
*/
if (found && idx > PANEL_PRIORITY_PRI_IDX)
scnprintf(info.type, sizeof(info.type),
"%d:%s", idx, name);
else
strlcpy(info.type, name, sizeof(info.type));
info.node = of_node_get(node);
}
}
if (info.node) {
if (!of_property_read_string(info.node, "dual-dsi", &dual_dsi)) {
if (!strcmp(dual_dsi, "main"))
dsim->dual_dsi = DSIM_DUAL_DSI_MAIN;
else if (!strcmp(dual_dsi, "sec"))
dsim->dual_dsi = DSIM_DUAL_DSI_SEC;
else
dsim->dual_dsi = DSIM_DUAL_DSI_NONE;
}
if (dsim->dual_dsi != DSIM_DUAL_DSI_SEC)
mipi_dsi_device_register_full(&dsim->dsi_host, &info);
if (dsim->dual_dsi == DSIM_DUAL_DSI_NONE)
panel_usage |= BIT(idx);
return 0;
}
return -ENODEV;
}
static const char *dsim_get_panel_name(const struct dsim_device *dsim, const char *name, size_t len)
{
const char *p = strchr(name, ':');
size_t pos;
int dsim_id;
/* if colon is found expect format which includes intended intf
* (ex. dsim0:panel_name), otherwise fallback to panel name only */
if (!p)
return name;
if ((len < 5) || strncmp(name, "dsim", 4))
return NULL;
dsim_id = name[4] - '0';
if (dsim->id != dsim_id)
return NULL;
p++;
pos = p - name;
if (pos >= len)
return NULL;
return p;
}
static int dsim_parse_panel_name(struct dsim_device *dsim)
{
const char *name;
enum panel_priority_index idx;
name = dsim_get_panel_name(dsim, panel_name, PANEL_DRV_LEN);
idx = PANEL_PRIORITY_PRI_IDX;
if (name && !(panel_usage & BIT(idx)))
return dsim_add_mipi_dsi_device(dsim, name, idx);
name = dsim_get_panel_name(dsim, sec_panel_name, PANEL_DRV_LEN);
idx = PANEL_PRIORITY_SEC_IDX;
if (name && name[0] && !(panel_usage & BIT(idx)))
return dsim_add_mipi_dsi_device(dsim, name, idx);
return -ENODEV;
}
static int dsim_bind(struct device *dev, struct device *master, void *data)
{
struct drm_encoder *encoder = dev_get_drvdata(dev);
struct dsim_device *dsim = encoder_to_dsim(encoder);
struct drm_device *drm_dev = data;
int ret = 0;
dsim_debug(dsim, "%s +\n", __func__);
/* parse the panel name to select the dsi device for the detected panel */
dsim_parse_panel_name(dsim);
if (dsim->dual_dsi == DSIM_DUAL_DSI_SEC)
return 0;
drm_encoder_init(drm_dev, encoder, &dsim_encoder_funcs,
DRM_MODE_ENCODER_DSI, NULL);
drm_encoder_helper_add(encoder, &dsim_encoder_helper_funcs);
encoder->possible_crtcs = exynos_drm_get_possible_crtcs(encoder,
dsim->output_type);
if (!encoder->possible_crtcs) {
dsim_err(dsim, "failed to get possible crtc, ret = %d\n", ret);
drm_encoder_cleanup(encoder);
return -ENOTSUPP;
}
ret = mipi_dsi_host_register(&dsim->dsi_host);
dsim_debug(dsim, "%s -\n", __func__);
return ret;
}
static void dsim_unbind(struct device *dev, struct device *master,
void *data)
{
struct drm_encoder *encoder = dev_get_drvdata(dev);
struct dsim_device *dsim = encoder_to_dsim(encoder);
dsim_debug(dsim, "%s +\n", __func__);
if (dsim->pll_params)
dsim_modes_release(dsim->pll_params);
if (dsim->dual_dsi == DSIM_DUAL_DSI_SEC)
return;
mipi_dsi_host_unregister(&dsim->dsi_host);
}
static const struct component_ops dsim_component_ops = {
.bind = dsim_bind,
.unbind = dsim_unbind,
};
static int dsim_parse_dt(struct dsim_device *dsim)
{
struct device_node *np = dsim->dev->of_node;
int ret;
if (!np) {
dsim_err(dsim, "no device tree information\n");
return -ENOTSUPP;
}
of_property_read_u32(np, "dsim,id", &dsim->id);
if (dsim->id < 0 || dsim->id >= MAX_DSI_CNT) {
dsim_err(dsim, "wrong dsim id(%d)\n", dsim->id);
return -ENODEV;
}
dsim->pll_params = dsim_of_get_clock_mode(dsim);
dsim_of_get_pll_diags(dsim);
ret = of_property_read_u32(np, "te_from", &dsim->te_from);
if (ret) {
dsim->te_from = MAX_DECON_TE_FROM_DDI;
dsim_warn(dsim, "failed to get TE from DDI\n");
}
dsim_debug(dsim, "TE from DDI%d\n", dsim->te_from);
if (!ret) {
dsim->te_gpio = of_get_named_gpio(np, "te-gpio", 0);
if (dsim->te_gpio < 0) {
dsim_warn(dsim, "failed to get TE gpio\n");
dsim->te_from = MAX_DECON_TE_FROM_DDI;
}
}
return 0;
}
static int dsim_remap_regs(struct dsim_device *dsim)
{
struct resource res;
struct device *dev = dsim->dev;
struct device_node *np = dev->of_node;
int i, ret = 0;
i = of_property_match_string(np, "reg-names", "dsi");
if (of_address_to_resource(np, i, &res)) {
pr_err("failed to get dsi resource\n");
goto err;
}
dsim->res.regs = ioremap(res.start, resource_size(&res));
if (IS_ERR(dsim->res.regs)) {
dsim_err(dsim, "failed to remap io region\n");
ret = PTR_ERR(dsim->res.regs);
goto err;
}
dsim_regs_desc_init(dsim->res.regs, res.start, "dsi", REGS_DSIM_DSI, dsim->id);
i = of_property_match_string(np, "reg-names", "dphy");
if (of_address_to_resource(np, i, &res)) {
pr_err("failed to get dphy resource\n");
goto err;
}
dsim->res.phy_regs = ioremap(res.start, resource_size(&res));
if (IS_ERR(dsim->res.phy_regs)) {
dsim_err(dsim, "failed to remap io region\n");
ret = PTR_ERR(dsim->res.regs);
goto err_dsi;
}
dsim_regs_desc_init(dsim->res.phy_regs, res.start, "dphy", REGS_DSIM_PHY,
dsim->id);
i = of_property_match_string(np, "reg-names", "dphy-extra");
if (of_address_to_resource(np, i, &res)) {
pr_err("failed to get dphy resource\n");
goto err_dsi;
}
dsim->res.phy_regs_ex = ioremap(res.start, resource_size(&res));
if (IS_ERR(dsim->res.phy_regs_ex))
dsim_warn(dsim, "failed to remap io region. it's optional\n");
dsim_regs_desc_init(dsim->res.phy_regs_ex, res.start, "dphy-extra",
REGS_DSIM_PHY_BIAS, dsim->id);
np = of_find_compatible_node(NULL, NULL, "samsung,exynos9-disp_ss");
i = of_property_match_string(np, "reg-names", "sys");
if (of_address_to_resource(np, i, &res)) {
dsim_err(dsim, "failed to get sys resource\n");
goto err_dphy_ext;
}
dsim->res.ss_reg_base = ioremap(res.start, resource_size(&res));
if (!dsim->res.ss_reg_base) {
dsim_err(dsim, "failed to map sysreg-disp address.");
ret = PTR_ERR(dsim->res.ss_reg_base);
goto err_dphy_ext;
}
dsim_regs_desc_init(dsim->res.ss_reg_base, res.start, np->name, REGS_DSIM_SYS,
dsim->id);
return ret;
err_dphy_ext:
iounmap(dsim->res.phy_regs_ex);
iounmap(dsim->res.phy_regs);
err_dsi:
iounmap(dsim->res.regs);
err:
return ret;
}
#if IS_ENABLED(CONFIG_ARM_EXYNOS_DEVFREQ)
static void dsim_underrun_info(struct dsim_device *dsim, u32 underrun_cnt)
{
printk_ratelimited("underrun irq occurs(%u): MIF(%lu), INT(%lu), DISP(%lu)\n",
underrun_cnt,
exynos_devfreq_get_domain_freq(DEVFREQ_MIF),
exynos_devfreq_get_domain_freq(DEVFREQ_INT),
exynos_devfreq_get_domain_freq(DEVFREQ_DISP));
}
#else
static void dsim_underrun_info(struct dsim_device *dsim, u32 underrun_cnt)
{
printk_ratelimited("underrun irq occurs(%u)\n", underrun_cnt);
}
#endif
static irqreturn_t dsim_irq_handler(int irq, void *dev_id)
{
struct dsim_device *dsim = dev_id;
struct decon_device *decon = (struct decon_device *)dsim_get_decon(dsim);
unsigned int int_src;
spin_lock(&dsim->slock);
dsim_debug(dsim, "%s +\n", __func__);
if (dsim->state != DSIM_STATE_HSCLKEN) {
dsim_info(dsim, "dsim power is off state(0x%x)\n", dsim->state);
spin_unlock(&dsim->slock);
return IRQ_HANDLED;
}
int_src = dsim_reg_get_int_and_clear(dsim->id);
if (int_src & DSIM_INTSRC_SFR_PH_FIFO_EMPTY) {
complete(&dsim->ph_wr_comp);
dsim_debug(dsim, "PH_FIFO_EMPTY irq occurs\n");
}
if (int_src & DSIM_INTSRC_SFR_PL_FIFO_EMPTY) {
complete(&dsim->pl_wr_comp);
dsim_debug(dsim, "PL_FIFO_EMPTY irq occurs\n");
}
if (int_src & DSIM_INTSRC_RX_DATA_DONE)
complete(&dsim->rd_comp);
if (int_src & DSIM_INTSRC_FRAME_DONE) {
dsim_debug(dsim, "framedone irq occurs\n");
if (decon)
DPU_EVENT_LOG(DPU_EVT_DSIM_FRAMEDONE, decon->id, NULL);
}
if (int_src & DSIM_INTSRC_RX_CRC) {
dsim_err(dsim, "RX CRC error was detected!\n");
if (decon)
DPU_EVENT_LOG(DPU_EVT_DSIM_CRC, decon->id, NULL);
}
if (int_src & DSIM_INTSRC_ERR_RX_ECC) {
dsim_err(dsim, "RX ECC Multibit error was detected!\n");
if (decon)
DPU_EVENT_LOG(DPU_EVT_DSIM_ECC, decon->id, NULL);
}
if (int_src & DSIM_INTSRC_UNDER_RUN) {
DPU_ATRACE_INT("DPU_UNDERRUN", 1);
if (decon) {
static unsigned long last_dumptime;
dsim_underrun_info(dsim, decon->d.underrun_cnt + 1);
DPU_EVENT_LOG(DPU_EVT_DSIM_UNDERRUN, decon->id, NULL);
if (time_after(jiffies, last_dumptime + msecs_to_jiffies(5000))) {
decon_dump_event_condition(decon, DPU_EVT_CONDITION_UNDERRUN);
last_dumptime = jiffies;
}
} else {
dsim_underrun_info(dsim, 0);
}
DPU_ATRACE_INT("DPU_UNDERRUN", 0);
}
spin_unlock(&dsim->slock);
return IRQ_HANDLED;
}
static int dsim_register_irq(struct dsim_device *dsim)
{
struct device *dev = dsim->dev;
struct device_node *np = dev->of_node;
struct platform_device *pdev;
int ret = 0;
pdev = container_of(dev, struct platform_device, dev);
dsim->irq = of_irq_get_byname(np, "dsim");
ret = devm_request_irq(dsim->dev, dsim->irq, dsim_irq_handler, 0,
pdev->name, dsim);
if (ret) {
dsim_err(dsim, "failed to install DSIM irq\n");
return -EINVAL;
}
disable_irq(dsim->irq);
return 0;
}
static int dsim_get_phys(struct dsim_device *dsim)
{
if (IS_ENABLED(CONFIG_BOARD_EMULATOR))
return 0;
dsim->res.phy = devm_phy_get(dsim->dev, "dsim_dphy");
if (IS_ERR(dsim->res.phy)) {
dsim_err(dsim, "failed to get dsim phy\n");
return PTR_ERR(dsim->res.phy);
}
dsim->res.phy_ex = devm_phy_get(dsim->dev, "dsim_dphy_extra");
if (IS_ERR(dsim->res.phy_ex)) {
dsim_warn(dsim, "failed to get dsim extra phy\n");
dsim->res.phy_ex = NULL;
}
return 0;
}
static int dsim_init_resources(struct dsim_device *dsim)
{
int ret = 0;
ret = dsim_remap_regs(dsim);
if (ret)
goto err;
ret = dsim_register_irq(dsim);
if (ret)
goto err;
ret = dsim_get_phys(dsim);
if (ret)
goto err;
err:
return ret;
}
static int dsim_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
{
struct dsim_device *dsim = host_to_dsi(host);
struct drm_bridge *bridge;
int ret;
dsim_debug(dsim, "%s +\n", __func__);
bridge = of_drm_find_bridge(device->dev.of_node);
if (!bridge) {
struct drm_panel *panel;
panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(panel)) {
dsim_err(dsim, "failed to find panel\n");
return PTR_ERR(panel);
}
bridge = devm_drm_panel_bridge_add_typed(host->dev, panel,
DRM_MODE_CONNECTOR_DSI);
if (IS_ERR(bridge)) {
dsim_err(dsim, "failed to create panel bridge\n");
return PTR_ERR(bridge);
}
}
ret = drm_bridge_attach(&dsim->encoder, bridge, NULL, 0);
if (ret) {
dsim_err(dsim, "Unable to attach panel bridge\n");
} else {
dsim->panel_bridge = bridge;
dsim->dsi_device = device;
}
ret = sysfs_create_link(&device->dev.kobj, &host->dev->kobj, "dsim");
if (ret)
dev_warn(&device->dev, "unable to link %s sysfs (%d)\n", "dsim", ret);
dsim_debug(dsim, "%s -\n", __func__);
return ret;
}
static int dsim_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
{
struct dsim_device *dsim = host_to_dsi(host);
dsim_info(dsim, "%s +\n", __func__);
_dsim_disable(dsim);
if (dsim->panel_bridge) {
struct drm_bridge *bridge = dsim->panel_bridge;
if (bridge->funcs && bridge->funcs->detach)
bridge->funcs->detach(bridge);
dsim->panel_bridge = NULL;
}
dsim->dsi_device = NULL;
sysfs_remove_link(&device->dev.kobj, "dsim");
dsim_info(dsim, "%s -\n", __func__);
return 0;
}
static int __dsim_wait_for_ph_fifo_empty(struct dsim_device *dsim)
{
const struct decon_device *decon = dsim_get_decon(dsim);
dsim_debug(dsim, "wait for packet header fifo empty\n");
if (!wait_for_completion_timeout(&dsim->ph_wr_comp, MIPI_WR_TIMEOUT)) {
if (dsim_reg_header_fifo_is_empty(dsim->id)) {
dsim_reg_clear_int(dsim->id,
DSIM_INTSRC_SFR_PH_FIFO_EMPTY);
return 0;
}
dsim_warn(dsim, "timeout: packet header fifo empty\n");
if (decon) {
DPU_EVENT_LOG(DPU_EVT_DSIM_PH_FIFO_TIMEOUT, decon->id,
NULL);
decon_dump_event_condition(decon,
DPU_EVT_CONDITION_FIFO_TIMEOUT);
}
return -ETIMEDOUT;
}
return 0;
}
static int __dsim_wait_for_pl_fifo_empty(struct dsim_device *dsim)
{
const struct decon_device *decon = dsim_get_decon(dsim);
dsim_debug(dsim, "wait for packet payload fifo empty\n");
if (!wait_for_completion_timeout(&dsim->pl_wr_comp, MIPI_WR_TIMEOUT)) {
if (dsim_reg_payload_fifo_is_empty(dsim->id)) {
dsim_reg_clear_int(dsim->id,
DSIM_INTSRC_SFR_PL_FIFO_EMPTY);
return 0;
}
dsim_warn(dsim, "timeout: packet payload fifo empty\n");
if (decon) {
DPU_EVENT_LOG(DPU_EVT_DSIM_PL_FIFO_TIMEOUT, decon->id,
NULL);
decon_dump_event_condition(decon,
DPU_EVT_CONDITION_FIFO_TIMEOUT);
}
return -ETIMEDOUT;
}
return 0;
}
static int dsim_wait_for_cmd_fifo_empty(struct dsim_device *dsim, bool is_long)
{
int ret = 0;
if (is_long)
ret = __dsim_wait_for_pl_fifo_empty(dsim);
ret |= __dsim_wait_for_ph_fifo_empty(dsim);
if (ret)
dsim_dump(dsim);
return ret;
}
static void
dsim_write_payload(struct dsim_device *dsim, const u8* buf, size_t len)
{
const u8 *p = buf;
const u8 *end = buf + len;
u32 payload;
dsim_debug(dsim, "payload length(%lu)\n", len);
while (p < end) {
size_t pkt_size = min_t(size_t, 4, end - p);
if (pkt_size >= 4)
payload = p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
else if (pkt_size == 3)
payload = p[0] | p[1] << 8 | p[2] << 16;
else if (pkt_size == 2)
payload = p[0] | p[1] << 8;
else if (pkt_size == 1)
payload = p[0];
dsim_reg_wr_tx_payload(dsim->id, payload);
dsim_debug(dsim, "payload: 0x%x\n", payload);
p += pkt_size;
}
}
static void __dsim_write_data(struct dsim_device *dsim,
const struct mipi_dsi_msg *msg, bool is_long)
{
struct mipi_dsi_packet packet;
mipi_dsi_create_packet(&packet, msg);
if (is_long)
dsim_write_payload(dsim, packet.payload, packet.payload_length);
dsim_reg_wr_tx_header(dsim->id, packet.header[0], packet.header[1],
packet.header[2], false);
dsim_debug(dsim, "header(0x%x 0x%x 0x%x) size(%lu) ph fifo(%d)\n",
packet.header[0], packet.header[1], packet.header[2],
packet.size, dsim_reg_get_ph_cnt(dsim->id));
}
static int dsim_write_single_cmd_locked(struct dsim_device *dsim,
const struct mipi_dsi_msg *msg, bool is_long)
{
const u8 *tx_buf = msg->tx_buf;
WARN_ON(!mutex_is_locked(&dsim->cmd_lock));
DPU_EVENT_LOG_CMD(dsim, msg->type, tx_buf[0], msg->tx_len);
dsim_reg_clear_int(dsim->id, DSIM_INTSRC_SFR_PH_FIFO_EMPTY);
reinit_completion(is_long ? &dsim->pl_wr_comp : &dsim->ph_wr_comp);
__dsim_write_data(dsim, msg, is_long);
return dsim_wait_for_cmd_fifo_empty(dsim, is_long);
}
/*
* <-- ACTIVE -->
* data transfer ---|-**************------------------|--
* <-CMD ALLOW->
* CMD LOCK __|-----------------|_____________|---
*
* TE PROTECT <------- TE PROTECT ON -------->
*
* ready allow <---- ready allow ---->|<-1ms->|
*
* It is important to set packet-go ready to high to send multiple commnads
* within one vblank interval at the same time. Because as soon as it becomes
* high, the piled commands will start transmission. The ready_allow_period is
* defined so that the stacked commands would not be sent in two vblank
* intervals. The ready_allow_period is set from start of vblank to end of
* CMD ALLOW period - 1ms. The 1ms is enough time to send the stacked commands
* at once.
*/
#define PKTGO_READY_MARGIN_NS 1000000
static void need_wait_vblank(struct dsim_device *dsim)
{
const struct decon_device *decon = dsim_get_decon(dsim);
struct drm_vblank_crtc *vblank;
struct drm_crtc *crtc;
ktime_t last_vblanktime, diff, cur_time;
int ready_allow_period;
if (!decon)
return;
crtc = &decon->crtc->base;
if (!crtc)
return;
if (drm_crtc_vblank_get(crtc))
return;
vblank = &crtc->dev->vblank[crtc->index];
ready_allow_period =
mult_frac(vblank->framedur_ns, 95, 100) - PKTGO_READY_MARGIN_NS;
drm_crtc_vblank_count_and_time(crtc, &last_vblanktime);
cur_time = ktime_get();
diff = ktime_sub_ns(cur_time, last_vblanktime);
dsim_debug(dsim, "last(%lld) cur(%lld) diff(%lld) ready allow period(%d)\n",
last_vblanktime, cur_time, diff, ready_allow_period);
if (diff > ready_allow_period)
drm_crtc_wait_one_vblank(crtc);
drm_crtc_vblank_put(crtc);
}
#define PL_FIFO_THRESHOLD mult_frac(MAX_PL_FIFO, 75, 100) /* 75% */
#define IS_LAST(flags) (((flags) & MIPI_DSI_MSG_LASTCOMMAND) != 0)
static int
dsim_write_data(struct dsim_device *dsim, const struct mipi_dsi_msg *msg)
{
int ret = 0;
u16 flags = msg->flags;
bool is_long = mipi_dsi_packet_format_is_long(msg->type);
if (dsim->config.mode == DSIM_VIDEO_MODE) {
ret = dsim_write_single_cmd_locked(dsim, msg, is_long);
goto err;
}
if (((dsim->total_pend_pl + msg->tx_len) > MAX_PL_FIFO) ||
(dsim->total_pend_ph == MAX_PH_FIFO)) {
dsim_err(dsim, "fifo would be full. ph(%u) pl(%lu) max(%d/%d)\n",
dsim->total_pend_ph,
dsim->total_pend_pl + msg->tx_len,
MAX_PH_FIFO, MAX_PL_FIFO);
ret = -EINVAL;
goto err;
}
if (!IS_LAST(msg->flags) &&
(((dsim->total_pend_ph + 1) == MAX_PH_FIFO) ||
((dsim->total_pend_pl + msg->tx_len) > PL_FIFO_THRESHOLD))) {
dsim_warn(dsim, "warning. changed last command. pend pl/pl(%u,%u)\n",
dsim->total_pend_ph, dsim->total_pend_pl);
flags |= MIPI_DSI_MSG_LASTCOMMAND;
}
dsim_debug(dsim, "%s last command\n", IS_LAST(flags) ? "" : "Not");
if (IS_LAST(flags)) {
if (dsim->total_pend_ph) {
reinit_completion(is_long ?
&dsim->pl_wr_comp : &dsim->ph_wr_comp);
__dsim_write_data(dsim, msg, is_long);
if (!(flags & EXYNOS_DSI_MSG_IGNORE_VBLANK))
need_wait_vblank(dsim);
dsim_reg_ready_packetgo(dsim->id, true);
dsim_debug(dsim, "packet go ready\n");
ret = dsim_wait_for_cmd_fifo_empty(dsim, is_long);
if (!ret) {
dsim_reg_enable_packetgo(dsim->id, false);
dsim->total_pend_ph = 0;
dsim->total_pend_pl = 0;
}
pm_runtime_put_sync(dsim->dev);
} else {
ret = dsim_write_single_cmd_locked(dsim, msg, is_long);
}
} else {
if (!dsim->total_pend_ph) {
pm_runtime_get_sync(dsim->dev);
dsim_reg_enable_packetgo(dsim->id, true);
}
dsim->total_pend_ph++;
dsim->total_pend_pl += ALIGN(msg->tx_len, 4);
__dsim_write_data(dsim, msg, is_long);
dsim_debug(dsim, "total pending packet header(%u) payload(%u)\n",
dsim->total_pend_ph, dsim->total_pend_pl);
}
err:
return ret;
}
static int
dsim_req_read_command(struct dsim_device *dsim, const struct mipi_dsi_msg *msg)
{
struct mipi_dsi_packet packet;
dsim_reg_clear_int(dsim->id, DSIM_INTSRC_SFR_PH_FIFO_EMPTY);
reinit_completion(&dsim->ph_wr_comp);
/* set the maximum packet size returned */
dsim_reg_wr_tx_header(dsim->id, MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE,
msg->rx_len, 0, false);
/* read request */
mipi_dsi_create_packet(&packet, msg);
dsim_reg_wr_tx_header(dsim->id, packet.header[0], packet.header[1],
packet.header[2], true);
return dsim_wait_for_cmd_fifo_empty(dsim, false);
}
static int
dsim_read_data(struct dsim_device *dsim, const struct mipi_dsi_msg *msg)
{
u32 rx_fifo, rx_size = 0;
int i = 0, ret = 0;
u8 *rx_buf = msg->rx_buf;
if (msg->rx_len > MAX_RX_FIFO) {
dsim_err(dsim, "invalid rx len(%lu) max(%d)\n", msg->rx_len,
MAX_RX_FIFO);
return -EINVAL;
}
/* Init RX FIFO before read and clear DSIM_INTSRC */
dsim_reg_clear_int(dsim->id, DSIM_INTSRC_RX_DATA_DONE);
reinit_completion(&dsim->rd_comp);
ret = dsim_req_read_command(dsim, msg);
if (ret) {
dsim_err(dsim, "failed to request dsi read command\n");
return ret;
}
if (!wait_for_completion_timeout(&dsim->rd_comp, MIPI_RD_TIMEOUT)) {
dsim_err(dsim, "read timeout\n");
return -ETIMEDOUT;
}
rx_fifo = dsim_reg_get_rx_fifo(dsim->id);
dsim_debug(dsim, "rx fifo:0x%8x, response:0x%x, rx_len:%lu\n", rx_fifo,
rx_fifo & 0xff, msg->rx_len);
/* 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_dump(dsim);
return ret;
}
break;
case MIPI_DSI_RX_END_OF_TRANSMISSION:
dsim_debug(dsim, "EoTp was received\n");
break;
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
WARN_ON(msg->rx_len > 2);
rx_buf[1] = (rx_fifo >> 16) & 0xff;
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
rx_buf[0] = (rx_fifo >> 8) & 0xff;
dsim_debug(dsim, "short packet was received\n");
rx_size = msg->rx_len;
break;
case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE:
case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE:
dsim_debug(dsim, "long packet was received\n");
rx_size = (rx_fifo & 0x00ffff00) >> 8;
while (i < rx_size) {
const u32 rx_max =
min_t(u32, rx_size, i + sizeof(rx_fifo));
rx_fifo = dsim_reg_get_rx_fifo(dsim->id);
dsim_debug(dsim, "payload: 0x%x i=%d max=%d\n", rx_fifo,
i, rx_max);
for (; i < rx_max; i++, rx_fifo >>= 8)
rx_buf[i] = rx_fifo & 0xff;
}
break;
default:
dsim_err(dsim, "packet format is invalid.\n");
dsim_dump(dsim);
return -EBUSY;
}
if (!dsim_reg_rx_fifo_is_empty(dsim->id)) {
u32 retry_cnt = RETRY_READ_FIFO_MAX;
dsim_warn(dsim, "RX FIFO is not empty: rx_size:%u, rx_len:%lu\n",
rx_size, msg->rx_len);
dsim_dump(dsim);
do {
rx_fifo = dsim_reg_get_rx_fifo(dsim->id);
dsim_info(dsim, "rx fifo:0x%8x, response:0x%x\n",
rx_fifo, rx_fifo & 0xff);
} while (!dsim_reg_rx_fifo_is_empty(dsim->id) && --retry_cnt);
}
return rx_size;
}
static int
dsim_write_data_dual(struct dsim_device *dsim, const struct mipi_dsi_msg *msg)
{
int ret;
ret = pm_runtime_resume_and_get(dsim->dev);
if (ret) {
dsim_err(dsim, "runtime resume failed (%d). unable to transfer cmd\n", ret);
return ret;
}
mutex_lock(&dsim->cmd_lock);
ret = dsim_write_data(dsim, msg);
mutex_unlock(&dsim->cmd_lock);
pm_runtime_mark_last_busy(dsim->dev);
pm_runtime_put_sync_autosuspend(dsim->dev);
return ret;
}
static ssize_t dsim_host_transfer(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
{
struct dsim_device *dsim = host_to_dsi(host);
struct dsim_device *sec_dsi;
int ret;
DPU_ATRACE_BEGIN(__func__);
ret = pm_runtime_resume_and_get(dsim->dev);
if (ret) {
dsim_err(dsim, "runtime resume failed (%d). unable to transfer cmd\n", ret);
return ret;
}
mutex_lock(&dsim->cmd_lock);
if (WARN_ON(dsim->state != DSIM_STATE_HSCLKEN)) {
ret = -EPERM;
goto abort;
}
switch (msg->type) {
case MIPI_DSI_DCS_READ:
case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
ret = dsim_read_data(dsim, msg);
break;
default:
ret = dsim_write_data(dsim, msg);
if (dsim->dual_dsi == DSIM_DUAL_DSI_MAIN) {
sec_dsi = exynos_get_dual_dsi(DSIM_DUAL_DSI_SEC);
if (sec_dsi)
ret = dsim_write_data_dual(sec_dsi, msg);
else
dsim_err(dsim, "could not get secondary dsi\n");
}
break;
}
abort:
mutex_unlock(&dsim->cmd_lock);
pm_runtime_mark_last_busy(dsim->dev);
pm_runtime_put_sync_autosuspend(dsim->dev);
DPU_ATRACE_END(__func__);
return ret;
}
/* TODO: Below operation will be registered after panel driver is created. */
static const struct mipi_dsi_host_ops dsim_host_ops = {
.attach = dsim_host_attach,
.detach = dsim_host_detach,
.transfer = dsim_host_transfer,
};
static int dsim_calc_pmsk(struct dsim_pll_features *pll_features,
struct stdphy_pms *pms, unsigned int hs_clock_mhz)
{
uint64_t hs_clock;
uint64_t fvco, q;
uint32_t p, m, s, k;
p = DIV_ROUND_CLOSEST(pll_features->finput, pll_features->foptimum);
if (p == 0)
p = 1;
if ((p < pll_features->p_min) || (p > pll_features->p_max)) {
pr_err("%s: p %u is out of range (%u, %u)\n",
__func__, p, pll_features->p_min, pll_features->p_max);
return -EINVAL;
}
hs_clock = (uint64_t) hs_clock_mhz * 1000000;
if ((hs_clock < pll_features->fout_min) ||
(hs_clock > pll_features->fout_max)) {
pr_err("%s: hs clock %llu out of range\n", __func__, hs_clock);
return -EINVAL;
}
/* find s: vco_min <= fout * 2 ^ s <= vco_max */
for (s = 0, fvco = 0; fvco < pll_features->fvco_min; s++)
fvco = hs_clock * (1 << s);
--s;
if (fvco > pll_features->fvco_max) {
pr_err("%s: no proper s found\n", __func__);
return -EINVAL;
}
if ((s < pll_features->s_min) || (s > pll_features->s_max)) {
pr_err("%s: s %u is out of range (%u, %u)\n",
__func__, s, pll_features->s_min, pll_features->s_max);
return -EINVAL;
}
/* (hs_clk * 2^s / 2) / (fin / p) = m + k / 2^k_bits */
fvco >>= 1;
q = fvco << (pll_features->k_bits + 1); /* 1 extra bit for roundup */
q /= pll_features->finput / p;
/* m is the integer part, k is the fraction part */
m = q >> (pll_features->k_bits + 1);
if ((m < pll_features->m_min) || (m > pll_features->m_max)) {
pr_err("%s: m %u is out of range (%u, %u)\n",
__func__, m, pll_features->m_min, pll_features->m_max);
return -EINVAL;
}
k = q & ((1 << (pll_features->k_bits + 1)) - 1);
k = DIV_ROUND_UP(k, 2);
/* k is two's complement integer */
if (k & (1 << (pll_features->k_bits - 1)))
m++;
pms->p = p;
pms->m = m;
pms->s = s;
pms->k = k;
return 0;
}
static int dsim_calc_underrun(const struct dsim_device *dsim, uint32_t hs_clock_mhz,
uint32_t *underrun)
{
const struct dsim_reg_config *config = &dsim->config;
uint32_t lanes = config->data_lane_cnt;
uint32_t number_of_transfer;
uint32_t w_threshold;
uint64_t wclk;
uint64_t max_frame_time;
uint64_t frame_data;
uint64_t packet_header;
uint64_t min_frame_transfer_time;
uint64_t max_lp_time;
number_of_transfer = config->p_timing.vactive;
w_threshold = config->p_timing.hactive;
if (config->dsc.enabled)
w_threshold /= 3;
wclk = (uint64_t) hs_clock_mhz * 1000000 / 16;
/* max time to transfer one frame, in the unit of nanosecond */
max_frame_time = NSEC_PER_SEC * 100 /
(config->p_timing.vrefresh * (100 + config->p_timing.te_var)) -
NSEC_PER_USEC * config->p_timing.te_idle_us;
/* one frame pixel data (bytes) */
frame_data = number_of_transfer * w_threshold * config->bpp / 8;
/* packet header (bytes) */
packet_header = number_of_transfer * 7;
/* minimum time to transfer one frame, in nanosecond */
min_frame_transfer_time = (frame_data + packet_header) *
NSEC_PER_SEC / (2 * lanes * wclk);
if (max_frame_time < min_frame_transfer_time) {
pr_err("%s: max frame time %llu < min frame time %llu\n",
__func__, max_frame_time, min_frame_transfer_time);
return -EINVAL;
}
max_lp_time = max_frame_time - min_frame_transfer_time;
/* underrun unit is 100 wclk, round up */
*underrun = (uint32_t) DIV_ROUND_UP(max_lp_time * wclk / NSEC_PER_SEC, 100);
return 0;
}
static int dsim_set_hs_clock(struct dsim_device *dsim, unsigned int hs_clock, bool apply_now)
{
int ret;
struct stdphy_pms pms;
uint32_t lp_underrun = 0;
struct dsim_pll_param *pll_param;
if (!dsim->pll_params || !dsim->pll_params->features)
return -ENODEV;
memset(&pms, 0, sizeof(pms));
ret = dsim_calc_pmsk(dsim->pll_params->features, &pms, hs_clock);
if (ret < 0) {
dsim_err(dsim, "Failed to update pll for hsclk %d\n", hs_clock);
return -EINVAL;
}
mutex_lock(&dsim->state_lock);
ret = dsim_calc_underrun(dsim, hs_clock, &lp_underrun);
if (ret < 0) {
dsim_err(dsim, "Failed to update underrun\n");
goto out;
}
pll_param = dsim->current_pll_param;
if (!pll_param) {
ret = -EAGAIN;
goto out;
}
pll_param->pll_freq = hs_clock;
pll_param->p = pms.p;
pll_param->m = pms.m;
pll_param->s = pms.s;
pll_param->k = pms.k;
pll_param->cmd_underrun_cnt = lp_underrun;
dsim_update_clock_config(dsim, pll_param);
if (!apply_now || dsim->state != DSIM_STATE_HSCLKEN)
goto out;
/* Restart dsim to apply new clock settings */
dsim_restart(dsim);
out:
mutex_unlock(&dsim->state_lock);
return ret;
}
static ssize_t bist_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", dsim->bist_mode);
}
static ssize_t bist_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
int rc;
unsigned int bist_mode;
bool bist_en;
rc = kstrtouint(buf, 0, &bist_mode);
if (rc < 0)
return rc;
/*
* BIST modes:
* 0: Disable, 1: Color Bar, 2: GRAY Gradient, 3: User-Defined,
* 4: Prbs7 Random
*/
if (bist_mode > DSIM_BIST_MODE_MAX) {
dsim_err(dsim, "invalid bist mode\n");
return -EINVAL;
}
bist_en = bist_mode > 0;
if (bist_en && dsim->state == DSIM_STATE_SUSPEND)
_dsim_enable(dsim);
dsim_reg_set_bist(dsim->id, bist_en, bist_mode - 1);
dsim->bist_mode = bist_mode;
if (!bist_en && dsim->state == DSIM_STATE_HSCLKEN)
_dsim_disable(dsim);
dsim_info(dsim, "0:Disable 1:ColorBar 2:GRAY Gradient 3:UserDefined\n");
dsim_info(dsim, "4:Prbs7 Random (%d)\n", dsim->bist_mode);
return len;
}
static DEVICE_ATTR_RW(bist_mode);
static ssize_t hs_clock_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", dsim->clk_param.hs_clk);
}
static ssize_t hs_clock_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
int rc;
unsigned int hs_clock;
bool apply_now = true;
char params[32];
char *hs_clk_str;
char *apply_now_str;
char *p = params;
strlcpy(params, buf, sizeof(params));
hs_clk_str = strsep(&p, " ");
apply_now_str = strsep(&p, " ");
if (apply_now_str) {
rc = kstrtobool(apply_now_str, &apply_now);
if (rc < 0)
return rc;
}
rc = kstrtouint(hs_clk_str, 0, &hs_clock);
if (rc < 0)
return rc;
/* ddr hs_clock unit: MHz */
dsim_info(dsim, "%s: hs clock %u, apply now: %u\n", __func__, hs_clock, apply_now);
rc = dsim_set_hs_clock(dsim, hs_clock, apply_now);
if (rc < 0)
return rc;
return len;
}
static DEVICE_ATTR_RW(hs_clock);
static int dsim_get_pinctrl(struct dsim_device *dsim)
{
int ret = 0;
dsim->pinctrl = devm_pinctrl_get(dsim->dev);
if (IS_ERR(dsim->pinctrl)) {
ret = PTR_ERR(dsim->pinctrl);
dsim_debug(dsim, "failed to get pinctrl (%d)\n", ret);
dsim->pinctrl = NULL;
/* optional in video mode */
return 0;
}
dsim->te_on = pinctrl_lookup_state(dsim->pinctrl, "hw_te_on");
if (IS_ERR(dsim->te_on)) {
dsim_err(dsim, "failed to get hw_te_on pin state\n");
ret = PTR_ERR(dsim->te_on);
dsim->te_on = NULL;
goto err;
}
dsim->te_off = pinctrl_lookup_state(dsim->pinctrl, "hw_te_off");
if (IS_ERR(dsim->te_off)) {
dsim_err(dsim, "failed to get hw_te_off pin state\n");
ret = PTR_ERR(dsim->te_off);
dsim->te_off = NULL;
goto err;
}
err:
return ret;
}
static int dsim_probe(struct platform_device *pdev)
{
struct dsim_device *dsim;
int ret;
dsim = devm_kzalloc(&pdev->dev, sizeof(*dsim), GFP_KERNEL);
if (!dsim)
return -ENOMEM;
dma_set_mask(&pdev->dev, DMA_BIT_MASK(36));
dsim->dsi_host.ops = &dsim_host_ops;
dsim->dsi_host.dev = &pdev->dev;
dsim->dev = &pdev->dev;
ret = dsim_parse_dt(dsim);
if (ret)
goto err;
dsim_drvdata[dsim->id] = dsim;
dsim->output_type = (dsim->id == 0) ?
EXYNOS_DISPLAY_TYPE_DSI0 : EXYNOS_DISPLAY_TYPE_DSI1;
spin_lock_init(&dsim->slock);
mutex_init(&dsim->cmd_lock);
mutex_init(&dsim->state_lock);
init_completion(&dsim->ph_wr_comp);
init_completion(&dsim->pl_wr_comp);
init_completion(&dsim->rd_comp);
ret = dsim_init_resources(dsim);
if (ret)
goto err;
ret = dsim_get_pinctrl(dsim);
if (ret)
goto err;
ret = device_create_file(dsim->dev, &dev_attr_bist_mode);
if (ret < 0)
dsim_err(dsim, "failed to add sysfs bist_mode entries\n");
ret = device_create_file(dsim->dev, &dev_attr_hs_clock);
if (ret < 0)
dsim_err(dsim, "failed to add sysfs hs_clock entries\n");
platform_set_drvdata(pdev, &dsim->encoder);
#if defined(CONFIG_CPU_IDLE)
dsim->idle_ip_index = exynos_get_idle_ip_index(dev_name(&pdev->dev));
dsim_info(dsim, "dsim idle_ip_index[%d]\n", dsim->idle_ip_index);
if (dsim->idle_ip_index < 0)
dsim_warn(dsim, "idle ip index is not provided\n");
exynos_update_ip_idle_status(dsim->idle_ip_index, 1);
#endif
dsim->state = DSIM_STATE_SUSPEND;
pm_runtime_use_autosuspend(dsim->dev);
pm_runtime_set_autosuspend_delay(dsim->dev, 20);
pm_runtime_enable(dsim->dev);
if (!IS_ENABLED(CONFIG_BOARD_EMULATOR)) {
phy_init(dsim->res.phy);
if (dsim->res.phy_ex)
phy_init(dsim->res.phy_ex);
}
dsim_info(dsim, "driver has been probed.\n");
return component_add(dsim->dev, &dsim_component_ops);
err:
dsim_err(dsim, "failed to probe exynos dsim driver\n");
return ret;
}
static int dsim_remove(struct platform_device *pdev)
{
struct dsim_device *dsim = platform_get_drvdata(pdev);
device_remove_file(dsim->dev, &dev_attr_bist_mode);
device_remove_file(dsim->dev, &dev_attr_hs_clock);
pm_runtime_disable(&pdev->dev);
component_del(&pdev->dev, &dsim_component_ops);
iounmap(dsim->res.ss_reg_base);
iounmap(dsim->res.phy_regs_ex);
iounmap(dsim->res.phy_regs);
iounmap(dsim->res.regs);
return 0;
}
#ifdef CONFIG_PM
static int dsim_runtime_suspend(struct device *dev)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
DPU_ATRACE_BEGIN(__func__);
mutex_lock(&dsim->state_lock);
dsim_debug(dsim, "state: %d\n", dsim->state);
if (dsim->state == DSIM_STATE_HSCLKEN)
_dsim_enter_ulps_locked(dsim);
dsim->suspend_state = dsim->state;
mutex_unlock(&dsim->state_lock);
DPU_ATRACE_END(__func__);
return 0;
}
static int dsim_runtime_resume(struct device *dev)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
int ret = 0;
DPU_ATRACE_BEGIN(__func__);
mutex_lock(&dsim->state_lock);
dsim_debug(dsim, "state: %d\n", dsim->state);
if (dsim->state == DSIM_STATE_BYPASS)
ret = -EPERM;
else if (dsim->state == DSIM_STATE_ULPS)
_dsim_exit_ulps_locked(dsim);
dsim->suspend_state = dsim->state;
mutex_unlock(&dsim->state_lock);
DPU_ATRACE_END(__func__);
return ret;
}
static int dsim_suspend(struct device *dev)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
mutex_lock(&dsim->state_lock);
dsim->suspend_state = dsim->state;
if (dsim->state == DSIM_STATE_HSCLKEN) {
_dsim_enter_ulps_locked(dsim);
dev->power.must_resume = true;
}
dsim_debug(dsim, "-\n");
mutex_unlock(&dsim->state_lock);
return 0;
}
static int dsim_resume(struct device *dev)
{
struct dsim_device *dsim = dev_get_drvdata(dev);
mutex_lock(&dsim->state_lock);
if (dsim->suspend_state == DSIM_STATE_HSCLKEN)
_dsim_exit_ulps_locked(dsim);
dsim_debug(dsim, "-\n");
mutex_unlock(&dsim->state_lock);
return 0;
}
#endif
static const struct dev_pm_ops dsim_pm_ops = {
SET_RUNTIME_PM_OPS(dsim_runtime_suspend, dsim_runtime_resume, NULL)
SET_LATE_SYSTEM_SLEEP_PM_OPS(dsim_suspend, dsim_resume)
};
struct platform_driver dsim_driver = {
.probe = dsim_probe,
.remove = dsim_remove,
.driver = {
.name = "exynos-dsim",
.owner = THIS_MODULE,
.of_match_table = dsim_of_match,
.pm = &dsim_pm_ops,
},
};
MODULE_SOFTDEP("pre: phy-exynos-mipi");
MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master");
MODULE_LICENSE("GPL v2");