1585 lines
40 KiB
C
1585 lines
40 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) Samsung Electronics Co., Ltd.
|
||
|
*
|
||
|
* 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/clk-provider.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/fb.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/irq.h>
|
||
|
#include <linux/lcd.h>
|
||
|
#include <linux/list.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_address.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include <linux/pinctrl/consumer.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/pm_runtime.h>
|
||
|
#include <linux/reboot.h>
|
||
|
#include <linux/rtc.h>
|
||
|
|
||
|
#include <media/v4l2-subdev.h>
|
||
|
#include "../../../../../kernel/irq/internals.h"
|
||
|
#if defined(CONFIG_ARCH_EXYNOS)
|
||
|
#include <linux/exynos_iovmm.h>
|
||
|
#include <soc/samsung/exynos-devfreq.h>
|
||
|
#endif
|
||
|
#if defined(CONFIG_CAL_IF)
|
||
|
#include <soc/samsung/cal-if.h>
|
||
|
#endif
|
||
|
#if defined(CONFIG_SOC_EXYNOS7885)
|
||
|
#include <dt-bindings/clock/exynos7885.h>
|
||
|
#endif
|
||
|
#if defined(CONFIG_SOC_EXYNOS9610)
|
||
|
#include <dt-bindings/clock/exynos9610.h>
|
||
|
#endif
|
||
|
#if defined(CONFIG_SOC_EXYNOS9810)
|
||
|
#include <dt-bindings/clock/exynos9810.h>
|
||
|
#endif
|
||
|
#if defined(CONFIG_SOC_EXYNOS3830)
|
||
|
#include <dt-bindings/soc/samsung/exynos3830-devfreq.h>
|
||
|
#endif
|
||
|
#if defined(CONFIG_SOC_EXYNOS9630)
|
||
|
#include <dt-bindings/soc/samsung/exynos9630-devfreq.h>
|
||
|
#endif
|
||
|
#if defined(CONFIG_SEC_DEBUG)
|
||
|
#include <linux/sec_debug.h>
|
||
|
#endif
|
||
|
|
||
|
#if defined(CONFIG_ARCH_EXYNOS) && defined(CONFIG_EXYNOS_DPU20)
|
||
|
#include "decon.h"
|
||
|
#include "dpp.h"
|
||
|
#include "dsim.h"
|
||
|
#elif defined(CONFIG_ARCH_EXYNOS) && defined(CONFIG_EXYNOS_DPU30)
|
||
|
#include "../dpu30/decon.h"
|
||
|
#include "../dpu30/dpp.h"
|
||
|
#include "../dpu30/dsim.h"
|
||
|
#include "panel.h"
|
||
|
#endif
|
||
|
|
||
|
#include "decon_abd.h"
|
||
|
#include "decon_board.h"
|
||
|
#include "decon_notify.h"
|
||
|
|
||
|
#define dbg_info(fmt, ...) pr_info(pr_fmt("decon: "fmt), ##__VA_ARGS__)
|
||
|
#define dbg_none(fmt, ...) pr_debug(pr_fmt("decon: "fmt), ##__VA_ARGS__)
|
||
|
|
||
|
#define abd_printf(m, ...) \
|
||
|
{ if (m) seq_printf(m, __VA_ARGS__); else dbg_info(__VA_ARGS__); } \
|
||
|
|
||
|
#if defined(CONFIG_ARCH_EXYNOS) && defined(CONFIG_LOGGING_BIGDATA_BUG)
|
||
|
/* Gen Big Data Error for Decon's Bug
|
||
|
*
|
||
|
* return value
|
||
|
* 1. 31 ~ 28 : decon_id
|
||
|
* 2. 27 ~ 24 : decon eing pend register
|
||
|
* 3. 23 ~ 16 : dsim underrun count
|
||
|
* 4. 15 ~ 8 : 0x0e panel register
|
||
|
* 5. 7 ~ 0 : 0x0a panel register
|
||
|
*/
|
||
|
|
||
|
static unsigned int gen_decon_bug_bigdata(struct decon_device *decon)
|
||
|
{
|
||
|
struct dsim_device *dsim;
|
||
|
unsigned int value, panel_value;
|
||
|
unsigned int underrun_cnt = 0;
|
||
|
|
||
|
/* for decon id */
|
||
|
value = decon->id << 28;
|
||
|
|
||
|
if (decon->id == 0) {
|
||
|
/* for eint pend value */
|
||
|
value |= (decon->eint_pend & 0x0f) << 24;
|
||
|
|
||
|
/* for underrun count */
|
||
|
dsim = container_of(decon->out_sd[0], struct dsim_device, sd);
|
||
|
if (dsim != NULL) {
|
||
|
underrun_cnt = dsim->total_underrun_cnt;
|
||
|
if (underrun_cnt > 0xff) {
|
||
|
dbg_info("%s: underrun exceed 1byte: %d\n",
|
||
|
__func__, underrun_cnt);
|
||
|
underrun_cnt = 0xff;
|
||
|
}
|
||
|
}
|
||
|
value |= underrun_cnt << 16;
|
||
|
|
||
|
/* for panel dump */
|
||
|
panel_value = call_panel_ops(dsim, get_buginfo, dsim);
|
||
|
value |= panel_value & 0xffff;
|
||
|
}
|
||
|
|
||
|
dbg_info("%s: big data: %x\n", __func__, value);
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
void log_decon_bigdata(struct decon_device *decon)
|
||
|
{
|
||
|
unsigned int bug_err_num;
|
||
|
|
||
|
bug_err_num = gen_decon_bug_bigdata(decon);
|
||
|
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
|
||
|
sec_debug_set_extra_info_decon(bug_err_num);
|
||
|
#endif
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if defined(CONFIG_ARCH_EXYNOS)
|
||
|
struct platform_device *of_find_abd_dt_parent_platform_device(void)
|
||
|
{
|
||
|
return of_find_dsim_platform_device();
|
||
|
}
|
||
|
|
||
|
struct platform_device *of_find_abd_container_platform_device(void)
|
||
|
{
|
||
|
return of_find_decon_platform_device();
|
||
|
}
|
||
|
|
||
|
static struct decon_device *find_container(void)
|
||
|
{
|
||
|
struct platform_device *pdev = NULL;
|
||
|
struct decon_device *container = NULL;
|
||
|
|
||
|
pdev = of_find_abd_container_platform_device();
|
||
|
if (!pdev) {
|
||
|
dbg_info("%s: of_find_device_by_node fail\n", __func__);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
container = platform_get_drvdata(pdev);
|
||
|
if (!container) {
|
||
|
dbg_info("%s: platform_get_drvdata fail\n", __func__);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return container;
|
||
|
}
|
||
|
|
||
|
static inline struct decon_device *get_abd_container_of(struct abd_protect *abd)
|
||
|
{
|
||
|
struct decon_device *container = container_of(abd, struct decon_device, abd);
|
||
|
|
||
|
return container;
|
||
|
}
|
||
|
|
||
|
#if defined(CONFIG_SOC_EXYNOS7885) || defined(CONFIG_SOC_EXYNOS9610)
|
||
|
static void set_frame_bypass(struct abd_protect *abd, unsigned int bypass)
|
||
|
{
|
||
|
struct decon_device *container = get_abd_container_of(abd);
|
||
|
|
||
|
if (bypass)
|
||
|
dbg_info("%s: %d\n", __func__, bypass);
|
||
|
|
||
|
container->ignore_vsync = bypass;
|
||
|
}
|
||
|
|
||
|
static int get_frame_bypass(struct abd_protect *abd)
|
||
|
{
|
||
|
struct decon_device *container = get_abd_container_of(abd);
|
||
|
|
||
|
return container->ignore_vsync;
|
||
|
}
|
||
|
#else
|
||
|
static void set_frame_bypass(struct abd_protect *abd, unsigned int bypass)
|
||
|
{
|
||
|
struct decon_device *container = get_abd_container_of(abd);
|
||
|
|
||
|
if (bypass)
|
||
|
dbg_info("%s: %d\n", __func__, bypass);
|
||
|
|
||
|
atomic_set(&container->bypass, bypass);
|
||
|
}
|
||
|
|
||
|
static int get_frame_bypass(struct abd_protect *abd)
|
||
|
{
|
||
|
struct decon_device *container = get_abd_container_of(abd);
|
||
|
|
||
|
return atomic_read(&container->bypass);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if defined(CONFIG_EXYNOS_DPU20)
|
||
|
static void set_mipi_rw_bypass(struct abd_protect *abd, unsigned int flag)
|
||
|
{
|
||
|
struct decon_device *container = get_abd_container_of(abd);
|
||
|
struct dsim_device *dsim = v4l2_get_subdevdata(container->out_sd[0]);
|
||
|
|
||
|
dsim->priv.lcdconnected = !flag;
|
||
|
}
|
||
|
|
||
|
static int get_mipi_rw_bypass(struct abd_protect *abd)
|
||
|
{
|
||
|
struct decon_device *container = get_abd_container_of(abd);
|
||
|
struct dsim_device *dsim = v4l2_get_subdevdata(container->out_sd[0]);
|
||
|
|
||
|
return !dsim->priv.lcdconnected;
|
||
|
}
|
||
|
|
||
|
static inline int get_boot_lcdtype(void)
|
||
|
{
|
||
|
return (int)lcdtype;
|
||
|
}
|
||
|
|
||
|
static inline unsigned int get_boot_lcdconnected(void)
|
||
|
{
|
||
|
return get_boot_lcdtype() ? 1 : 0;
|
||
|
}
|
||
|
#else
|
||
|
static void set_mipi_rw_bypass(struct abd_protect *abd, unsigned int flag)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
static int get_mipi_rw_bypass(struct abd_protect *abd)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static inline int get_boot_lcdtype(void)
|
||
|
{
|
||
|
return boot_panel_id;
|
||
|
}
|
||
|
|
||
|
static inline unsigned int get_boot_lcdconnected(void)
|
||
|
{
|
||
|
return (get_boot_lcdtype() >= 0) ? 1 : 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static struct fb_info *get_fbinfo(struct abd_protect *abd)
|
||
|
{
|
||
|
struct decon_device *container = get_abd_container_of(abd);
|
||
|
|
||
|
return container->win[container->dt.dft_win]->fbinfo;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
void decon_abd_save_str(struct abd_protect *abd, const char *print)
|
||
|
{
|
||
|
struct abd_str *event = NULL;
|
||
|
struct str_log *event_log = NULL;
|
||
|
|
||
|
if (!abd || !abd->init_done)
|
||
|
return;
|
||
|
|
||
|
event = &abd->s_event;
|
||
|
event_log = &event->log[(event->count % ABD_LOG_MAX)];
|
||
|
|
||
|
event_log->stamp = local_clock();
|
||
|
event_log->ktime = ktime_get_real_seconds();
|
||
|
event_log->print = print;
|
||
|
|
||
|
event->count++;
|
||
|
|
||
|
abd_printf(NULL, "%s\n", print);
|
||
|
}
|
||
|
|
||
|
static void _decon_abd_print_bit(struct seq_file *m, struct bit_log *log)
|
||
|
{
|
||
|
struct timeval tv;
|
||
|
struct rtc_time tm;
|
||
|
unsigned int bit = 0;
|
||
|
char print_buf[200] = {0, };
|
||
|
struct seq_file p = {
|
||
|
.buf = print_buf,
|
||
|
.size = sizeof(print_buf) - 1,
|
||
|
};
|
||
|
|
||
|
if (!m)
|
||
|
seq_puts(&p, "decon_abd: ");
|
||
|
|
||
|
tv = ns_to_timeval(log->stamp);
|
||
|
rtc_time_to_tm(log->ktime, &tm);
|
||
|
seq_printf(&p, "%d-%02d-%02d %02d:%02d:%02d / %lu.%06lu / 0x%0*X, ",
|
||
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||
|
(unsigned long)tv.tv_sec, tv.tv_usec, log->size >> 2, log->value);
|
||
|
|
||
|
for (bit = 0; bit < log->size; bit++) {
|
||
|
if (log->print[bit]) {
|
||
|
if (!bit || !log->print[bit - 1] || strcmp(log->print[bit - 1], log->print[bit]))
|
||
|
seq_printf(&p, "%s, ", log->print[bit]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
abd_printf(m, "%s\n", p.count ? p.buf : "");
|
||
|
}
|
||
|
|
||
|
void decon_abd_save_bit(struct abd_protect *abd, unsigned int size, unsigned int value, char **print)
|
||
|
{
|
||
|
struct abd_bit *first = NULL;
|
||
|
struct abd_bit *event = NULL;
|
||
|
|
||
|
struct bit_log *first_log = NULL;
|
||
|
struct bit_log *event_log = NULL;
|
||
|
|
||
|
if (!abd || !abd->init_done)
|
||
|
return;
|
||
|
|
||
|
first = &abd->b_first;
|
||
|
event = &abd->b_event;
|
||
|
|
||
|
first_log = &first->log[(first->count % ABD_LOG_MAX)];
|
||
|
event_log = &event->log[(event->count % ABD_LOG_MAX)];
|
||
|
|
||
|
memset(event_log, 0, sizeof(struct bit_log));
|
||
|
event_log->stamp = local_clock();
|
||
|
event_log->ktime = ktime_get_real_seconds();
|
||
|
event_log->value = value;
|
||
|
event_log->size = size;
|
||
|
memcpy(&event_log->print, print, sizeof(char *) * size);
|
||
|
|
||
|
if (!first->count) {
|
||
|
memset(first_log, 0, sizeof(struct bit_log));
|
||
|
memcpy(first_log, event_log, sizeof(struct bit_log));
|
||
|
first->count++;
|
||
|
}
|
||
|
|
||
|
_decon_abd_print_bit(NULL, event_log);
|
||
|
|
||
|
event->count++;
|
||
|
}
|
||
|
|
||
|
void decon_abd_save_fto(struct abd_protect *abd, void *fence)
|
||
|
{
|
||
|
struct abd_fto *first = NULL;
|
||
|
struct abd_fto *lcdon = NULL;
|
||
|
struct abd_fto *event = NULL;
|
||
|
|
||
|
struct fto_log *first_log = NULL;
|
||
|
struct fto_log *lcdon_log = NULL;
|
||
|
struct fto_log *event_log = NULL;
|
||
|
|
||
|
if (!abd || !abd->init_done)
|
||
|
return;
|
||
|
|
||
|
first = &abd->f_first;
|
||
|
lcdon = &abd->f_lcdon;
|
||
|
event = &abd->f_event;
|
||
|
|
||
|
first_log = &first->log[(first->count % ABD_LOG_MAX)];
|
||
|
lcdon_log = &lcdon->log[(lcdon->count % ABD_LOG_MAX)];
|
||
|
event_log = &event->log[(event->count % ABD_LOG_MAX)];
|
||
|
|
||
|
memset(event_log, 0, sizeof(struct fto_log));
|
||
|
event_log->stamp = local_clock();
|
||
|
event_log->ktime = ktime_get_real_seconds();
|
||
|
#if defined(CONFIG_SOC_EXYNOS7885)
|
||
|
memcpy(&event_log->fence, fence, sizeof(struct sync_fence));
|
||
|
#elif defined(CONFIG_SOC_EXYNOS9810)
|
||
|
memcpy(&event_log->fence, fence, sizeof(struct sync_file));
|
||
|
#else
|
||
|
memcpy(&event_log->fence, fence, sizeof(struct dma_fence));
|
||
|
#endif
|
||
|
|
||
|
if (!first->count) {
|
||
|
memset(first_log, 0, sizeof(struct fto_log));
|
||
|
memcpy(first_log, event_log, sizeof(struct fto_log));
|
||
|
first->count++;
|
||
|
}
|
||
|
|
||
|
if (!lcdon->lcdon_flag) {
|
||
|
memset(lcdon_log, 0, sizeof(struct fto_log));
|
||
|
memcpy(lcdon_log, event_log, sizeof(struct fto_log));
|
||
|
lcdon->count++;
|
||
|
lcdon->lcdon_flag++;
|
||
|
}
|
||
|
|
||
|
event->count++;
|
||
|
}
|
||
|
|
||
|
void decon_abd_save_udr(struct abd_protect *abd, unsigned long mif, unsigned long iint, unsigned long disp)
|
||
|
{
|
||
|
struct decon_device *decon = NULL;
|
||
|
struct abd_udr *first = NULL;
|
||
|
struct abd_udr *lcdon = NULL;
|
||
|
struct abd_udr *event = NULL;
|
||
|
|
||
|
struct udr_log *first_log = NULL;
|
||
|
struct udr_log *lcdon_log = NULL;
|
||
|
struct udr_log *event_log = NULL;
|
||
|
|
||
|
if (!abd || !abd->init_done)
|
||
|
return;
|
||
|
|
||
|
if (!mif | !iint | !disp) {
|
||
|
#if defined(CONFIG_EXYNOS_DPU20)
|
||
|
mif = cal_dfs_get_rate(ACPM_DVFS_MIF);
|
||
|
iint = cal_dfs_get_rate(ACPM_DVFS_INT);
|
||
|
disp = cal_dfs_get_rate(ACPM_DVFS_DISP);
|
||
|
#else
|
||
|
mif = exynos_devfreq_get_domain_freq(DEVFREQ_MIF);
|
||
|
iint = exynos_devfreq_get_domain_freq(DEVFREQ_INT);
|
||
|
disp = exynos_devfreq_get_domain_freq(DEVFREQ_DISP);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
decon = get_abd_container_of(abd);
|
||
|
first = &abd->u_first;
|
||
|
lcdon = &abd->u_lcdon;
|
||
|
event = &abd->u_event;
|
||
|
|
||
|
first_log = &first->log[(first->count) % ABD_LOG_MAX];
|
||
|
lcdon_log = &lcdon->log[(lcdon->count) % ABD_LOG_MAX];
|
||
|
event_log = &event->log[(event->count) % ABD_LOG_MAX];
|
||
|
|
||
|
event_log->stamp = local_clock();
|
||
|
event_log->ktime = ktime_get_real_seconds();
|
||
|
event_log->mif = mif;
|
||
|
event_log->iint = iint;
|
||
|
event_log->disp = disp;
|
||
|
event_log->aclk = decon->prev_aclk_khz;
|
||
|
memcpy(event_log->bts, &decon->bts, sizeof(struct decon_bts));
|
||
|
|
||
|
if (!first->count) {
|
||
|
first_log->stamp = event_log->stamp;
|
||
|
first_log->mif = event_log->mif;
|
||
|
first_log->iint = event_log->iint;
|
||
|
first_log->disp = event_log->disp;
|
||
|
|
||
|
memcpy(first_log->bts, event_log->bts, sizeof(struct decon_bts));
|
||
|
first->count++;
|
||
|
}
|
||
|
|
||
|
if (!lcdon->lcdon_flag) {
|
||
|
lcdon_log->stamp = event_log->stamp;
|
||
|
lcdon_log->mif = event_log->mif;
|
||
|
lcdon_log->iint = event_log->iint;
|
||
|
lcdon_log->disp = event_log->disp;
|
||
|
|
||
|
memcpy(lcdon_log->bts, event_log->bts, sizeof(struct decon_bts));
|
||
|
lcdon->count++;
|
||
|
lcdon->lcdon_flag++;
|
||
|
}
|
||
|
|
||
|
event->count++;
|
||
|
}
|
||
|
|
||
|
static void decon_abd_save_pin(struct abd_protect *abd, struct abd_pin_info *pin, struct abd_pin *trace, bool on)
|
||
|
{
|
||
|
struct decon_device *decon = NULL;
|
||
|
struct abd_pin *first = NULL;
|
||
|
|
||
|
struct pin_log *first_log = NULL;
|
||
|
struct pin_log *trace_log = NULL;
|
||
|
|
||
|
if (!abd || !abd->init_done)
|
||
|
return;
|
||
|
|
||
|
decon = get_abd_container_of(abd);
|
||
|
first = &pin->p_first;
|
||
|
|
||
|
first_log = &first->log[(first->count) % ABD_LOG_MAX];
|
||
|
trace_log = &trace->log[(trace->count) % ABD_LOG_MAX];
|
||
|
|
||
|
trace_log->stamp = local_clock();
|
||
|
trace_log->ktime = ktime_get_real_seconds();
|
||
|
trace_log->level = pin->level;
|
||
|
trace_log->state = decon->state;
|
||
|
trace_log->onoff = on;
|
||
|
|
||
|
if (!first->count) {
|
||
|
memset(first_log, 0, sizeof(struct pin_log));
|
||
|
memcpy(first_log, trace_log, sizeof(struct pin_log));
|
||
|
first->count++;
|
||
|
}
|
||
|
|
||
|
trace->count++;
|
||
|
}
|
||
|
|
||
|
static void decon_abd_pin_clear_pending_bit(int irq)
|
||
|
{
|
||
|
struct irq_desc *desc;
|
||
|
|
||
|
desc = irq_to_desc(irq);
|
||
|
if (desc->irq_data.chip->irq_ack) {
|
||
|
desc->irq_data.chip->irq_ack(&desc->irq_data);
|
||
|
desc->istate &= ~IRQS_PENDING;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void decon_abd_pin_enable_irq(int irq, unsigned int on)
|
||
|
{
|
||
|
if (on) {
|
||
|
decon_abd_pin_clear_pending_bit(irq);
|
||
|
enable_irq(irq);
|
||
|
} else {
|
||
|
decon_abd_pin_clear_pending_bit(irq);
|
||
|
disable_irq_nosync(irq);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static struct abd_pin_info *decon_abd_find_pin_info(struct abd_protect *abd, unsigned int gpio)
|
||
|
{
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++) {
|
||
|
if (abd->pin[i].gpio == gpio)
|
||
|
return &abd->pin[i];
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static void _decon_abd_pin_enable(struct abd_protect *abd, struct abd_pin_info *pin, bool on)
|
||
|
{
|
||
|
struct abd_pin *trace = &pin->p_lcdon;
|
||
|
struct abd_pin *event = &pin->p_event;
|
||
|
unsigned int state = 0;
|
||
|
|
||
|
if (!abd || !abd->init_done || !pin)
|
||
|
return;
|
||
|
|
||
|
if (!pin->gpio && !pin->irq)
|
||
|
return;
|
||
|
|
||
|
if (pin->enable == on)
|
||
|
return;
|
||
|
|
||
|
pin->enable = on;
|
||
|
|
||
|
pin->level = gpio_get_value(pin->gpio);
|
||
|
|
||
|
if (pin->level == pin->active_level)
|
||
|
decon_abd_save_pin(abd, pin, trace, on);
|
||
|
|
||
|
dbg_info("%s: on: %d, %s(%3d,%d) level: %d, count: %d(event: %d), state: %d, %s\n", __func__,
|
||
|
on, pin->name, pin->irq, pin->desc->depth, pin->level, trace->count, event->count, state,
|
||
|
(pin->level == pin->active_level) ? "abnormal" : "normal");
|
||
|
|
||
|
if (pin->name && !strcmp(pin->name, "pcd"))
|
||
|
set_frame_bypass(abd, (pin->level == pin->active_level) ? 1 : 0);
|
||
|
|
||
|
if (pin->irq)
|
||
|
decon_abd_pin_enable_irq(pin->irq, on);
|
||
|
}
|
||
|
|
||
|
int decon_abd_pin_enable(struct abd_protect *abd, unsigned int gpio, bool on)
|
||
|
{
|
||
|
struct abd_pin_info *pin = NULL;
|
||
|
|
||
|
pin = decon_abd_find_pin_info(abd, gpio);
|
||
|
if (!pin)
|
||
|
return -EINVAL;
|
||
|
|
||
|
_decon_abd_pin_enable(abd, pin, on);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void decon_abd_enable(struct abd_protect *abd, unsigned int enable)
|
||
|
{
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
if (!abd)
|
||
|
return;
|
||
|
|
||
|
if (abd->enable == enable)
|
||
|
dbg_none("%s: already %s\n", __func__, enable ? "enabled" : "disabled");
|
||
|
|
||
|
if (abd->enable != enable)
|
||
|
dbg_info("%s: bypass: %d,%d\n", __func__, get_mipi_rw_bypass(abd), get_frame_bypass(abd));
|
||
|
|
||
|
if (!abd->enable && enable) { /* off -> on */
|
||
|
abd->f_lcdon.lcdon_flag = 0;
|
||
|
abd->u_lcdon.lcdon_flag = 0;
|
||
|
}
|
||
|
|
||
|
abd->enable = enable;
|
||
|
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++)
|
||
|
_decon_abd_pin_enable(abd, &abd->pin[i], enable);
|
||
|
}
|
||
|
|
||
|
irqreturn_t decon_abd_handler(int irq, void *dev_id)
|
||
|
{
|
||
|
struct abd_protect *abd = (struct abd_protect *)dev_id;
|
||
|
struct decon_device *decon = get_abd_container_of(abd);
|
||
|
struct abd_pin_info *pin = NULL;
|
||
|
struct abd_pin *trace = NULL;
|
||
|
struct abd_pin *lcdon = NULL;
|
||
|
struct adb_pin_handler *pin_handler = NULL;
|
||
|
unsigned int i = 0, state = 0;
|
||
|
|
||
|
spin_lock(&abd->slock);
|
||
|
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++) {
|
||
|
pin = &abd->pin[i];
|
||
|
trace = &pin->p_event;
|
||
|
lcdon = &pin->p_lcdon;
|
||
|
if (pin && irq == pin->irq)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (i == ABD_PIN_MAX) {
|
||
|
dbg_info("%s: irq(%d) is not in abd\n", __func__, irq);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
pin->level = gpio_get_value(pin->gpio);
|
||
|
state = decon->state;
|
||
|
|
||
|
decon_abd_save_pin(abd, pin, trace, 1);
|
||
|
|
||
|
dbg_info("%s: %s(%d) level: %d, count: %d(lcdon: %d), state: %d, %s\n", __func__,
|
||
|
pin->name, pin->irq, pin->level, trace->count, lcdon->count, state,
|
||
|
(pin->level == pin->active_level) ? "abnormal" : "normal");
|
||
|
|
||
|
if (pin->active_level != pin->level)
|
||
|
goto exit;
|
||
|
|
||
|
if (i == ABD_PIN_PCD)
|
||
|
set_frame_bypass(abd, 1);
|
||
|
|
||
|
list_for_each_entry(pin_handler, &pin->handler_list, node) {
|
||
|
if (pin_handler && pin_handler->handler)
|
||
|
pin_handler->handler(irq, pin_handler->dev_id);
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
spin_unlock(&abd->slock);
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
int decon_abd_pin_register_handler(struct abd_protect *abd, int irq, irq_handler_t handler, void *dev_id)
|
||
|
{
|
||
|
struct abd_pin_info *pin = NULL;
|
||
|
struct adb_pin_handler *pin_handler = NULL, *tmp = NULL;
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
if (!irq) {
|
||
|
dbg_info("%s: irq(%d) invalid\n", __func__, irq);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!handler) {
|
||
|
dbg_info("%s: handler invalid\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++) {
|
||
|
pin = &abd->pin[i];
|
||
|
if (pin && irq == pin->irq) {
|
||
|
dbg_info("%s: find irq(%d) for %s pin\n", __func__, irq, pin->name);
|
||
|
|
||
|
list_for_each_entry_safe(pin_handler, tmp, &pin->handler_list, node) {
|
||
|
WARN(pin_handler->handler == handler && pin_handler->dev_id == dev_id,
|
||
|
"%s: already registered handler\n", __func__);
|
||
|
}
|
||
|
|
||
|
pin_handler = kzalloc(sizeof(struct adb_pin_handler), GFP_KERNEL);
|
||
|
if (!pin_handler) {
|
||
|
dbg_info("%s: handler kzalloc fail\n", __func__);
|
||
|
break;
|
||
|
}
|
||
|
pin_handler->handler = handler;
|
||
|
pin_handler->dev_id = dev_id;
|
||
|
list_add_tail(&pin_handler->node, &pin->handler_list);
|
||
|
|
||
|
dbg_info("%s: handler is registered\n", __func__);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (i == ABD_PIN_MAX) {
|
||
|
dbg_info("%s: irq(%d) is not in abd\n", __func__, irq);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int decon_abd_pin_unregister_handler(struct abd_protect *abd, int irq, irq_handler_t handler, void *dev_id)
|
||
|
{
|
||
|
struct abd_pin_info *pin = NULL;
|
||
|
struct adb_pin_handler *pin_handler = NULL, *tmp = NULL;
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
if (!irq) {
|
||
|
dbg_info("%s: irq(%d) invalid\n", __func__, irq);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!handler) {
|
||
|
dbg_info("%s: handler invalid\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++) {
|
||
|
pin = &abd->pin[i];
|
||
|
if (pin && irq == pin->irq) {
|
||
|
dbg_info("%s: find irq(%d) for %s pin\n", __func__, irq, pin->name);
|
||
|
|
||
|
list_for_each_entry_safe(pin_handler, tmp, &pin->handler_list, node) {
|
||
|
if (pin_handler->handler == handler && pin_handler->dev_id == dev_id)
|
||
|
list_del(&pin->handler_list);
|
||
|
kfree(pin_handler);
|
||
|
}
|
||
|
|
||
|
dbg_info("%s: handler is unregistered\n", __func__);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (i == ABD_PIN_MAX) {
|
||
|
dbg_info("%s: irq(%d) is not in abd\n", __func__, irq);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void _decon_abd_print_pin(struct seq_file *m, struct abd_pin *trace)
|
||
|
{
|
||
|
struct timeval tv;
|
||
|
struct rtc_time tm;
|
||
|
struct pin_log *log;
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
if (!trace->count)
|
||
|
return;
|
||
|
|
||
|
abd_printf(m, "%s total count: %d\n", trace->name, trace->count);
|
||
|
for (i = 0; i < ABD_LOG_MAX; i++) {
|
||
|
log = &trace->log[i];
|
||
|
if (!log->stamp)
|
||
|
continue;
|
||
|
|
||
|
tv = ns_to_timeval(log->stamp);
|
||
|
rtc_time_to_tm(log->ktime, &tm);
|
||
|
abd_printf(m, "%d-%02d-%02d %02d:%02d:%02d / %lu.%06lu / level: %d onoff: %d state: %d\n",
|
||
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||
|
(unsigned long)tv.tv_sec, tv.tv_usec, log->level, log->onoff, log->state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void decon_abd_print_pin(struct seq_file *m, struct abd_pin_info *pin)
|
||
|
{
|
||
|
if (!pin->irq && !pin->gpio)
|
||
|
return;
|
||
|
|
||
|
if (!pin->p_first.count)
|
||
|
return;
|
||
|
|
||
|
abd_printf(m, "[%s]\n", pin->name);
|
||
|
|
||
|
_decon_abd_print_pin(m, &pin->p_first);
|
||
|
_decon_abd_print_pin(m, &pin->p_lcdon);
|
||
|
_decon_abd_print_pin(m, &pin->p_event);
|
||
|
}
|
||
|
|
||
|
static const char *sync_status_str(int status)
|
||
|
{
|
||
|
if (status < 0)
|
||
|
return "error";
|
||
|
|
||
|
if (status > 0)
|
||
|
return "signaled";
|
||
|
|
||
|
return "active";
|
||
|
}
|
||
|
|
||
|
static void decon_abd_print_fto(struct seq_file *m, struct abd_fto *trace)
|
||
|
{
|
||
|
struct timeval tv;
|
||
|
struct rtc_time tm;
|
||
|
struct fto_log *log;
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
if (!trace->count)
|
||
|
return;
|
||
|
|
||
|
abd_printf(m, "%s total count: %d\n", trace->name, trace->count);
|
||
|
for (i = 0; i < ABD_LOG_MAX; i++) {
|
||
|
log = &trace->log[i];
|
||
|
if (!log->stamp)
|
||
|
continue;
|
||
|
tv = ns_to_timeval(log->stamp);
|
||
|
rtc_time_to_tm(log->ktime, &tm);
|
||
|
|
||
|
#if defined(CONFIG_SOC_EXYNOS7885)
|
||
|
abd_printf(m, "%d-%02d-%02d %02d:%02d:%02d / %lu.%06lu / winid: %d, %s:%s\n",
|
||
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||
|
(unsigned long)tv.tv_sec, tv.tv_usec, log->winid, log->fence.name, sync_status_str(atomic_read(&log->fence.status)));
|
||
|
#elif defined(CONFIG_SOC_EXYNOS9810)
|
||
|
abd_printf(m, "%d-%02d-%02d %02d:%02d:%02d / %lu.%06lu / winid: %d, %s\n",
|
||
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||
|
(unsigned long)tv.tv_sec, tv.tv_usec, log->winid, log->fence.name, sync_status_str(fence_get_status(log->fence.fence)));
|
||
|
#else
|
||
|
abd_printf(m, "%d-%02d-%02d %02d:%02d:%02d / %lu.%06lu / winid: %d, %s\n",
|
||
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||
|
(unsigned long)tv.tv_sec, tv.tv_usec, log->winid, sync_status_str(dma_fence_get_status_locked(&log->fence)));
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void decon_abd_print_udr(struct seq_file *m, struct abd_udr *trace)
|
||
|
{
|
||
|
struct timeval tv;
|
||
|
struct rtc_time tm;
|
||
|
struct udr_log *log;
|
||
|
struct decon_bts *bts;
|
||
|
struct bts_decon_info *bts_info;
|
||
|
unsigned int i = 0, idx;
|
||
|
|
||
|
if (!trace->count)
|
||
|
return;
|
||
|
|
||
|
abd_printf(m, "%s total count: %d\n", trace->name, trace->count);
|
||
|
for (i = 0; i < ABD_LOG_MAX; i++) {
|
||
|
log = &trace->log[i];
|
||
|
if (!log->stamp)
|
||
|
continue;
|
||
|
tv = ns_to_timeval(log->stamp);
|
||
|
rtc_time_to_tm(log->ktime, &tm);
|
||
|
abd_printf(m, "%d-%02d-%02d %02d:%02d:%02d / %lu.%06lu\n",
|
||
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||
|
(unsigned long)tv.tv_sec, tv.tv_usec);
|
||
|
|
||
|
bts = (struct decon_bts *)log->bts;
|
||
|
bts_info = &bts->bts_info;
|
||
|
abd_printf(m, "MIF(%lu), INT(%lu), DISP(%lu), ACLK(%lu) / total(%u %u), max(%u %u), peak(%u)\n",
|
||
|
log->mif, log->iint, log->disp, log->aclk,
|
||
|
bts->prev_total_bw,
|
||
|
bts->total_bw,
|
||
|
bts->prev_max_disp_freq,
|
||
|
bts->max_disp_freq,
|
||
|
bts->peak);
|
||
|
|
||
|
for (idx = 0; idx < BTS_DPP_MAX; ++idx) {
|
||
|
if (!bts_info->dpp[idx].used)
|
||
|
continue;
|
||
|
|
||
|
#if defined(CONFIG_SOC_EXYNOS7885)
|
||
|
abd_printf(m, "DPP[%d] (%d) b(%d) s(%4d %4d) d(%4d %4d %4d %4d)\n",
|
||
|
idx, bts_info->dpp[idx].idma_type, bts_info->dpp[idx].bpp,
|
||
|
bts_info->dpp[idx].src_w, bts_info->dpp[idx].src_h,
|
||
|
bts_info->dpp[idx].dst.x1, bts_info->dpp[idx].dst.x2,
|
||
|
bts_info->dpp[idx].dst.y1, bts_info->dpp[idx].dst.y2);
|
||
|
#else
|
||
|
abd_printf(m, "DPP[%d] b(%d) s(%4d %4d) d(%4d %4d %4d %4d) r(%d)\n",
|
||
|
idx, bts_info->dpp[idx].bpp,
|
||
|
bts_info->dpp[idx].src_w, bts_info->dpp[idx].src_h,
|
||
|
bts_info->dpp[idx].dst.x1, bts_info->dpp[idx].dst.x2,
|
||
|
bts_info->dpp[idx].dst.y1, bts_info->dpp[idx].dst.y2,
|
||
|
bts_info->dpp[idx].rotation);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void decon_abd_print_ss_log(struct abd_protect *abd, struct seq_file *m)
|
||
|
{
|
||
|
unsigned int log_max = 200, i, idx;
|
||
|
struct timeval tv;
|
||
|
struct decon_device *decon = get_abd_container_of(abd);
|
||
|
int start = atomic_read(&decon->d.event_log_idx);
|
||
|
struct dpu_log *log;
|
||
|
|
||
|
start = (start > log_max) ? start - log_max + 1 : 0;
|
||
|
|
||
|
for (i = 0; i < log_max; i++) {
|
||
|
idx = (start + i) % DPU_EVENT_LOG_MAX;
|
||
|
log = &decon->d.event_log[idx];
|
||
|
|
||
|
if (!ktime_to_ns(log->time))
|
||
|
continue;
|
||
|
tv = ktime_to_timeval(log->time);
|
||
|
if (i && !(i % 10))
|
||
|
abd_printf(m, "\n");
|
||
|
abd_printf(m, "%lu.%06lu %2u, ", (unsigned long)tv.tv_sec, tv.tv_usec, log->type);
|
||
|
}
|
||
|
|
||
|
abd_printf(m, "\n");
|
||
|
}
|
||
|
|
||
|
static void decon_abd_print_str(struct seq_file *m, struct abd_str *trace)
|
||
|
{
|
||
|
unsigned int log_max = ABD_LOG_MAX, i, idx;
|
||
|
struct timeval tv;
|
||
|
struct rtc_time tm;
|
||
|
int start = trace->count - 1;
|
||
|
struct str_log *log;
|
||
|
char print_buf[200] = {0, };
|
||
|
struct seq_file p = {
|
||
|
.buf = print_buf,
|
||
|
.size = sizeof(print_buf) - 1,
|
||
|
};
|
||
|
|
||
|
if (start < 0)
|
||
|
return;
|
||
|
|
||
|
abd_printf(m, "==========_STR_DEBUG_==========\n");
|
||
|
|
||
|
start = (start > log_max) ? start - log_max + 1 : 0;
|
||
|
|
||
|
for (i = 0; i < log_max; i++) {
|
||
|
idx = (start + i) % ABD_LOG_MAX;
|
||
|
log = &trace->log[idx];
|
||
|
|
||
|
if (!log->stamp)
|
||
|
continue;
|
||
|
tv = ns_to_timeval(log->stamp);
|
||
|
rtc_time_to_tm(log->ktime, &tm);
|
||
|
if (i && !(i % 2)) {
|
||
|
abd_printf(m, "%s\n", p.buf);
|
||
|
p.count = 0;
|
||
|
memset(print_buf, 0, sizeof(print_buf));
|
||
|
}
|
||
|
seq_printf(&p, "%d-%02d-%02d %02d:%02d:%02d / %lu.%06lu / %-20s ",
|
||
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||
|
(unsigned long)tv.tv_sec, tv.tv_usec, log->print);
|
||
|
}
|
||
|
|
||
|
abd_printf(m, "%s\n", p.count ? p.buf : "");
|
||
|
}
|
||
|
|
||
|
static void decon_abd_print_bit(struct seq_file *m, struct abd_bit *trace)
|
||
|
{
|
||
|
struct bit_log *log;
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
if (!trace->count)
|
||
|
return;
|
||
|
|
||
|
abd_printf(m, "%s total count: %d\n", trace->name, trace->count);
|
||
|
for (i = 0; i < ABD_LOG_MAX; i++) {
|
||
|
log = &trace->log[i];
|
||
|
if (!log->stamp)
|
||
|
continue;
|
||
|
_decon_abd_print_bit(m, log);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int decon_abd_show(struct seq_file *m, void *unused)
|
||
|
{
|
||
|
struct abd_protect *abd = m->private;
|
||
|
struct decon_device *decon = get_abd_container_of(abd);
|
||
|
struct dsim_device *dsim = v4l2_get_subdevdata(decon->out_sd[0]);
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
abd_printf(m, "==========_DECON_ABD_==========\n");
|
||
|
abd_printf(m, "bypass: %d,%d, lcdtype: %6X\n", get_frame_bypass(abd), get_mipi_rw_bypass(abd), get_boot_lcdtype());
|
||
|
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++) {
|
||
|
if (abd->pin[i].p_first.count) {
|
||
|
abd_printf(m, "==========_PIN_DEBUG_==========\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++)
|
||
|
decon_abd_print_pin(m, &abd->pin[i]);
|
||
|
|
||
|
if (abd->b_first.count) {
|
||
|
abd_printf(m, "==========_BIT_DEBUG_==========\n");
|
||
|
decon_abd_print_bit(m, &abd->b_first);
|
||
|
decon_abd_print_bit(m, &abd->b_event);
|
||
|
}
|
||
|
|
||
|
if (abd->f_first.count) {
|
||
|
abd_printf(m, "==========_FTO_DEBUG_==========\n");
|
||
|
decon_abd_print_fto(m, &abd->f_first);
|
||
|
decon_abd_print_fto(m, &abd->f_lcdon);
|
||
|
decon_abd_print_fto(m, &abd->f_event);
|
||
|
}
|
||
|
|
||
|
if (abd->u_first.count) {
|
||
|
abd_printf(m, "==========_UDR_DEBUG_==========\n");
|
||
|
abd_printf(m, "dsim underrun irq occurs(%d)\n", dsim->total_underrun_cnt);
|
||
|
decon_abd_print_udr(m, &abd->u_first);
|
||
|
decon_abd_print_udr(m, &abd->u_lcdon);
|
||
|
decon_abd_print_udr(m, &abd->u_event);
|
||
|
}
|
||
|
|
||
|
decon_abd_print_str(m, &abd->s_event);
|
||
|
|
||
|
abd_printf(m, "==========_RAM_DEBUG_==========\n");
|
||
|
decon_abd_print_ss_log(abd, m);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int decon_abd_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return single_open(file, decon_abd_show, inode->i_private);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations decon_abd_fops = {
|
||
|
.read = seq_read,
|
||
|
.llseek = seq_lseek,
|
||
|
.release = seq_release,
|
||
|
.open = decon_abd_open,
|
||
|
};
|
||
|
|
||
|
static int decon_abd_reboot_notifier(struct notifier_block *this,
|
||
|
unsigned long code, void *unused)
|
||
|
{
|
||
|
struct abd_protect *abd = container_of(this, struct abd_protect, reboot_notifier);
|
||
|
unsigned int i = 0;
|
||
|
struct seq_file *m = NULL;
|
||
|
|
||
|
dbg_info("++ %s: %lu\n", __func__, code);
|
||
|
|
||
|
decon_abd_enable(abd, 0);
|
||
|
|
||
|
abd_printf(m, "==========_DECON_ABD_==========\n");
|
||
|
abd_printf(m, "bypass: %d,%d, lcdtype: %6X\n", get_frame_bypass(abd), get_mipi_rw_bypass(abd), get_boot_lcdtype());
|
||
|
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++) {
|
||
|
if (abd->pin[i].p_first.count) {
|
||
|
abd_printf(m, "==========_PIN_DEBUG_==========\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++)
|
||
|
decon_abd_print_pin(m, &abd->pin[i]);
|
||
|
|
||
|
if (abd->b_first.count) {
|
||
|
abd_printf(m, "==========_BIT_DEBUG_==========\n");
|
||
|
decon_abd_print_bit(m, &abd->b_first);
|
||
|
decon_abd_print_bit(m, &abd->b_event);
|
||
|
}
|
||
|
|
||
|
if (abd->f_first.count) {
|
||
|
abd_printf(m, "==========_FTO_DEBUG_==========\n");
|
||
|
decon_abd_print_fto(m, &abd->f_first);
|
||
|
decon_abd_print_fto(m, &abd->f_lcdon);
|
||
|
decon_abd_print_fto(m, &abd->f_event);
|
||
|
}
|
||
|
|
||
|
if (abd->u_first.count) {
|
||
|
abd_printf(m, "==========_UDR_DEBUG_==========\n");
|
||
|
decon_abd_print_udr(m, &abd->u_first);
|
||
|
decon_abd_print_udr(m, &abd->u_lcdon);
|
||
|
decon_abd_print_udr(m, &abd->u_event);
|
||
|
}
|
||
|
|
||
|
decon_abd_print_str(m, &abd->s_event);
|
||
|
|
||
|
dbg_info("-- %s: %lu\n", __func__, code);
|
||
|
|
||
|
return NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
static int decon_abd_pin_register_function(struct abd_protect *abd, struct abd_pin_info *pin, char *keyword,
|
||
|
irqreturn_t func(int irq, void *dev_id))
|
||
|
{
|
||
|
int ret = 0, gpio = 0;
|
||
|
enum of_gpio_flags flags;
|
||
|
struct device_node *np = NULL;
|
||
|
struct platform_device *pdev = NULL;
|
||
|
struct device *dev = NULL;
|
||
|
unsigned int irqf_type = IRQF_TRIGGER_RISING;
|
||
|
struct abd_pin *trace = &pin->p_lcdon;
|
||
|
char *prefix_gpio = "gpio_";
|
||
|
char dts_name[10] = {0, };
|
||
|
|
||
|
if (strlen(keyword) + strlen(prefix_gpio) >= sizeof(dts_name)) {
|
||
|
dbg_info("%s: %s is too log(%zu)\n", __func__, keyword, strlen(keyword));
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
scnprintf(dts_name, sizeof(dts_name), "%s%s", prefix_gpio, keyword);
|
||
|
|
||
|
pdev = of_find_abd_dt_parent_platform_device();
|
||
|
dev = &pdev->dev;
|
||
|
|
||
|
np = of_find_decon_board(pdev ? &pdev->dev : NULL);
|
||
|
|
||
|
if (!of_find_property(np, dts_name, NULL))
|
||
|
goto exit;
|
||
|
|
||
|
gpio = of_get_named_gpio_flags(np, dts_name, 0, &flags);
|
||
|
if (!gpio_is_valid(gpio)) {
|
||
|
dbg_info("%s: gpio_is_valid fail, gpio: %s, %d\n", __func__, dts_name, gpio);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
dbg_info("%s: found %s(%d) success\n", __func__, dts_name, gpio);
|
||
|
|
||
|
if (gpio_to_irq(gpio) > 0) {
|
||
|
pin->gpio = gpio;
|
||
|
pin->irq = gpio_to_irq(gpio);
|
||
|
pin->desc = irq_to_desc(pin->irq);
|
||
|
} else {
|
||
|
dbg_info("%s: gpio_to_irq fail, gpio: %d, irq: %d\n", __func__, gpio, gpio_to_irq(gpio));
|
||
|
pin->gpio = gpio;
|
||
|
pin->irq = 0;
|
||
|
pin->desc = kzalloc(sizeof(struct irq_desc), GFP_KERNEL);
|
||
|
}
|
||
|
|
||
|
pin->active_level = !(flags & OF_GPIO_ACTIVE_LOW);
|
||
|
irqf_type = (flags & OF_GPIO_ACTIVE_LOW) ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
|
||
|
dbg_info("%s: %s is active %s%s\n", __func__, keyword, pin->active_level ? "high" : "low",
|
||
|
(pin->irq) ? ((irqf_type == IRQF_TRIGGER_RISING) ? ", rising" : ", falling") : "");
|
||
|
|
||
|
pin->name = keyword;
|
||
|
pin->p_first.name = "first";
|
||
|
pin->p_lcdon.name = "lcdon";
|
||
|
pin->p_event.name = "event";
|
||
|
|
||
|
pin->level = gpio_get_value(pin->gpio);
|
||
|
if (pin->level == pin->active_level) {
|
||
|
dbg_info("%s: %s(%d) is already %s(%d)\n", __func__, keyword, pin->gpio,
|
||
|
(pin->active_level) ? "high" : "low", pin->level);
|
||
|
|
||
|
decon_abd_save_pin(abd, pin, trace, 1);
|
||
|
|
||
|
if (pin->name && !strcmp(pin->name, "pcd"))
|
||
|
set_frame_bypass(abd, 1);
|
||
|
}
|
||
|
|
||
|
if (pin->irq) {
|
||
|
irq_set_irq_type(pin->irq, irqf_type);
|
||
|
irq_set_status_flags(pin->irq, _IRQ_NOAUTOEN);
|
||
|
decon_abd_pin_clear_pending_bit(pin->irq);
|
||
|
|
||
|
if (devm_request_irq(dev, pin->irq, func, irqf_type, keyword, abd)) {
|
||
|
dbg_info("%s: failed to request irq for %s\n", __func__, keyword);
|
||
|
/* pin->gpio = 0; */
|
||
|
pin->irq = 0;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
INIT_LIST_HEAD(&pin->handler_list);
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if defined(CONFIG_ARCH_EXYNOS)
|
||
|
static int _decon_abd_fb_blank(struct fb_info *info, int blank)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!lock_fb_info(info)) {
|
||
|
dbg_info("%s: fblock is failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
dbg_info("+ %s\n", __func__);
|
||
|
|
||
|
info->flags |= FBINFO_MISC_USEREVENT;
|
||
|
ret = fb_blank(info, blank);
|
||
|
info->flags &= ~FBINFO_MISC_USEREVENT;
|
||
|
unlock_fb_info(info);
|
||
|
|
||
|
dbg_info("- %s\n", __func__);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int decon_abd_con_set_dummy_blank(struct abd_protect *abd, struct fb_info *fbinfo, unsigned int dummy)
|
||
|
{
|
||
|
int ret = NOTIFY_DONE;
|
||
|
|
||
|
if (dummy) {
|
||
|
fbinfo->fbops->fb_blank = NULL;
|
||
|
ret = NOTIFY_STOP_MASK;
|
||
|
} else {
|
||
|
_decon_abd_pin_enable(abd, &abd->pin[ABD_PIN_CON], 0);
|
||
|
fbinfo->fbops->fb_blank = abd->fbops.fb_blank;
|
||
|
set_frame_bypass(abd, 0);
|
||
|
abd->con_blank = 0;
|
||
|
ret = NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void decon_abd_con_prepare_dummy_info(struct abd_protect *abd)
|
||
|
{
|
||
|
struct fb_info *fbinfo = get_fbinfo(abd);
|
||
|
struct fb_ops *fbops = fbinfo->fbops;
|
||
|
|
||
|
memcpy(&abd->fbops, fbops, sizeof(struct fb_ops));
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int decon_abd_con_fb_blank(struct abd_protect *abd)
|
||
|
{
|
||
|
struct fb_info *fbinfo = get_fbinfo(abd);
|
||
|
|
||
|
dbg_info("%s\n", __func__);
|
||
|
|
||
|
set_mipi_rw_bypass(abd, 1);
|
||
|
|
||
|
_decon_abd_fb_blank(fbinfo, FB_BLANK_POWERDOWN);
|
||
|
|
||
|
decon_abd_con_set_dummy_blank(abd, fbinfo, 1);
|
||
|
|
||
|
//_decon_abd_pin_enable(abd, &abd->pin[ABD_PIN_CON], 1);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void decon_abd_con_work(struct work_struct *work)
|
||
|
{
|
||
|
struct abd_protect *abd = container_of(work, struct abd_protect, con_work);
|
||
|
|
||
|
dbg_info("%s\n", __func__);
|
||
|
|
||
|
decon_abd_con_fb_blank(abd);
|
||
|
}
|
||
|
|
||
|
irqreturn_t decon_abd_con_handler(int irq, void *dev_id)
|
||
|
{
|
||
|
struct abd_protect *abd = (struct abd_protect *)dev_id;
|
||
|
|
||
|
dbg_info("%s: %d\n", __func__, abd->con_blank);
|
||
|
|
||
|
if (!abd->con_blank)
|
||
|
queue_work(abd->con_workqueue, &abd->con_work);
|
||
|
|
||
|
abd->con_blank = 1;
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
static int decon_abd_con_fb_notifier_callback(struct notifier_block *this,
|
||
|
unsigned long event, void *data)
|
||
|
{
|
||
|
struct abd_protect *abd = container_of(this, struct abd_protect, con_fb_notifier);
|
||
|
struct fb_event *evdata = data;
|
||
|
int fb_blank;
|
||
|
|
||
|
switch (event) {
|
||
|
case FB_EARLY_EVENT_BLANK:
|
||
|
break;
|
||
|
default:
|
||
|
return NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
fb_blank = *(int *)evdata->data;
|
||
|
|
||
|
if (evdata->info->node)
|
||
|
return NOTIFY_DONE;
|
||
|
|
||
|
if (!abd->init_done) {
|
||
|
abd->init_done = 1;
|
||
|
return NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
if (fb_blank == FB_BLANK_UNBLANK && event == FB_EARLY_EVENT_BLANK) {
|
||
|
int gpio_active = of_gpio_get_active("gpio_con");
|
||
|
|
||
|
flush_workqueue(abd->con_workqueue);
|
||
|
dbg_info("%s: %s\n", __func__, gpio_active ? "disconnected" : "connected");
|
||
|
if (gpio_active > 0)
|
||
|
return decon_abd_con_set_dummy_blank(abd, evdata->info, 1);
|
||
|
else
|
||
|
return decon_abd_con_set_dummy_blank(abd, evdata->info, 0);
|
||
|
}
|
||
|
|
||
|
return NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
static int decon_abd_con_pin_register_hanlder(struct abd_protect *abd)
|
||
|
{
|
||
|
int ret = 0, gpio = 0;
|
||
|
enum of_gpio_flags flags;
|
||
|
struct device_node *np = NULL;
|
||
|
struct platform_device *pdev = NULL;
|
||
|
unsigned int irqf_type = IRQF_TRIGGER_RISING;
|
||
|
char *prefix_gpio = "gpio_";
|
||
|
char dts_name[10] = {0, };
|
||
|
char *keyword = "con";
|
||
|
struct abd_pin_info pin = {0, };
|
||
|
|
||
|
if (strlen(keyword) + strlen(prefix_gpio) >= sizeof(dts_name)) {
|
||
|
dbg_info("%s: %s is too log(%zu)\n", __func__, keyword, strlen(keyword));
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
scnprintf(dts_name, sizeof(dts_name), "%s%s", prefix_gpio, keyword);
|
||
|
|
||
|
pdev = of_find_abd_dt_parent_platform_device();
|
||
|
|
||
|
np = of_find_decon_board(pdev ? &pdev->dev : NULL);
|
||
|
|
||
|
if (!of_find_property(np, dts_name, NULL)) {
|
||
|
dbg_info("%s: %s not exist\n", __func__, dts_name);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
gpio = of_get_named_gpio_flags(np, dts_name, 0, &flags);
|
||
|
if (!gpio_is_valid(gpio)) {
|
||
|
dbg_info("%s: gpio_is_valid fail, gpio: %s, %d\n", __func__, dts_name, gpio);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
dbg_info("%s: found %s(%d) success\n", __func__, dts_name, gpio);
|
||
|
|
||
|
if (gpio_to_irq(gpio) > 0) {
|
||
|
pin.gpio = gpio;
|
||
|
pin.irq = gpio_to_irq(gpio);
|
||
|
} else {
|
||
|
dbg_info("%s: gpio_to_irq fail, gpio: %d, irq: %d\n", __func__, gpio, gpio_to_irq(gpio));
|
||
|
pin.gpio = 0;
|
||
|
pin.irq = 0;
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
pin.active_level = !(flags & OF_GPIO_ACTIVE_LOW);
|
||
|
irqf_type = (flags & OF_GPIO_ACTIVE_LOW) ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
|
||
|
dbg_info("%s: %s is active %s%s\n", __func__, keyword, pin.active_level ? "high" : "low",
|
||
|
(irqf_type == IRQF_TRIGGER_RISING) ? ", rising" : ", falling");
|
||
|
|
||
|
pin.level = gpio_get_value(pin.gpio);
|
||
|
if (pin.level != pin.active_level)
|
||
|
decon_abd_pin_register_handler(abd, pin.irq, decon_abd_con_handler, abd);
|
||
|
|
||
|
exit:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int decon_abd_con_register(struct abd_protect *abd)
|
||
|
{
|
||
|
struct platform_device *pdev = NULL;
|
||
|
struct device_node *np = NULL;
|
||
|
|
||
|
pdev = of_find_abd_dt_parent_platform_device();
|
||
|
|
||
|
np = of_find_decon_board(pdev ? &pdev->dev : NULL);
|
||
|
|
||
|
if (!of_find_property(np, "gpio_con", NULL))
|
||
|
goto exit;
|
||
|
|
||
|
decon_abd_con_prepare_dummy_info(abd);
|
||
|
|
||
|
INIT_WORK(&abd->con_work, decon_abd_con_work);
|
||
|
|
||
|
abd->con_workqueue = create_singlethread_workqueue("abd_conn_workqueue");
|
||
|
if (!abd->con_workqueue) {
|
||
|
dbg_info("%s: create_singlethread_workqueue fail\n", __func__);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
abd->con_fb_notifier.priority = INT_MAX;
|
||
|
abd->con_fb_notifier.notifier_call = decon_abd_con_fb_notifier_callback;
|
||
|
decon_register_notifier(&abd->con_fb_notifier);
|
||
|
|
||
|
decon_abd_con_pin_register_hanlder(abd);
|
||
|
|
||
|
exit:
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void decon_pm_wake(struct abd_protect *abd, unsigned int wake_lock)
|
||
|
{
|
||
|
struct device *fbdev = NULL;
|
||
|
|
||
|
if (!abd->fbdev)
|
||
|
return;
|
||
|
|
||
|
fbdev = abd->fbdev;
|
||
|
|
||
|
if (abd->wake_lock_enable == wake_lock)
|
||
|
return;
|
||
|
|
||
|
if (wake_lock) {
|
||
|
pm_stay_awake(fbdev);
|
||
|
dbg_info("%s: pm_stay_awake", __func__);
|
||
|
} else if (!wake_lock) {
|
||
|
pm_relax(fbdev);
|
||
|
dbg_info("%s: pm_relax", __func__);
|
||
|
}
|
||
|
|
||
|
abd->wake_lock_enable = wake_lock;
|
||
|
}
|
||
|
|
||
|
static int decon_abd_pin_early_notifier_callback(struct notifier_block *this,
|
||
|
unsigned long event, void *data)
|
||
|
{
|
||
|
struct abd_protect *abd = container_of(this, struct abd_protect, pin_early_notifier);
|
||
|
struct fb_event *evdata = data;
|
||
|
int fb_blank;
|
||
|
|
||
|
switch (event) {
|
||
|
case FB_EARLY_EVENT_BLANK:
|
||
|
case DECON_EARLY_EVENT_DOZE:
|
||
|
break;
|
||
|
default:
|
||
|
return NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
fb_blank = *(int *)evdata->data;
|
||
|
|
||
|
if (evdata->info->node)
|
||
|
return NOTIFY_DONE;
|
||
|
|
||
|
if (IS_EARLY(event) && fb_blank == FB_BLANK_POWERDOWN)
|
||
|
decon_abd_enable(abd, 0);
|
||
|
|
||
|
if (IS_EARLY(event) && fb_blank == FB_BLANK_UNBLANK)
|
||
|
decon_pm_wake(abd, 1);
|
||
|
|
||
|
return NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
static int decon_abd_pin_after_notifier_callback(struct notifier_block *this,
|
||
|
unsigned long event, void *data)
|
||
|
{
|
||
|
struct abd_protect *abd = container_of(this, struct abd_protect, pin_after_notifier);
|
||
|
struct fb_event *evdata = data;
|
||
|
struct abd_pin_info *pin = NULL;
|
||
|
unsigned int i = 0;
|
||
|
int fb_blank;
|
||
|
|
||
|
switch (event) {
|
||
|
case FB_EVENT_BLANK:
|
||
|
case DECON_EVENT_DOZE:
|
||
|
break;
|
||
|
default:
|
||
|
return NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
fb_blank = *(int *)evdata->data;
|
||
|
|
||
|
if (evdata->info->node)
|
||
|
return NOTIFY_DONE;
|
||
|
|
||
|
if (IS_AFTER(event) && fb_blank == FB_BLANK_UNBLANK)
|
||
|
decon_abd_enable(abd, 1);
|
||
|
else if (IS_AFTER(event) && fb_blank == FB_BLANK_POWERDOWN) {
|
||
|
for (i = 0; i < ABD_PIN_MAX; i++) {
|
||
|
pin = &abd->pin[i];
|
||
|
if (pin && pin->irq)
|
||
|
decon_abd_pin_clear_pending_bit(pin->irq);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (IS_AFTER(event) && fb_blank == FB_BLANK_POWERDOWN)
|
||
|
decon_pm_wake(abd, 0);
|
||
|
|
||
|
return NOTIFY_DONE;
|
||
|
}
|
||
|
|
||
|
static void decon_abd_pin_register(struct abd_protect *abd)
|
||
|
{
|
||
|
spin_lock_init(&abd->slock);
|
||
|
|
||
|
decon_abd_pin_register_function(abd, &abd->pin[ABD_PIN_PCD], "pcd", decon_abd_handler);
|
||
|
decon_abd_pin_register_function(abd, &abd->pin[ABD_PIN_DET], "det", decon_abd_handler);
|
||
|
decon_abd_pin_register_function(abd, &abd->pin[ABD_PIN_ERR], "err", decon_abd_handler);
|
||
|
decon_abd_pin_register_function(abd, &abd->pin[ABD_PIN_CON], "con", decon_abd_handler);
|
||
|
decon_abd_pin_register_function(abd, &abd->pin[ABD_PIN_LOG], "log", decon_abd_handler);
|
||
|
|
||
|
abd->pin_early_notifier.notifier_call = decon_abd_pin_early_notifier_callback;
|
||
|
abd->pin_early_notifier.priority = decon_nb_priority_max.priority - 1;
|
||
|
decon_register_notifier(&abd->pin_early_notifier);
|
||
|
|
||
|
abd->pin_after_notifier.notifier_call = decon_abd_pin_after_notifier_callback;
|
||
|
abd->pin_after_notifier.priority = decon_nb_priority_min.priority + 1;
|
||
|
decon_register_notifier(&abd->pin_after_notifier);
|
||
|
}
|
||
|
|
||
|
static void decon_abd_register(struct abd_protect *abd)
|
||
|
{
|
||
|
struct decon_device *decon = get_abd_container_of(abd);
|
||
|
struct dentry *abd_debugfs_root = decon->d.debug_root;
|
||
|
unsigned int i = 0;
|
||
|
|
||
|
dbg_info("%s: ++\n", __func__);
|
||
|
|
||
|
if (!abd_debugfs_root)
|
||
|
abd_debugfs_root = debugfs_create_dir("panel", NULL);
|
||
|
|
||
|
abd->debugfs_root = abd_debugfs_root;
|
||
|
|
||
|
abd->u_first.name = abd->f_first.name = abd->b_first.name = "first";
|
||
|
abd->u_lcdon.name = abd->f_lcdon.name = "lcdon";
|
||
|
abd->u_event.name = abd->f_event.name = abd->b_event.name = abd->s_event.name = "event";
|
||
|
|
||
|
for (i = 0; i < ABD_LOG_MAX; i++) {
|
||
|
abd->u_first.log[i].bts = kzalloc(sizeof(struct decon_bts), GFP_KERNEL);
|
||
|
abd->u_lcdon.log[i].bts = kzalloc(sizeof(struct decon_bts), GFP_KERNEL);
|
||
|
abd->u_event.log[i].bts = kzalloc(sizeof(struct decon_bts), GFP_KERNEL);
|
||
|
}
|
||
|
|
||
|
debugfs_create_file("debug", 0444, abd_debugfs_root, abd, &decon_abd_fops);
|
||
|
|
||
|
abd->reboot_notifier.notifier_call = decon_abd_reboot_notifier;
|
||
|
register_reboot_notifier(&abd->reboot_notifier);
|
||
|
|
||
|
dbg_info("%s: -- entity was registered\n", __func__);
|
||
|
}
|
||
|
|
||
|
static int match_dev_name(struct device *dev, const void *data)
|
||
|
{
|
||
|
const char *keyword = data;
|
||
|
|
||
|
return dev_name(dev) ? !!strstr(dev_name(dev), keyword) : 0;
|
||
|
}
|
||
|
|
||
|
struct device *find_lcd_class_device(void)
|
||
|
{
|
||
|
static struct class *p_lcd_class;
|
||
|
struct lcd_device *new_ld = NULL;
|
||
|
struct device *dev = NULL;
|
||
|
|
||
|
if (!p_lcd_class) {
|
||
|
new_ld = lcd_device_register("dummy_lcd_class_device", NULL, NULL, NULL);
|
||
|
if (!new_ld)
|
||
|
return NULL;
|
||
|
|
||
|
p_lcd_class = new_ld->dev.class;
|
||
|
lcd_device_unregister(new_ld);
|
||
|
}
|
||
|
|
||
|
dev = class_find_device(p_lcd_class, NULL, "panel", match_dev_name);
|
||
|
|
||
|
return dev;
|
||
|
}
|
||
|
|
||
|
static int __init decon_abd_init(void)
|
||
|
{
|
||
|
struct decon_device *container = find_container();
|
||
|
struct abd_protect *abd = NULL;
|
||
|
struct fb_info *fbinfo = NULL;
|
||
|
|
||
|
if (!container) {
|
||
|
dbg_info("find_container fail\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
abd = &container->abd;
|
||
|
|
||
|
decon_abd_register(abd);
|
||
|
abd->init_done = 1;
|
||
|
|
||
|
find_lcd_class_device();
|
||
|
|
||
|
dbg_info("%s: lcdtype: %6X\n", __func__, get_boot_lcdtype());
|
||
|
|
||
|
if (get_boot_lcdconnected())
|
||
|
decon_abd_pin_register(abd);
|
||
|
else
|
||
|
set_frame_bypass(abd, 1);
|
||
|
|
||
|
decon_abd_enable(abd, 1);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ARCH_EXYNOS))
|
||
|
return 0;
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_MEDIATEK_SOLUTION))
|
||
|
return 0;
|
||
|
|
||
|
fbinfo = get_fbinfo(abd);
|
||
|
abd->fbdev = fbinfo->dev;
|
||
|
|
||
|
if (abd->fbdev) {
|
||
|
device_init_wakeup(abd->fbdev, true);
|
||
|
dbg_info("%s: device_init_wakeup\n", __func__);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
late_initcall_sync(decon_abd_init);
|
||
|
|