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

1166 lines
29 KiB
C
Raw Permalink Normal View History

2023-06-18 22:53:49 +00:00
/*
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Interface file between DECON and DSIM for Samsung EXYNOS DPU 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/of_gpio.h>
#include <linux/of.h>
#include <linux/clk-provider.h>
#include <linux/pm_runtime.h>
#include <linux/exynos_iovmm.h>
#if defined(CONFIG_SUPPORT_KERNEL_4_9)
#include <linux/sched.h>
#else
#include <linux/sched/types.h>
#endif
#include <linux/of_address.h>
#include <linux/pinctrl/consumer.h>
#include <linux/irq.h>
#include <media/v4l2-subdev.h>
#if defined(CONFIG_EXYNOS_WD_DVFS)
#include <linux/exynos-wd.h>
#endif
#include "decon.h"
#include "dsim.h"
#include "dpp.h"
//#include "../../../../soc/samsung/pwrcal/pwrcal.h"
//#include "../../../../soc/samsung/pwrcal/S5E8890/S5E8890-vclk.h"
#include "../../../../../kernel/irq/internals.h"
#ifdef CONFIG_EXYNOS_WD_DVFS
struct task_struct *devfreq_change_task;
#endif
/* DECON irq handler for DSI interface */
static irqreturn_t decon_irq_handler(int irq, void *dev_data)
{
struct decon_device *decon = dev_data;
u32 irq_sts_reg;
u32 ext_irq = 0;
spin_lock(&decon->slock);
if (IS_DECON_OFF_STATE(decon))
goto irq_end;
irq_sts_reg = decon_reg_get_interrupt_and_clear(decon->id, &ext_irq);
decon_dbg("%s: irq_sts_reg = %x, ext_irq = %x\n", __func__,
irq_sts_reg, ext_irq);
if (irq_sts_reg & DPU_FRAME_START_INT_PEND) {
/* VSYNC interrupt, accept it */
decon->frame_cnt++;
wake_up_interruptible_all(&decon->wait_vstatus);
if (decon->state == DECON_STATE_TUI)
decon_info("%s:%d TUI Frame Start\n", __func__, __LINE__);
}
if (irq_sts_reg & DPU_FRAME_DONE_INT_PEND) {
DPU_EVENT_LOG(DPU_EVT_DECON_FRAMEDONE, &decon->sd, ktime_set(0, 0));
decon_hiber_trig_reset(decon);
if (decon->state == DECON_STATE_TUI)
decon_info("%s:%d TUI Frame Done\n", __func__, __LINE__);
}
if (ext_irq & DPU_RESOURCE_CONFLICT_INT_PEND)
DPU_EVENT_LOG(DPU_EVT_RSC_CONFLICT, &decon->sd, ktime_set(0, 0));
if (ext_irq & DPU_TIME_OUT_INT_PEND)
decon_err("%s: DECON%d timeout irq occurs\n", __func__, decon->id);
irq_end:
spin_unlock(&decon->slock);
return IRQ_HANDLED;
}
#ifdef CONFIG_EXYNOS_WD_DVFS
static int decon_devfreq_change_task(void *data)
{
while (!kthread_should_stop()) {
set_current_state(TASK_INTERRUPTIBLE);
schedule();
set_current_state(TASK_RUNNING);
exynos_wd_call_chain();
}
return 0;
}
#endif
int decon_register_irq(struct decon_device *decon)
{
struct device *dev = decon->dev;
struct platform_device *pdev;
struct resource *res;
int ret = 0;
pdev = container_of(dev, struct platform_device, dev);
/* 1: FRAME START */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
ret = devm_request_irq(dev, res->start, decon_irq_handler,
0, pdev->name, decon);
if (ret) {
decon_err("failed to install FRAME START irq\n");
return ret;
}
/* 2: FRAME DONE */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
ret = devm_request_irq(dev, res->start, decon_irq_handler,
0, pdev->name, decon);
if (ret) {
decon_err("failed to install FRAME DONE irq\n");
return ret;
}
/* 3: EXTRA: resource conflict, timeout and error irq */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 2);
ret = devm_request_irq(dev, res->start, decon_irq_handler,
0, pdev->name, decon);
if (ret) {
decon_err("failed to install EXTRA irq\n");
return ret;
}
/*
* If below IRQs are needed, please define irq number sequence
* like below
*
* DECON0
* 4: DIMMING_START
* 5: DIMMING_END
* 6: DQE_DIMMING_START
* 7: DQE_DIMMING_END
*
* DECON2
* 4: VSTATUS
*/
return ret;
}
int decon_get_clocks(struct decon_device *decon)
{
return 0;
}
void decon_set_clocks(struct decon_device *decon)
{
}
int decon_get_out_sd(struct decon_device *decon)
{
decon->out_sd[0] = decon->dsim_sd[decon->dt.out_idx[0]];
if (IS_ERR_OR_NULL(decon->out_sd[0])) {
decon_err("failed to get dsim%d sd\n", decon->dt.out_idx[0]);
return -ENOMEM;
}
if (decon->dt.dsi_mode == DSI_MODE_DUAL_DSI) {
decon->out_sd[1] = decon->dsim_sd[decon->dt.out_idx[1]];
if (IS_ERR_OR_NULL(decon->out_sd[1])) {
decon_err("failed to get 2nd dsim%d sd\n",
decon->dt.out_idx[1]);
return -ENOMEM;
}
}
v4l2_subdev_call(decon->out_sd[0], core, ioctl, DSIM_IOC_GET_LCD_INFO, NULL);
decon->lcd_info =
(struct decon_lcd *)v4l2_get_subdev_hostdata(decon->out_sd[0]);
if (IS_ERR_OR_NULL(decon->lcd_info)) {
decon_err("failed to get lcd information\n");
return -EINVAL;
}
decon_info("lcd_info: hfp %d hbp %d hsa %d vfp %d vbp %d vsa %d",
decon->lcd_info->hfp, decon->lcd_info->hbp,
decon->lcd_info->hsa, decon->lcd_info->vfp,
decon->lcd_info->vbp, decon->lcd_info->vsa);
decon_info("xres %d yres %d\n",
decon->lcd_info->xres, decon->lcd_info->yres);
return 0;
}
int decon_get_pinctrl(struct decon_device *decon)
{
int ret = 0;
if ((decon->dt.out_type != DECON_OUT_DSI) ||
(decon->dt.psr_mode == DECON_VIDEO_MODE) ||
(decon->dt.trig_mode != DECON_HW_TRIG)) {
decon_warn("decon%d doesn't need pinctrl\n", decon->id);
return 0;
}
decon->res.pinctrl = devm_pinctrl_get(decon->dev);
if (IS_ERR(decon->res.pinctrl)) {
decon_err("failed to get decon-%d pinctrl\n", decon->id);
ret = PTR_ERR(decon->res.pinctrl);
decon->res.pinctrl = NULL;
goto err;
}
decon->res.hw_te_on = pinctrl_lookup_state(decon->res.pinctrl, "hw_te_on");
if (IS_ERR(decon->res.hw_te_on)) {
decon_err("failed to get hw_te_on pin state\n");
ret = PTR_ERR(decon->res.hw_te_on);
decon->res.hw_te_on = NULL;
goto err;
}
decon->res.hw_te_off = pinctrl_lookup_state(decon->res.pinctrl, "hw_te_off");
if (IS_ERR(decon->res.hw_te_off)) {
decon_err("failed to get hw_te_off pin state\n");
ret = PTR_ERR(decon->res.hw_te_off);
decon->res.hw_te_off = NULL;
goto err;
}
err:
return ret;
}
static irqreturn_t decon_ext_irq_handler(int irq, void *dev_id)
{
struct decon_device *decon = dev_id;
struct decon_mode_info psr;
ktime_t timestamp = ktime_get();
decon_systrace(decon, 'C', "decon_te_signal", 1);
DPU_EVENT_LOG(DPU_EVT_TE_INTERRUPT, &decon->sd, timestamp);
spin_lock(&decon->slock);
if (decon->dt.trig_mode == DECON_SW_TRIG) {
decon_to_psr_info(decon, &psr);
decon_reg_set_trigger(decon->id, &psr, DECON_TRIG_ENABLE);
}
if (decon->hiber.enabled && decon->state == DECON_STATE_ON &&
decon->dt.out_type == DECON_OUT_DSI) {
if (decon_min_lock_cond(decon)) {
if (list_empty(&decon->hiber.worker.work_list)) {
atomic_inc(&decon->hiber.remaining_hiber);
kthread_queue_work(&decon->hiber.worker, &decon->hiber.work);
}
}
}
decon_systrace(decon, 'C', "decon_te_signal", 0);
decon->vsync.timestamp = timestamp;
wake_up_interruptible_all(&decon->vsync.wait);
spin_unlock(&decon->slock);
#ifdef CONFIG_EXYNOS_WD_DVFS
if (devfreq_change_task)
wake_up_process(devfreq_change_task);
#endif
return IRQ_HANDLED;
}
int decon_register_ext_irq(struct decon_device *decon)
{
struct device *dev = decon->dev;
struct platform_device *pdev;
int gpio = -EINVAL, gpio1 = -EINVAL;
int ret = 0;
pdev = container_of(dev, struct platform_device, dev);
/* Get IRQ resource and register IRQ handler. */
if (of_get_property(dev->of_node, "gpios", NULL) != NULL) {
gpio = of_get_gpio(dev->of_node, 0);
if (gpio < 0) {
decon_err("failed to get proper gpio number\n");
return -EINVAL;
}
gpio1 = of_get_gpio(dev->of_node, 1);
if (gpio1 < 0)
decon_info("This board doesn't support TE GPIO of 2nd LCD\n");
} else {
decon_err("failed to find gpio node from device tree\n");
return -EINVAL;
}
decon->res.irq = gpio_to_irq(gpio);
decon_info("%s: gpio(%d)\n", __func__, decon->res.irq);
ret = devm_request_irq(dev, decon->res.irq, decon_ext_irq_handler,
IRQF_TRIGGER_RISING, pdev->name, decon);
decon->eint_status = 1;
#ifdef CONFIG_EXYNOS_WD_DVFS
devfreq_change_task =
kthread_create(decon_devfreq_change_task, NULL,
"devfreq_change");
if (IS_ERR(devfreq_change_task))
return PTR_ERR(devfreq_change_task);
#endif
return ret;
}
static ssize_t decon_show_vsync(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct decon_device *decon = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%llu\n",
ktime_to_ns(decon->vsync.timestamp));
}
static DEVICE_ATTR(vsync, S_IRUGO, decon_show_vsync, NULL);
static int decon_vsync_thread(void *data)
{
struct decon_device *decon = data;
while (!kthread_should_stop()) {
ktime_t timestamp = decon->vsync.timestamp;
#if defined(CONFIG_SUPPORT_KERNEL_4_9)
int ret = wait_event_interruptible(decon->vsync.wait,
!ktime_equal(timestamp, decon->vsync.timestamp) &&
decon->vsync.active);
#else
int ret = wait_event_interruptible(decon->vsync.wait,
(timestamp != decon->vsync.timestamp) &&
decon->vsync.active);
#endif
if (!ret)
sysfs_notify(&decon->dev->kobj, NULL, "vsync");
}
return 0;
}
int decon_create_vsync_thread(struct decon_device *decon)
{
int ret = 0;
char name[16];
if (decon->dt.out_type != DECON_OUT_DSI) {
decon_info("vsync thread is only needed for DSI path\n");
return 0;
}
ret = device_create_file(decon->dev, &dev_attr_vsync);
if (ret) {
decon_err("failed to create vsync file\n");
return ret;
}
sprintf(name, "decon%d-vsync", decon->id);
decon->vsync.thread = kthread_run(decon_vsync_thread, decon, name);
if (IS_ERR_OR_NULL(decon->vsync.thread)) {
decon_err("failed to run vsync thread\n");
decon->vsync.thread = NULL;
ret = PTR_ERR(decon->vsync.thread);
goto err;
}
return 0;
err:
device_remove_file(decon->dev, &dev_attr_vsync);
return ret;
}
void decon_destroy_vsync_thread(struct decon_device *decon)
{
device_remove_file(decon->dev, &dev_attr_vsync);
if (decon->vsync.thread)
kthread_stop(decon->vsync.thread);
}
#if defined(CONFIG_EXYNOS_READ_ESD_SOLUTION)
#define ESD_RECOVERY_RETRY_CNT 5
static int decon_handle_esd(struct decon_device *decon)
{
struct dsim_device *dsim;
int ret = 0;
int retry = 0;
int status = 0;
decon_info("%s +\n", __func__);
if (decon == NULL) {
decon_warn("%s invalid param\n", __func__);
return -EINVAL;
}
decon_bypass_on(decon);
dsim = container_of(decon->out_sd[0], struct dsim_device, sd);
dsim->esd_recovering = true;
while (++retry <= ESD_RECOVERY_RETRY_CNT) {
decon_warn("%s try recovery(%d times)\n",
__func__, retry);
ret = decon_update_pwr_state(decon, DISP_PWR_OFF);
if (ret < 0) {
decon_err("%s decon-%d failed to set subdev OFF state\n",
__func__, decon->id);
continue;
}
ret = decon_update_pwr_state(decon, DISP_PWR_NORMAL);
if (ret < 0) {
decon_err("%s decon-%d failed to set subdev ON state\n",
__func__, decon->id);
continue;
}
#if defined(READ_ESD_SOLUTION_TEST)
status = DSIM_ESD_OK;
#else
status = call_panel_ops(dsim, read_state, dsim);
#endif
if (status != DSIM_ESD_OK) {
decon_err("%s failed to recover subdev(status %d)\n",
__func__, status);
continue;
}
DPU_FULL_RECT(&decon->last_regs.up_region, decon->lcd_info);
decon->last_regs.need_update = true;
ret = decon_update_last_regs(decon, &decon->last_regs);
if (ret < 0) {
decon_err("%s failed to update last image(ret %d)\n",
__func__, ret);
continue;
}
decon_info("%s recovery successfully(retry %d times)\n",
__func__, retry);
break;
}
if (retry > ESD_RECOVERY_RETRY_CNT) {
decon_err("DECON:ERR:%s:failed to recover(retry %d times)\n",
__func__, ESD_RECOVERY_RETRY_CNT);
decon_dump(decon);
if (decon->dt.out_type == DECON_OUT_DSI)
v4l2_subdev_call(decon->out_sd[0], core, ioctl,
DSIM_IOC_DUMP, NULL);
BUG();
}
dsim->esd_recovering = false;
decon_bypass_off(decon);
decon_info("%s -\n", __func__);
return ret;
}
static void decon_esd_process(int esd, struct decon_device *decon)
{
int ret;
switch (esd) {
case DSIM_ESD_CHECK_ERROR:
decon_err("%s, It is not ESD, \
but DDI is abnormal state(%d)\n", __func__, esd);
break;
case DSIM_ESD_OK:
decon_info("%s, DDI has normal state(%d)\n", __func__, esd);
break;
case DSIM_ESD_ERROR:
decon_err("%s, ESD is detected(%d)\n", __func__, esd);
ret = decon_handle_esd(decon);
if (ret)
decon_err("%s, failed to recover ESD\n", __func__);
break;
default:
decon_err("%s, Not supported value(%d)\n", __func__, esd);
break;
}
}
static int decon_esd_thread(void *data)
{
struct decon_device *decon = data;
struct dsim_device *dsim = NULL;
int esd = 0;
while (!kthread_should_stop()) {
/* Loop for ESD detection */
if (decon->state == DECON_STATE_OFF) {
/* go to sleep when decon is not ready */
decon_info("%s, Sleep \n", __func__);
set_current_state(TASK_INTERRUPTIBLE);
schedule();
set_current_state(TASK_RUNNING);
continue;
}
decon_hiber_block_exit(decon);
if (decon->state == DECON_STATE_ON) {
mutex_lock(&decon->esd.lock);
dsim = container_of(decon->out_sd[0],
struct dsim_device, sd);
decon_info("%s, Try to check ESD\n", __func__);
esd = call_panel_ops(dsim, read_state, dsim);
decon_esd_process(esd, decon);
mutex_unlock(&decon->esd.lock);
}
decon_hiber_unblock(decon);
/* sleep ESD_SLEEP_TIME second when decon is not state on
* and after read DDI state
*/
decon_info("%s, Sleep %d second\n", __func__, ESD_SLEEP_TIME);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(ESD_SLEEP_TIME * HZ);
}
/* when if kthread_should_stop() return true */
return 0;
}
int decon_create_esd_thread(struct decon_device *decon)
{
int ret = 0;
char name[16];
if (decon->dt.out_type != DECON_OUT_DSI) {
decon_info("esd thread is only needed for DSI path\n");
return 0;
}
sprintf(name, "decon%d-esd", decon->id);
decon->esd.thread = kthread_run(decon_esd_thread, decon, name);
if (IS_ERR_OR_NULL(decon->esd.thread)) {
decon_err("failed to run esd thread\n");
decon->esd.thread = NULL;
ret = PTR_ERR(decon->esd.thread);
}
return ret;
}
void decon_destroy_esd_thread(struct decon_device *decon)
{
if (decon->esd.thread)
kthread_stop(decon->esd.thread);
}
#endif /* CONFIG_EXYNOS_READ_ESD_SOLUTION */
/*
* Variable Descriptions
* dsc_en : comp_mode (0=No Comp, 1=DSC, 2=MIC, 3=LEGO)
* dsc_width : min_window_update_width depending on compression mode
* dsc_height : min_window_update_height depending on compression mode
*/
static ssize_t decon_show_psr_info(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct decon_device *decon = dev_get_drvdata(dev);
struct decon_lcd *lcd_info = decon->lcd_info;
int i;
char *p = buf;
struct lcd_mres_info *mres_info = &lcd_info->dt_lcd_mres;
int len;
struct v4l2_subdev *sd;
struct dpp_restriction res;
int sz_align = 1;
sd = decon->dpp_sd[0];
v4l2_subdev_call(sd, core, ioctl, DPP_GET_RESTRICTION, &res);
len = sprintf(p, "%d\n", decon->dt.psr_mode);
len += sprintf(p + len, "%d\n", mres_info->mres_number);
for (i = 0; i < mres_info->mres_number; i++) {
if (mres_info->res_info[i].dsc_en)
len += sprintf(p + len, "%d\n%d\n%d\n%d\n%d\n",
mres_info->res_info[i].width,
mres_info->res_info[i].height,
mres_info->res_info[i].dsc_width,
mres_info->res_info[i].dsc_height,
mres_info->res_info[i].dsc_en);
else
len += sprintf(p + len, "%d\n%d\n%d\n%d\n%d\n",
mres_info->res_info[i].width,
mres_info->res_info[i].height,
max((res.src_f_w.min * sz_align), lcd_info->update_min_w),
max((res.src_f_h.min * sz_align), lcd_info->update_min_h),
mres_info->res_info[i].dsc_en);
}
return len;
}
static DEVICE_ATTR(psr_info, S_IRUGO, decon_show_psr_info, NULL);
int decon_create_psr_info(struct decon_device *decon)
{
int ret = 0;
/* It's enought to make a file for PSR information */
if (decon->id != 0)
return 0;
ret = device_create_file(decon->dev, &dev_attr_psr_info);
if (ret) {
decon_err("failed to create psr info file\n");
return ret;
}
return ret;
}
void decon_destroy_psr_info(struct decon_device *decon)
{
device_remove_file(decon->dev, &dev_attr_psr_info);
}
/* Framebuffer interface related callback functions */
static u32 fb_visual(u32 bits_per_pixel, unsigned short palette_sz)
{
switch (bits_per_pixel) {
case 32:
case 24:
case 16:
case 12:
return FB_VISUAL_TRUECOLOR;
case 8:
if (palette_sz >= 256)
return FB_VISUAL_PSEUDOCOLOR;
else
return FB_VISUAL_TRUECOLOR;
case 1:
return FB_VISUAL_MONO01;
default:
return FB_VISUAL_PSEUDOCOLOR;
}
}
static inline u32 fb_linelength(u32 xres_virtual, u32 bits_per_pixel)
{
return (xres_virtual * bits_per_pixel) / 8;
}
static u16 fb_panstep(u32 res, u32 res_virtual)
{
return res_virtual > res ? 1 : 0;
}
int decon_set_par(struct fb_info *info)
{
struct fb_var_screeninfo *var = &info->var;
struct decon_win *win = info->par;
struct decon_device *decon = win->decon;
struct decon_window_regs win_regs;
int win_no = win->idx;
if ((!IS_DECON_HIBER_STATE(decon) && IS_DECON_OFF_STATE(decon)) ||
decon->state == DECON_STATE_INIT)
return 0;
memset(&win_regs, 0, sizeof(struct decon_window_regs));
decon_hiber_block_exit(decon);
decon_reg_wait_update_done_timeout(decon->id, SHADOW_UPDATE_TIMEOUT);
info->fix.visual = fb_visual(var->bits_per_pixel, 0);
info->fix.line_length = fb_linelength(var->xres_virtual,
var->bits_per_pixel);
info->fix.xpanstep = fb_panstep(var->xres, var->xres_virtual);
info->fix.ypanstep = fb_panstep(var->yres, var->yres_virtual);
win_regs.wincon |= wincon(var->transp.length, 0, 0xFF,
0xFF, DECON_BLENDING_NONE, win_no);
win_regs.start_pos = win_start_pos(0, 0);
win_regs.end_pos = win_end_pos(0, 0, var->xres, var->yres);
win_regs.pixel_count = (var->xres * var->yres);
win_regs.whole_w = var->xoffset + var->xres;
win_regs.whole_h = var->yoffset + var->yres;
win_regs.offset_x = var->xoffset;
win_regs.offset_y = var->yoffset;
win_regs.type = decon->dt.dft_idma;
decon_reg_set_window_control(decon->id, win_no, &win_regs, false);
/* decon_reg_all_win_shadow_update_req(decon->id); */
decon_hiber_unblock(decon);
return 0;
}
EXPORT_SYMBOL(decon_set_par);
int decon_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct decon_win *win = info->par;
struct decon_device *decon = win->decon;
var->xres_virtual = max(var->xres_virtual, var->xres);
var->yres_virtual = max(var->yres_virtual, var->yres);
if (!decon_validate_x_alignment(decon, 0, var->xres,
var->bits_per_pixel))
return -EINVAL;
/* always ensure these are zero, for drop through cases below */
var->transp.offset = 0;
var->transp.length = 0;
switch (var->bits_per_pixel) {
case 1:
case 2:
case 4:
case 8:
var->red.offset = 4;
var->green.offset = 2;
var->blue.offset = 0;
var->red.length = 5;
var->green.length = 3;
var->blue.length = 2;
var->transp.offset = 7;
var->transp.length = 1;
break;
case 19:
/* 666 with one bit alpha/transparency */
var->transp.offset = 18;
var->transp.length = 1;
case 18:
var->bits_per_pixel = 32;
/* 666 format */
var->red.offset = 12;
var->green.offset = 6;
var->blue.offset = 0;
var->red.length = 6;
var->green.length = 6;
var->blue.length = 6;
break;
case 16:
/* 16 bpp, 565 format */
var->red.offset = 11;
var->green.offset = 5;
var->blue.offset = 0;
var->red.length = 5;
var->green.length = 6;
var->blue.length = 5;
break;
case 32:
case 28:
case 25:
var->transp.length = var->bits_per_pixel - 24;
var->transp.offset = 24;
/* drop through */
case 24:
/* our 24bpp is unpacked, so 32bpp */
var->bits_per_pixel = 32;
var->red.offset = 16;
var->red.length = 8;
var->green.offset = 8;
var->green.length = 8;
var->blue.offset = 0;
var->blue.length = 8;
break;
default:
decon_err("invalid bpp %d\n", var->bits_per_pixel);
return -EINVAL;
}
decon_dbg("xres:%d, yres:%d, v_xres:%d, v_yres:%d, bpp:%d\n",
var->xres, var->yres, var->xres_virtual,
var->yres_virtual, var->bits_per_pixel);
return 0;
}
static inline unsigned int chan_to_field(unsigned int chan,
struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
int decon_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
struct decon_win *win = info->par;
struct decon_device *decon = win->decon;
unsigned int val;
printk("@@@@ %s\n", __func__);
decon_dbg("%s: win %d: %d => rgb=%d/%d/%d\n", __func__, win->idx,
regno, red, green, blue);
if (IS_DECON_OFF_STATE(decon))
return 0;
switch (info->fix.visual) {
case FB_VISUAL_TRUECOLOR:
/* true-colour, use pseudo-palette */
if (regno < 16) {
u32 *pal = info->pseudo_palette;
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pal[regno] = val;
}
break;
default:
return 1; /* unknown type */
}
return 0;
}
EXPORT_SYMBOL(decon_setcolreg);
int decon_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct decon_win *win = info->par;
struct decon_device *decon = win->decon;
struct v4l2_subdev *sd = NULL;
struct decon_win_config config;
int ret = 0;
struct decon_mode_info psr;
int dpp_id = DPU_DMA2CH(decon->dt.dft_idma);
if (decon->dt.out_type != DECON_OUT_DSI) {
decon_warn("%s: decon%d unspported on out_type(%d)\n",
__func__, decon->id, decon->dt.out_type);
return 0;
}
if ((!IS_DECON_HIBER_STATE(decon) && IS_DECON_OFF_STATE(decon)) ||
decon->state == DECON_STATE_INIT) {
decon_warn("%s: decon%d state(%d), UNBLANK missed\n",
__func__, decon->id, decon->state);
return 0;
}
decon_info("%s: [%d %d %d %d %d %d]\n", __func__,
var->xoffset, var->yoffset,
var->xres, var->yres,
var->xres_virtual, var->yres_virtual);
memset(&config, 0, sizeof(struct decon_win_config));
switch (var->bits_per_pixel) {
case 16:
config.format = DECON_PIXEL_FORMAT_RGB_565;
break;
case 24:
case 32:
config.format = DECON_PIXEL_FORMAT_ABGR_8888; /* DECON_PIXEL_FORMAT_BGRA_8888; */
break;
default:
decon_err("%s: Not supported bpp %d\n", __func__,
var->bits_per_pixel);
return -EINVAL;
}
config.dpp_parm.addr[0] = info->fix.smem_start;
config.src.x = var->xoffset;
config.src.y = var->yoffset;
config.src.w = var->xres;
config.src.h = var->yres;
config.src.f_w = var->xres_virtual;
config.src.f_h = var->yres_virtual;
config.dst.w = config.src.w;
config.dst.h = config.src.h;
config.dst.f_w = decon->lcd_info->xres;
config.dst.f_h = decon->lcd_info->yres;
if (decon_check_limitation(decon, decon->dt.dft_win, &config) < 0)
return -EINVAL;
decon_hiber_block_exit(decon);
decon_to_psr_info(decon, &psr);
/*
* info->var is old parameters and var is new requested parameters.
* var must be copied to info->var before decon_set_par function
* is called.
*
* If not, old parameters are set to window configuration
* and new parameters are set to DMA and DPP configuration.
*/
memcpy(&info->var, var, sizeof(struct fb_var_screeninfo));
set_bit(dpp_id, &decon->cur_using_dpp);
set_bit(dpp_id, &decon->prev_used_dpp);
sd = decon->dpp_sd[dpp_id];
if (v4l2_subdev_call(sd, core, ioctl, DPP_WIN_CONFIG, &config)) {
decon_err("%s: Failed to config DPP-%d\n", __func__, win->dpp_id);
decon_reg_win_enable_and_update(decon->id, decon->dt.dft_win, false);
clear_bit(dpp_id, &decon->cur_using_dpp);
set_bit(dpp_id, &decon->dpp_err_stat);
goto err;
}
decon_reg_update_req_window(decon->id, win->idx);
#if 0
decon_set_par(info);
#endif
decon_reg_start(decon->id, &psr);
err:
decon_wait_for_vsync(decon, VSYNC_TIMEOUT_MSEC);
if (decon_reg_wait_update_done_and_mask(decon->id, &psr, SHADOW_UPDATE_TIMEOUT)
< 0)
decon_err("%s: wait_for_update_timeout\n", __func__);
decon_hiber_unblock(decon);
return ret;
}
EXPORT_SYMBOL(decon_pan_display);
int decon_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
int ret;
struct decon_win *win = info->par;
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
#if defined(CONFIG_FB_TEST)
ret = dma_buf_mmap(win->fb_buf_data.dma_buf, vma, 0);
#else
ret = dma_buf_mmap(win->dma_buf_data[0].dma_buf, vma, 0);
#endif
return ret;
}
EXPORT_SYMBOL(decon_mmap);
int decon_exit_hiber(struct decon_device *decon)
{
int ret = 0;
struct decon_param p;
struct decon_mode_info psr;
enum decon_state prev_state = decon->state;
DPU_EVENT_START();
if (!decon->hiber.enabled)
return 0;
decon_hiber_block(decon);
if (atomic_read(&decon->hiber.remaining_hiber))
kthread_flush_worker(&decon->hiber.worker);
mutex_lock(&decon->hiber.lock);
if (decon->state != DECON_STATE_HIBER)
goto err;
decon_dbg("enable decon-%d\n", decon->id);
ret = decon_set_out_sd_state(decon, DECON_STATE_ON);
if (ret < 0) {
decon_err("%s decon-%d failed to set subdev EXIT_ULPS state\n",
__func__, decon->id);
}
decon_to_init_param(decon, &p);
decon_reg_init(decon->id, decon->dt.out_idx[0], &p);
/*
* After hibernation exit, If panel is partial size, DECON and DSIM
* are also set as same partial size.
*/
if (!is_full(&decon->win_up.prev_up_region, decon->lcd_info))
dpu_set_win_update_partial_size(decon, &decon->win_up.prev_up_region);
if (!decon->id && !decon->eint_status) {
struct irq_desc *desc = irq_to_desc(decon->res.irq);
/* Pending IRQ clear */
if ((!IS_ERR_OR_NULL(desc)) && (desc->irq_data.chip->irq_ack)) {
desc->irq_data.chip->irq_ack(&desc->irq_data);
desc->istate &= ~IRQS_PENDING;
}
enable_irq(decon->res.irq);
decon->eint_status = 1;
}
decon->state = DECON_STATE_ON;
decon_to_psr_info(decon, &psr);
decon_reg_set_int(decon->id, &psr, 1);
decon_hiber_trig_reset(decon);
decon_dbg("decon-%d %s - (state:%d -> %d)\n",
decon->id, __func__, prev_state, decon->state);
decon->hiber.exit_cnt++;
DPU_EVENT_LOG(DPU_EVT_EXIT_HIBER, &decon->sd, start);
err:
decon_hiber_unblock(decon);
mutex_unlock(&decon->hiber.lock);
return ret;
}
int decon_enter_hiber(struct decon_device *decon)
{
int ret = 0;
struct decon_mode_info psr;
enum decon_state prev_state = decon->state;
DPU_EVENT_START();
if (!decon->hiber.enabled)
return 0;
mutex_lock(&decon->hiber.lock);
if (decon_is_enter_shutdown(decon))
goto err2;
if (decon_is_hiber_blocked(decon))
goto err2;
decon_hiber_block(decon);
if (decon->state != DECON_STATE_ON)
goto err;
decon_dbg("decon-%d %s +\n", decon->id, __func__);
decon_hiber_trig_reset(decon);
kthread_flush_worker(&decon->up.worker);
decon_to_psr_info(decon, &psr);
decon_reg_set_int(decon->id, &psr, 0);
if (!decon->id && (decon->vsync.irq_refcount <= 0) &&
decon->eint_status) {
disable_irq(decon->res.irq);
decon->eint_status = 0;
}
ret = decon_reg_stop(decon->id, decon->dt.out_idx[0], &psr, true,
decon->lcd_info->fps);
if (ret < 0)
decon_dump(decon);
/* DMA protection disable must be happen on dpp domain is alive */
if (decon->dt.out_type != DECON_OUT_WB) {
#if defined(CONFIG_EXYNOS_CONTENT_PATH_PROTECTION)
decon_set_protected_content(decon, NULL);
#endif
decon->cur_using_dpp = 0;
decon_dpp_stop(decon, false);
}
#if defined(CONFIG_EXYNOS_BTS)
decon->bts.ops->bts_release_bw(decon);
#endif
ret = decon_set_out_sd_state(decon, DECON_STATE_HIBER);
if (ret < 0)
decon_err("%s decon-%d failed to set subdev ENTER_ULPS state\n",
__func__, decon->id);
decon->state = DECON_STATE_HIBER;
decon->hiber.enter_cnt++;
DPU_EVENT_LOG(DPU_EVT_ENTER_HIBER, &decon->sd, start);
err:
decon_hiber_unblock(decon);
err2:
mutex_unlock(&decon->hiber.lock);
decon_dbg("decon-%d %s - (state:%d -> %d)\n",
decon->id, __func__, prev_state, decon->state);
return ret;
}
int decon_hiber_block_exit(struct decon_device *decon)
{
int ret = 0;
if (!decon || !decon->hiber.enabled)
return 0;
decon_hiber_block(decon);
ret = decon_exit_hiber(decon);
return ret;
}
static void decon_hiber_handler(struct kthread_work *work)
{
struct decon_hiber *hiber =
container_of(work, struct decon_hiber, work);
struct decon_device *decon =
container_of(hiber, struct decon_device, hiber);
if (!decon || !decon->hiber.enabled) {
atomic_dec(&decon->hiber.remaining_hiber);
return;
}
if (decon_hiber_enter_cond(decon))
decon_enter_hiber(decon);
atomic_dec(&decon->hiber.remaining_hiber);
}
int decon_register_hiber_work(struct decon_device *decon)
{
struct sched_param param;
decon->hiber.enabled = false;
if (!IS_ENABLED(CONFIG_EXYNOS_HIBERNATION)) {
decon_info("display doesn't support hibernation mode\n");
return 0;
}
mutex_init(&decon->hiber.lock);
atomic_set(&decon->hiber.trig_cnt, 0);
atomic_set(&decon->hiber.block_cnt, 0);
kthread_init_worker(&decon->hiber.worker);
decon->hiber.thread = kthread_run(kthread_worker_fn,
&decon->hiber.worker, "decon_hiber");
if (IS_ERR(decon->hiber.thread)) {
decon->hiber.thread = NULL;
decon_err("failed to run hibernation thread\n");
return PTR_ERR(decon->hiber.thread);
}
param.sched_priority = 20;
sched_setscheduler_nocheck(decon->hiber.thread, SCHED_FIFO, &param);
kthread_init_work(&decon->hiber.work, decon_hiber_handler);
decon->hiber.enabled = true;
decon_info("display supports hibernation mode\n");
return 0;
}
void decon_init_low_persistence_mode(struct decon_device *decon)
{
decon->low_persistence = false;
if (!IS_ENABLED(CONFIG_EXYNOS_LOW_PERSISTENCE)) {
decon_info("display doesn't support low persistence mode\n");
return;
}
decon->low_persistence = true;
decon_info("display supports low persistence mode\n");
}