1166 lines
29 KiB
C
1166 lines
29 KiB
C
|
/*
|
||
|
* 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, ¶m);
|
||
|
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");
|
||
|
}
|