video/rk_eink: add rockchip eink support.
Signed-off-by: Wenping Zhang <wenping.zhang@rock-chips.com> Change-Id: I39e92ee00690ea1be274b1abd94d54284ef36898
This commit is contained in:
parent
78263d89a3
commit
93a7515a89
|
@ -623,4 +623,9 @@ config VIDEO_DW_HDMI
|
|||
rather requires a SoC-specific glue driver to call it), it
|
||||
can not be enabled from the configuration menu.
|
||||
|
||||
config ROCKCHIP_EINK
|
||||
bool "enable rockchip eink driver"
|
||||
help
|
||||
Enable rockchip eink driver
|
||||
source "drivers/video/rk_eink/Kconfig"
|
||||
endmenu
|
||||
|
|
|
@ -57,6 +57,7 @@ obj-${CONFIG_VIDEO_TEGRA124} += tegra124/
|
|||
obj-${CONFIG_EXYNOS_FB} += exynos/
|
||||
obj-${CONFIG_VIDEO_ROCKCHIP} += rockchip/
|
||||
obj-${CONFIG_DRM_ROCKCHIP} += drm/
|
||||
obj-${CONFIG_ROCKCHIP_EINK} += rk_eink/
|
||||
|
||||
obj-y += bridge/
|
||||
obj-y += sunxi/
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
config ROCKCHIP_EBC_TCON
|
||||
bool "Rockchip ebc tcon Support"
|
||||
depends on ROCKCHIP_EINK
|
||||
help
|
||||
Choose this option to enable support for EBC TCON controller
|
||||
|
||||
config ROCKCHIP_TPS65185
|
||||
bool "TPS65185 Support"
|
||||
depends on ROCKCHIP_EINK
|
||||
help
|
||||
Choose this option to enable support for TPS65185, which is
|
||||
used to supply power for eink panel.
|
||||
|
||||
config ROCKCHIP_EINK_DISPLAY
|
||||
bool "Rockchip EINK DISPLAY Support"
|
||||
depends on ROCKCHIP_EINK
|
||||
help
|
||||
Choose this option to enable support for eink display, which is
|
||||
used to display uboot/charging/lowerpower logo, and also used to
|
||||
load kernel logo to ram, which can pass to kernel to display.
|
|
@ -0,0 +1,9 @@
|
|||
#
|
||||
# (C) Copyright Fuzhou Rockchip Electronics Co., Ltd
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
|
||||
obj-$(CONFIG_ROCKCHIP_EBC_TCON) += rk_ebc_tcon.o
|
||||
obj-$(CONFIG_ROCKCHIP_TPS65185) += tps65185.o
|
||||
obj-$(CONFIG_ROCKCHIP_EINK_DISPLAY) += rk_eink_display.o epdlut/
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
obj-$(CONFIG_ROCKCHIP_EINK_DISPLAY) += epd_lut.o pvi_waveform.o rkf_waveform.o
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2020 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* Author: Zorro Liu <zorro.liu@rock-chips.com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <common.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "epd_lut.h"
|
||||
|
||||
int epd_lut_from_mem_init(void *waveform, struct epd_lut_ops *ops)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
ret = rkf_wf_input(waveform);
|
||||
if (ret < 0) {
|
||||
printf("[lut]: failed to input RKF waveform\n");
|
||||
} else {
|
||||
printf("[lut]: RKF waveform\n");
|
||||
ops->lut_get = rkf_wf_get_lut;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = pvi_wf_input(waveform);
|
||||
if (ret < 0) {
|
||||
printf("[lut]: failed to input PVI waveform\n");
|
||||
} else {
|
||||
printf("[lut]: PVI waveform\n");
|
||||
ops->lut_get = pvi_wf_get_lut;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *epd_lut_get_wf_version(void)
|
||||
{
|
||||
if (rkf_wf_get_version())
|
||||
return rkf_wf_get_version();
|
||||
if (pvi_wf_get_version())
|
||||
return pvi_wf_get_version();
|
||||
return NULL;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2020 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* Author: Zorro Liu <zorro.liu@rock-chips.com>
|
||||
*/
|
||||
|
||||
#ifndef EPD_LUT_H
|
||||
#define EPD_LUT_H
|
||||
|
||||
#define LUT_SUCCESS (0)
|
||||
#define LUT_ERROR (-1)
|
||||
|
||||
#define LUT_FROM_GPIO_SPI_FLASH (0)
|
||||
#define LUT_FROM_RK_SPI_FLASH (1)
|
||||
#define LUT_FROM_NAND_FLASH (2)
|
||||
#define LUT_FROM_WAVEFORM_FILE (3)
|
||||
#define LUT_FROM_MEM_RESERVED (4)
|
||||
|
||||
enum epd_lut_type {
|
||||
WF_TYPE_RESET = 1,
|
||||
WF_TYPE_GRAY16 = 2,
|
||||
WF_TYPE_GRAY4 = 3,
|
||||
WF_TYPE_GRAY2 = 4,
|
||||
WF_TYPE_AUTO = 5,
|
||||
WF_TYPE_A2 = 6,
|
||||
WF_TYPE_GC16 = 7,
|
||||
WF_TYPE_GL16 = 8,
|
||||
WF_TYPE_GLR16 = 9,
|
||||
WF_TYPE_GLD16 = 10,
|
||||
WF_TYPE_GCC16 = 11,
|
||||
WF_TYPE_GRAY32 = 12,
|
||||
WF_TYPE_MAX = 13,
|
||||
};
|
||||
|
||||
enum waveform_type {
|
||||
RKF_WAVEFORM = 1,
|
||||
PVI_WAVEFORM = 2,
|
||||
OED_WAVEFORM = 3,
|
||||
};
|
||||
|
||||
enum pvi_wf_mode {
|
||||
PVI_WF_RESET = 0,
|
||||
PVI_WF_DU = 1,
|
||||
PVI_WF_DU4 = 2,
|
||||
PVI_WF_GC16 = 3,
|
||||
PVI_WF_GL16 = 4,
|
||||
PVI_WF_GLR16 = 5,
|
||||
PVI_WF_GLD16 = 6,
|
||||
PVI_WF_A2 = 7,
|
||||
PVI_WF_GCC16 = 8,
|
||||
PVI_WF_MAX,
|
||||
};
|
||||
|
||||
enum oed_wf_mode {
|
||||
OED_WF_RESET = 0,
|
||||
OED_WF_DU = 1,
|
||||
OED_WF_GC16 = 2,
|
||||
OED_WF_GU16 = 3,
|
||||
OED_WF_A2 = 4,
|
||||
OED_WF_GL16 = 5,
|
||||
OED_WF_A2IN = 6,
|
||||
OED_WF_A2OUT = 7,
|
||||
OED_WF_MAX,
|
||||
};
|
||||
|
||||
struct epd_lut_data {
|
||||
unsigned int frame_num;
|
||||
unsigned int *data;
|
||||
};
|
||||
|
||||
struct epd_lut_ops {
|
||||
int (*lut_get)(struct epd_lut_data *lut, enum epd_lut_type type,
|
||||
int temp);
|
||||
};
|
||||
|
||||
/*
|
||||
* EPD LUT module export symbols
|
||||
*/
|
||||
int epd_lut_from_mem_init(void *waveform, struct epd_lut_ops *ops);
|
||||
const char *epd_lut_get_wf_version(void);
|
||||
|
||||
/*
|
||||
* External functions
|
||||
*/
|
||||
int map_gray16_mode(void);
|
||||
int map_auto_mode(void);
|
||||
|
||||
/*
|
||||
* PVI Waveform Interfaces
|
||||
*/
|
||||
int pvi_wf_input(void *waveform_file);
|
||||
const char *pvi_wf_get_version(void);
|
||||
int pvi_wf_get_lut(struct epd_lut_data *output, enum epd_lut_type lut_type,
|
||||
int temperature);
|
||||
|
||||
/*
|
||||
* RKF Waveform Interfaces
|
||||
*/
|
||||
int rkf_wf_input(void *waveform_file);
|
||||
const char *rkf_wf_get_version(void);
|
||||
int rkf_wf_get_lut(struct epd_lut_data *output, enum epd_lut_type lut_type,
|
||||
int temperature);
|
||||
#endif
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* (C) Copyright 2020 Rockchip Electronics Co., Ltd
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
* Author: Wenping Zhang <wenping.zhang@rock-chips.com>
|
||||
*/
|
||||
|
||||
#ifndef RK_EBC_H
|
||||
#define RK_EBC_H
|
||||
|
||||
#include "epdlut/epd_lut.h"
|
||||
|
||||
struct ebc_panel {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 width_mm;
|
||||
u32 height_mm;
|
||||
|
||||
u32 sdck;
|
||||
u32 lsl;
|
||||
u32 lbl;
|
||||
u32 ldl;
|
||||
u32 lel;
|
||||
u32 gdck_sta;
|
||||
u32 lgonl;
|
||||
u32 fsl;
|
||||
u32 fbl;
|
||||
u32 fdl;
|
||||
u32 fel;
|
||||
u32 panel_16bit;
|
||||
u32 panel_color;
|
||||
u32 mirror;
|
||||
u32 disp_pbuf;
|
||||
u32 disp_pbuf_size;
|
||||
u32 *lut_pbuf;
|
||||
u32 lut_pbuf_size;
|
||||
struct epd_lut_data lut_data;
|
||||
struct epd_lut_ops lut_ops;
|
||||
};
|
||||
|
||||
struct rk_ebc_tcon_ops {
|
||||
int (*enable)(struct udevice *dev, struct ebc_panel *panel);
|
||||
int (*disable)(struct udevice *dev);
|
||||
int (*dsp_mode_set)(struct udevice *dev, int update_mode,
|
||||
int display_mode, int three_win_mode,
|
||||
int eink_mode);
|
||||
int (*image_addr_set)(struct udevice *dev, u32 pre_image_addr,
|
||||
u32 cur_image_addr);
|
||||
int (*frame_addr_set)(struct udevice *dev, u32 frame_addr);
|
||||
int (*lut_data_set)(struct udevice *dev, unsigned int *lut_data,
|
||||
int frame_count, int lut_32);
|
||||
int (*frame_start)(struct udevice *dev, int frame_total);
|
||||
int (*wait_for_last_frame_complete)(struct udevice *dev);
|
||||
};
|
||||
|
||||
#define ebc_tcon_get_ops(dev) ((struct rk_ebc_tcon_ops *)(dev)->driver->ops)
|
||||
|
||||
/*
|
||||
*interface for ebc power control
|
||||
*/
|
||||
struct rk_ebc_pwr_ops {
|
||||
int (*power_on)(struct udevice *dev);
|
||||
int (*power_down)(struct udevice *dev);
|
||||
int (*temp_get)(struct udevice *dev, u32 *temp);
|
||||
int (*vcom_set)(struct udevice *dev, u32 vcom);
|
||||
};
|
||||
|
||||
#define ebc_pwr_get_ops(dev) ((struct rk_ebc_pwr_ops *)(dev)->driver->ops)
|
||||
|
||||
//display mode define
|
||||
#define DIRECT_MODE 0
|
||||
#define LUT_MODE 1
|
||||
#define THREE_WIN_MODE 1
|
||||
#define EINK_MODE 1
|
||||
|
||||
#endif
|
|
@ -0,0 +1,529 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2020 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* Author: Wenping Zhang <wenping.zhang@rock-chips.com>
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <dm/pinctrl.h>
|
||||
#include <pwm.h>
|
||||
#include <regmap.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/arch/clock.h>
|
||||
#include <asm/io.h>
|
||||
#include <syscon.h>
|
||||
#include <linux/io.h>
|
||||
#include "rk_ebc.h"
|
||||
#ifdef CONFIG_IRQ
|
||||
#include <irq-generic.h>
|
||||
#endif
|
||||
|
||||
struct ebc_tcon_priv {
|
||||
struct udevice *dev;
|
||||
void __iomem *reg;
|
||||
u32 *regcache;
|
||||
u32 reg_len;
|
||||
void *grf;
|
||||
void *pmugrf;
|
||||
};
|
||||
|
||||
#define msleep(a) udelay((a) * 1000)
|
||||
#define HIWORD_UPDATE(x, l, h) (((x) << (l)) | (GENMASK(h, l) << 16))
|
||||
#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l)))
|
||||
|
||||
#define REG_LOAD_GLOBAL_EN 0x1
|
||||
|
||||
/* ebc register define */
|
||||
#define EBC_DSP_START 0x0000 //Frame statrt register
|
||||
#define EBC_EPD_CTRL 0x0004 //EPD control register
|
||||
#define EBC_DSP_CTRL 0x0008 //Display control register
|
||||
#define EBC_DSP_HTIMING0 0x000c //H-Timing setting register0
|
||||
#define EBC_DSP_HTIMING1 0x0010 //H-Timing setting register1
|
||||
#define EBC_DSP_VTIMING0 0x0014 //V-Timing setting register0
|
||||
#define EBC_DSP_VTIMING1 0x0018 //V-Timing setting register1
|
||||
#define EBC_DSP_ACT_INFO 0x001c //ACTIVE width/height
|
||||
#define EBC_WIN_CTRL 0x0020 //Window ctrl
|
||||
#define EBC_WIN_MST0 0x0024 //Current win memory start
|
||||
#define EBC_WIN_MST1 0x0028 //Next win memory start
|
||||
#define EBC_WIN_VIR 0x002c //Window vir width/height
|
||||
#define EBC_WIN_ACT 0x0030 //Window act width/height
|
||||
#define EBC_WIN_DSP 0x0034 //Window dsp width/height
|
||||
#define EBC_WIN_DSP_ST 0x0038 //Window display start point
|
||||
#define EBC_INT_STATUS 0x003c //Interrupt register
|
||||
#define EBC_VCOM0 0x0040 //VCOM setting register0
|
||||
#define EBC_VCOM1 0x0044 //VCOM setting register1
|
||||
#define EBC_VCOM2 0x0048 //VCOM setting register2
|
||||
#define EBC_VCOM3 0x004c //VCOM setting register3
|
||||
#define EBC_CONFIG_DONE 0x0050 //Config done register
|
||||
#define EBC_VNUM 0x0054 //Line flag num
|
||||
#define EBC_WIN_MST2 0x0058 //Framecount memory start
|
||||
#define EBC_LUT_DATA_ADDR 0x1000 //lut data address
|
||||
|
||||
#define DSP_HTOTAL(x) UPDATE(x, 27, 16)
|
||||
#define DSP_HS_END(x) UPDATE(x, 7, 0)
|
||||
#define DSP_HACT_END(x) UPDATE(x, 26, 16)
|
||||
#define DSP_HACT_ST(x) UPDATE(x, 7, 0)
|
||||
#define DSP_VTOTAL(x) UPDATE(x, 26, 16)
|
||||
#define DSP_VS_END(x) UPDATE(x, 7, 0)
|
||||
#define DSP_VACT_END(x) UPDATE(x, 26, 16)
|
||||
#define DSP_VACT_ST(x) UPDATE(x, 7, 0)
|
||||
#define DSP_HEIGHT(x) UPDATE(x, 26, 16)
|
||||
#define DSP_WIDTH(x) UPDATE(x, 11, 0)
|
||||
|
||||
#define WIN2_FIFO_ALMOST_FULL_LEVEL(x) UPDATE(x, 27, 19)
|
||||
#define WIN_EN(x) UPDATE(x, 18, 18)
|
||||
#define BURST_REG(x) UPDATE(x, 12, 10)
|
||||
#define WIN_FIFO_ALMOST_FULL_LEVEL(x) UPDATE(x, 9, 2)
|
||||
#define WIN_FMT(x) UPDATE(x, 1, 0)
|
||||
|
||||
#define WIN_VIR_HEIGHT(x) UPDATE(x, 31, 16)
|
||||
#define WIN_VIR_WIDTH(x) UPDATE(x, 15, 0)
|
||||
#define WIN_ACT_HEIGHT(x) UPDATE(x, 26, 16)
|
||||
#define WIN_ACT_WIDTH(x) UPDATE(x, 11, 0)
|
||||
#define WIN_DSP_HEIGHT(x) UPDATE(x, 26, 16)
|
||||
#define WIN_DSP_WIDTH(x) UPDATE(x, 11, 0)
|
||||
#define WIN_DSP_YST(x) UPDATE(x, 26, 16)
|
||||
#define WIN_DSP_XST(x) UPDATE(x, 11, 0)
|
||||
|
||||
#define DSP_OUT_LOW BIT(31)
|
||||
#define DSP_EINK_MODE(x) UPDATE(x, 13, 13)
|
||||
#define DSP_EINK_MODE_MASK BIT(13)
|
||||
#define DSP_SDCE_WIDTH(x) UPDATE(x, 25, 16)
|
||||
#define DSP_FRM_TOTAL(x) UPDATE(x, 9, 2)
|
||||
#define DSP_FRM_TOTAL_MASK GENMASK(9, 2)
|
||||
#define DSP_FRM_START BIT(0)
|
||||
#define DSP_FRM_START_MASK BIT(0)
|
||||
#define SW_BURST_CTRL BIT(12)
|
||||
|
||||
#define EINK_MODE_SWAP(x) UPDATE(x, 31, 31)
|
||||
#define EINK_MODE_FRM_SEL(x) UPDATE(x, 30, 30)
|
||||
#define DSP_GD_END(x) UPDATE(x, 26, 16)
|
||||
#define DSP_GD_ST(x) UPDATE(x, 15, 8)
|
||||
#define DSP_THREE_WIN_MODE(x) UPDATE(x, 7, 7)
|
||||
#define THREE_WIN_MODE_MASK BIT(7)
|
||||
#define DSP_SDDW_MODE(x) UPDATE(x, 6, 6)
|
||||
#define EPD_AUO(x) UPDATE(x, 5, 5)
|
||||
#define EPD_PWR(x) UPDATE(x, 4, 2)
|
||||
#define EPD_GDRL(x) UPDATE(x, 1, 1)
|
||||
#define EPD_SDSHR(x) UPDATE(x, 0, 0)
|
||||
|
||||
#define DSP_SWAP_MODE(x) UPDATE(x, 31, 30)
|
||||
#define DSP_SWAP_MODE_MASK GENMASK(31, 30)
|
||||
#define DSP_SDCLK_DIV(x) UPDATE(x, 19, 16)
|
||||
#define DSP_SDCLK_DIV_MASK GENMASK(19, 16)
|
||||
#define DSP_VCOM_MODE(x) UPDATE(x, 27, 27)
|
||||
#define DSP_VCOM_MODE_MASK BIT(27)
|
||||
|
||||
#define DSP_UPDATE_MODE(x) UPDATE(x, 29, 29)
|
||||
#define DSP_DISPLAY_MODE(x) UPDATE(x, 28, 28)
|
||||
#define UPDATE_MODE_MASK BIT(29)
|
||||
#define DISPLAY_MODE_MASK BIT(28)
|
||||
|
||||
#define DSP_FRM_INT_NUM(x) UPDATE(x, 19, 12)
|
||||
#define FRM_END_INT BIT(0)
|
||||
#define DSP_END_INT BIT(1)
|
||||
#define DSP_FRM_INT BIT(2)
|
||||
#define LINE_FLAG_INT BIT(3)
|
||||
#define FRM_END_INT_MASK BIT(4)
|
||||
#define DSP_END_INT_MASK BIT(5)
|
||||
#define DSP_FRM_INT_MASK BIT(6)
|
||||
#define LINE_FLAG_INT_MASK BIT(7)
|
||||
#define FRM_END_INT_CLR BIT(8)
|
||||
#define DSP_END_INT_CLR BIT(9)
|
||||
#define DSP_FRM_INT_CLR BIT(10)
|
||||
#define LINE_FLAG_INT_CLR BIT(11)
|
||||
|
||||
#define PMU_BASE_ADDR 0xfdd90000
|
||||
#define PMU_PWR_GATE_SFTCON 0xA0
|
||||
#define PMU_PWR_DWN_ST 0x98
|
||||
#define RGA_PD_OFF BIT(5)
|
||||
#define RGA_PD_STAT BIT(5)
|
||||
enum ebc_win_data_fmt {
|
||||
Y_DATA_4BPP = 0,
|
||||
Y_DATA_8BPP = 1,
|
||||
RGB888 = 2,
|
||||
RGB565 = 3,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_IRQ
|
||||
#define IRQ_EBC 49
|
||||
static volatile int frame_done;
|
||||
#endif
|
||||
static inline void regs_dump(struct ebc_tcon_priv *tcon)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf("dump registers:\n");
|
||||
for (i = 0; i <= EBC_WIN_MST2; i = i + 4) {
|
||||
if (!(i % 16))
|
||||
printf("\n 0x%p:\t", tcon->reg + i);
|
||||
printf("0x%x\t", readl(tcon->reg + i));
|
||||
}
|
||||
printf("\nlut data:\n");
|
||||
for (i = 0x1000; i <= 0x1100; i = i + 4) {
|
||||
if (!(i % 16))
|
||||
printf("\n 0x%p:\t", tcon->reg + i);
|
||||
printf("0x%x\t", readl(tcon->reg + i));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static int ebc_power_domain(int on)
|
||||
{
|
||||
u32 pd_reg;
|
||||
u32 pd_stat;
|
||||
int delay = 0;
|
||||
|
||||
if (on) {
|
||||
pd_reg = RGA_PD_OFF << 16;
|
||||
pd_stat = RGA_PD_STAT;
|
||||
} else {
|
||||
pd_reg = RGA_PD_OFF | (RGA_PD_OFF << 16);
|
||||
pd_stat = ~((u32)RGA_PD_STAT);
|
||||
}
|
||||
|
||||
/* enable rga pd for ebc tcon*/
|
||||
writel(pd_reg, PMU_BASE_ADDR + PMU_PWR_GATE_SFTCON);
|
||||
delay = 1000;
|
||||
do {
|
||||
udelay(1);
|
||||
delay--;
|
||||
if (delay == 0) {
|
||||
printf("Enable rga pd for ebc failed !\n");
|
||||
return -1;
|
||||
}
|
||||
} while (readl(PMU_BASE_ADDR + PMU_PWR_DWN_ST) & pd_stat);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void tcon_write(struct ebc_tcon_priv *tcon, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
unsigned int *cache = tcon->regcache + (reg >> 2);
|
||||
|
||||
writel(value, tcon->reg + reg);
|
||||
*cache = value;
|
||||
}
|
||||
|
||||
static inline unsigned int tcon_read(struct ebc_tcon_priv *tcon,
|
||||
unsigned int reg)
|
||||
{
|
||||
return readl(tcon->reg + reg);
|
||||
}
|
||||
|
||||
static inline void tcon_update_bits(struct ebc_tcon_priv *tcon,
|
||||
unsigned int reg, unsigned int mask,
|
||||
unsigned int val)
|
||||
{
|
||||
unsigned int tmp;
|
||||
unsigned int *cache = tcon->regcache + (reg >> 2);
|
||||
|
||||
tmp = *cache & ~mask;
|
||||
tmp |= val & mask;
|
||||
|
||||
writel(tmp, tcon->reg + reg);
|
||||
*cache = tmp;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IRQ
|
||||
static void ebc_irq_handler(int irq, void *data)
|
||||
{
|
||||
u32 intr_status;
|
||||
struct udevice *dev = data;
|
||||
struct ebc_tcon_priv *tcon = dev_get_priv(dev);
|
||||
|
||||
intr_status = readl(tcon->reg + EBC_INT_STATUS);
|
||||
|
||||
if (intr_status & DSP_END_INT) {
|
||||
tcon_update_bits(tcon, EBC_INT_STATUS,
|
||||
DSP_END_INT_CLR, DSP_END_INT_CLR);
|
||||
frame_done = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void tcon_cfg_done(struct ebc_tcon_priv *tcon)
|
||||
{
|
||||
writel(REG_LOAD_GLOBAL_EN, tcon->reg + EBC_CONFIG_DONE);
|
||||
}
|
||||
|
||||
static int ebc_tcon_enable(struct udevice *dev, struct ebc_panel *panel)
|
||||
{
|
||||
struct ebc_tcon_priv *tcon = dev_get_priv(dev);
|
||||
|
||||
/* panel timing and win info config */
|
||||
tcon_write(tcon, EBC_DSP_HTIMING0,
|
||||
DSP_HTOTAL(panel->lsl + panel->lbl + panel->ldl +
|
||||
panel->lel) | DSP_HS_END(panel->lsl + 2));
|
||||
tcon_write(tcon, EBC_DSP_HTIMING1,
|
||||
DSP_HACT_END(panel->lsl + panel->lbl + panel->ldl) |
|
||||
DSP_HACT_ST(panel->lsl + panel->lbl - 1));
|
||||
tcon_write(tcon, EBC_DSP_VTIMING0,
|
||||
DSP_VTOTAL(panel->fsl + panel->fbl + panel->fdl +
|
||||
panel->fel) | DSP_VS_END(panel->fsl));
|
||||
tcon_write(tcon, EBC_DSP_VTIMING1,
|
||||
DSP_VACT_END(panel->fsl + panel->fbl + panel->fdl) |
|
||||
DSP_VACT_ST(panel->fsl + panel->fbl));
|
||||
tcon_write(tcon, EBC_DSP_ACT_INFO,
|
||||
DSP_HEIGHT(panel->height) |
|
||||
DSP_WIDTH(panel->width));
|
||||
tcon_write(tcon, EBC_WIN_VIR,
|
||||
WIN_VIR_HEIGHT(panel->height) |
|
||||
WIN_VIR_WIDTH(panel->width));
|
||||
tcon_write(tcon, EBC_WIN_ACT,
|
||||
WIN_ACT_HEIGHT(panel->height) |
|
||||
WIN_ACT_WIDTH(panel->width));
|
||||
tcon_write(tcon, EBC_WIN_DSP,
|
||||
WIN_DSP_HEIGHT(panel->height) |
|
||||
WIN_DSP_WIDTH(panel->width));
|
||||
tcon_write(tcon, EBC_WIN_DSP_ST,
|
||||
WIN_DSP_YST(panel->fsl + panel->fbl) |
|
||||
WIN_DSP_XST(panel->lsl + panel->lbl));
|
||||
|
||||
/* win2 fifo is 512x128, win fifo is 256x128,
|
||||
* we set fifo almost value (fifo_size - 16)
|
||||
* burst_reg = 7 mean ahb burst is incr16
|
||||
*/
|
||||
tcon_write(tcon, EBC_WIN_CTRL,
|
||||
WIN2_FIFO_ALMOST_FULL_LEVEL(496) | WIN_EN(1) |
|
||||
BURST_REG(7) | WIN_FIFO_ALMOST_FULL_LEVEL(240) |
|
||||
WIN_FMT(Y_DATA_4BPP));
|
||||
|
||||
/*
|
||||
* EBC_EPD_CTRL info:
|
||||
* DSP_GD_ST: GCLK rising edge point(SCLK), which count from
|
||||
* the rasing edge of hsync(spec is wrong, count
|
||||
* from rasing edge of hsync, not falling edge of hsync)
|
||||
* DSP_GD_END : GCLK falling edge point(SCLK), which count from
|
||||
* the rasing edge of hsync
|
||||
* DSP_THREE_WIN_MODE: 0: lut mode or direct mode; 1: three win mode
|
||||
* DSP_SDDW_MODE: 0: 8 bit data output; 1: 16 bit data output
|
||||
* EPD_AUO: 0: EINK; 1:AUO
|
||||
* EPD_GDRL: gate scanning direction: 1:button to top 0:top to button
|
||||
* EPD_SDSHR: source scanning direction 1:right to left 0:left to right
|
||||
*/
|
||||
tcon_write(tcon, EBC_EPD_CTRL,
|
||||
EINK_MODE_SWAP(1) |
|
||||
DSP_GD_ST(panel->lsl + panel->gdck_sta) |
|
||||
DSP_GD_END(panel->lsl + panel->gdck_sta + panel->lgonl) |
|
||||
DSP_THREE_WIN_MODE(0) |
|
||||
DSP_SDDW_MODE(!!panel->panel_16bit) |
|
||||
EPD_AUO(0) |
|
||||
EPD_GDRL(1) |
|
||||
EPD_SDSHR(1));
|
||||
tcon_write(tcon, EBC_DSP_START,
|
||||
DSP_SDCE_WIDTH(panel->ldl) | SW_BURST_CTRL);
|
||||
|
||||
tcon_write(tcon, EBC_DSP_CTRL,
|
||||
DSP_SWAP_MODE(panel->panel_16bit ? 2 : 3) |
|
||||
DSP_VCOM_MODE(1) |
|
||||
DSP_SDCLK_DIV(panel->panel_16bit ? 7 : 3));
|
||||
|
||||
tcon_cfg_done(tcon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_tcon_disable(struct udevice *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_tcon_dsp_mode_set(struct udevice *dev, int update_mode,
|
||||
int display_mode, int three_win_mode,
|
||||
int eink_mode)
|
||||
{
|
||||
struct ebc_tcon_priv *tcon = dev_get_priv(dev);
|
||||
|
||||
tcon_update_bits(tcon, EBC_DSP_CTRL,
|
||||
UPDATE_MODE_MASK | DISPLAY_MODE_MASK,
|
||||
DSP_UPDATE_MODE(!!update_mode) |
|
||||
DSP_DISPLAY_MODE(!!display_mode));
|
||||
|
||||
tcon_update_bits(tcon, EBC_EPD_CTRL, THREE_WIN_MODE_MASK,
|
||||
DSP_THREE_WIN_MODE(!!three_win_mode));
|
||||
/* always set frm start bit 0 before real frame start */
|
||||
tcon_update_bits(tcon, EBC_DSP_START,
|
||||
DSP_EINK_MODE_MASK | DSP_FRM_START_MASK,
|
||||
DSP_EINK_MODE(!!eink_mode));
|
||||
tcon_cfg_done(tcon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_tcon_image_addr_set(struct udevice *dev, u32 pre_image_addr,
|
||||
u32 cur_image_addr)
|
||||
{
|
||||
struct ebc_tcon_priv *tcon = dev_get_priv(dev);
|
||||
|
||||
tcon_write(tcon, EBC_WIN_MST0, pre_image_addr);
|
||||
tcon_write(tcon, EBC_WIN_MST1, cur_image_addr);
|
||||
tcon_cfg_done(tcon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_tcon_frame_addr_set(struct udevice *dev, u32 frame_addr)
|
||||
{
|
||||
struct ebc_tcon_priv *tcon = dev_get_priv(dev);
|
||||
|
||||
tcon_write(tcon, EBC_WIN_MST2, frame_addr);
|
||||
tcon_cfg_done(tcon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_tcon_lut_data_set(struct udevice *dev, unsigned int *lut_data,
|
||||
int frame_count, int lut_32)
|
||||
{
|
||||
int i, lut_size;
|
||||
struct ebc_tcon_priv *tcon = dev_get_priv(dev);
|
||||
|
||||
if ((!lut_32 && frame_count > 256) || (lut_32 && frame_count > 64)) {
|
||||
dev_err(tcon->dev, "frame count over flow\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lut_32)
|
||||
lut_size = frame_count * 64;
|
||||
else
|
||||
lut_size = frame_count * 16;
|
||||
|
||||
for (i = 0; i < lut_size; i++)
|
||||
tcon_write(tcon, EBC_LUT_DATA_ADDR + (i * 4), lut_data[i]);
|
||||
|
||||
tcon_cfg_done(tcon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wait_for_last_frame_complete(struct udevice *dev)
|
||||
{
|
||||
#ifndef CONFIG_IRQ
|
||||
u32 intr_status;
|
||||
#endif
|
||||
struct ebc_tcon_priv *tcon = dev_get_priv(dev);
|
||||
|
||||
#ifdef CONFIG_IRQ
|
||||
while (!frame_done)
|
||||
msleep(1);
|
||||
frame_done = 0;
|
||||
#else
|
||||
/* wait for frame display end*/
|
||||
do {
|
||||
msleep(1);
|
||||
intr_status = readl(tcon->reg + EBC_INT_STATUS);
|
||||
} while (!(intr_status & DSP_END_INT));
|
||||
#endif
|
||||
|
||||
tcon_update_bits(tcon, EBC_INT_STATUS,
|
||||
DSP_END_INT_CLR, DSP_END_INT_CLR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_tcon_frame_start(struct udevice *dev, int frame_total)
|
||||
{
|
||||
struct ebc_tcon_priv *tcon = dev_get_priv(dev);
|
||||
|
||||
tcon_write(tcon, EBC_INT_STATUS,
|
||||
LINE_FLAG_INT_MASK | DSP_FRM_INT_MASK | FRM_END_INT_MASK);
|
||||
tcon_update_bits(tcon, EBC_DSP_START,
|
||||
DSP_FRM_TOTAL_MASK, DSP_FRM_TOTAL(frame_total - 1));
|
||||
tcon_cfg_done(tcon);
|
||||
|
||||
tcon_update_bits(tcon, EBC_DSP_START,
|
||||
DSP_FRM_START_MASK, DSP_FRM_START);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_ebc_tcon_probe(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct ebc_tcon_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/*Enable PD first*/
|
||||
ret = ebc_power_domain(1);
|
||||
if (ret) {
|
||||
printf("%s, enable pd failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
priv->dev = dev;
|
||||
ret = clk_set_defaults(dev);
|
||||
if (ret)
|
||||
printf("%s clk_set_defaults failed %d\n", __func__, ret);
|
||||
|
||||
#ifdef CONFIG_IRQ
|
||||
irq_install_handler(IRQ_EBC, ebc_irq_handler, dev);
|
||||
irq_handler_enable(IRQ_EBC);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct rk_ebc_tcon_ops ebc_tcon_funcs = {
|
||||
.enable = ebc_tcon_enable,
|
||||
.disable = ebc_tcon_disable,
|
||||
.dsp_mode_set = ebc_tcon_dsp_mode_set,
|
||||
.image_addr_set = ebc_tcon_image_addr_set,
|
||||
.frame_addr_set = ebc_tcon_frame_addr_set,
|
||||
.lut_data_set = ebc_tcon_lut_data_set,
|
||||
.frame_start = ebc_tcon_frame_start,
|
||||
.wait_for_last_frame_complete = wait_for_last_frame_complete,
|
||||
};
|
||||
|
||||
static int rk_ebc_tcon_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
fdt_size_t size;
|
||||
fdt_addr_t addr;
|
||||
struct ebc_tcon_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
|
||||
if (priv->grf <= 0) {
|
||||
debug("%s: Get syscon grf failed (ret=%p)\n",
|
||||
__func__, priv->grf);
|
||||
return -ENXIO;
|
||||
}
|
||||
priv->pmugrf = syscon_get_first_range(ROCKCHIP_SYSCON_PMUGRF);
|
||||
if (priv->pmugrf <= 0) {
|
||||
debug("%s: Get syscon pmugrf failed (ret=%p)\n",
|
||||
__func__, priv->grf);
|
||||
return -ENXIO;
|
||||
}
|
||||
addr = dev_read_addr_size(dev, "reg", &size);
|
||||
if (addr == FDT_ADDR_T_NONE) {
|
||||
debug("%s: Get ebc_tcon address failed\n", __func__);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
priv->reg = ioremap(addr, size);
|
||||
priv->reg_len = size;
|
||||
priv->regcache = malloc(size);
|
||||
memset(priv->regcache, 0, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id ebc_tcon_ids[] = {
|
||||
{ .compatible = "rockchip,rk3568-ebc-tcon" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rk_ebc_tcon) = {
|
||||
.name = "rk_ebc_tcon",
|
||||
.id = UCLASS_EBC,
|
||||
.of_match = ebc_tcon_ids,
|
||||
.ofdata_to_platdata = rk_ebc_tcon_ofdata_to_platdata,
|
||||
.probe = rk_ebc_tcon_probe,
|
||||
.ops = &ebc_tcon_funcs,
|
||||
.priv_auto_alloc_size = sizeof(struct ebc_tcon_priv),
|
||||
};
|
||||
|
||||
UCLASS_DRIVER(ebc_tcon) = {
|
||||
.id = UCLASS_EBC,
|
||||
.name = "ebc_tcon",
|
||||
};
|
||||
|
|
@ -0,0 +1,727 @@
|
|||
/*
|
||||
* (C) Copyright 2020 Rockchip Electronics Co., Ltd
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
* Author: Wenping Zhang <wenping.zhang@rock-chips.com>
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <mapmem.h>
|
||||
#include <stdlib.h>
|
||||
#include <asm/arch/vendor.h>
|
||||
#include <dm/of_access.h>
|
||||
#include <dm/uclass.h>
|
||||
#include <dm/uclass-id.h>
|
||||
#include <boot_rkimg.h>
|
||||
#include <rk_eink.h>
|
||||
#include "rk_ebc.h"
|
||||
#include "epdlut/epd_lut.h"
|
||||
|
||||
#define PART_WAVEFORM "waveform"
|
||||
#define EINK_LOGO_PART_MAGIC "RKEL"
|
||||
#define EINK_LOGO_IMAGE_MAGIC "GR04"
|
||||
/*
|
||||
* grayscale logo partition format:
|
||||
* block0:
|
||||
* struct logo_part_header part_header;
|
||||
* struct grayscale_header logo1_header;
|
||||
* struct grayscale_header logo2_header;
|
||||
* struct grayscale_header logo3_header;
|
||||
* struct grayscale_header logo4_header;
|
||||
* ....
|
||||
*
|
||||
* block 1:
|
||||
* logo1_image
|
||||
*
|
||||
* .....
|
||||
* block m:
|
||||
* logo2_image
|
||||
*
|
||||
* ........
|
||||
* block n:
|
||||
* logo3_image
|
||||
*
|
||||
* ........
|
||||
* block i:
|
||||
* logoi_image
|
||||
*/
|
||||
|
||||
//logo partition Header, 64byte
|
||||
struct logo_part_header {
|
||||
char magic[4]; /* must be "RKEL" */
|
||||
u32 totoal_size;
|
||||
u32 screen_width;
|
||||
u32 screen_height;
|
||||
u32 logo_count;
|
||||
u8 version[4];
|
||||
u32 rsv[10];
|
||||
} __packed;
|
||||
|
||||
// logo image header,32 byte
|
||||
struct grayscale_header {
|
||||
char magic[4]; /* must be "GR04" */
|
||||
u16 x;
|
||||
u16 y;
|
||||
u16 w;
|
||||
u16 h;
|
||||
u32 logo_type;
|
||||
u32 data_offset; /* image offset in byte */
|
||||
u32 data_size; /* image size in byte */
|
||||
u32 rsv[2];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* The start address of logo image in logo.img must be aligned in 512 bytes,
|
||||
* so the header size must be times of 512 bytes. Here we fix the size to 512
|
||||
* bytes, so the count of logo image can only support up to 14.
|
||||
*/
|
||||
struct logo_info {
|
||||
struct logo_part_header part_hdr;
|
||||
struct grayscale_header img_hdr[14];
|
||||
} __packed;
|
||||
|
||||
struct rockchip_eink_display_priv {
|
||||
struct udevice *dev;
|
||||
struct udevice *ebc_tcon_dev;
|
||||
struct udevice *ebc_pwr_dev;
|
||||
int vcom;
|
||||
};
|
||||
|
||||
enum {
|
||||
EBC_PWR_DOWN = 0,
|
||||
EBC_PWR_ON = 1,
|
||||
};
|
||||
|
||||
#define EINK_VCOM_ID 17
|
||||
#define EINK_VCOM_MAX 64
|
||||
#define VCOM_DEFAULT_VALUE 1650
|
||||
|
||||
static struct logo_info eink_logo_info;
|
||||
static struct udevice *eink_dev;
|
||||
static volatile int last_logo_type = -1;
|
||||
static int read_vcom_from_vendor(void)
|
||||
{
|
||||
int ret = 0;
|
||||
char vcom_str[EINK_VCOM_MAX] = {0};
|
||||
char vcom_args[EINK_VCOM_MAX] = {0};
|
||||
|
||||
/* Read vcom value from vendor storage part */
|
||||
ret = vendor_storage_read(EINK_VCOM_ID, vcom_str, (EINK_VCOM_MAX - 1));
|
||||
if (ret > 0) {
|
||||
snprintf(vcom_args, strlen(vcom_str) + 6, "vcom=%s", vcom_str);
|
||||
printf("eink update bootargs: %s\n", vcom_args);
|
||||
env_update("bootargs", vcom_args);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return atoi(vcom_str);
|
||||
}
|
||||
|
||||
static int read_waveform(struct udevice *dev)
|
||||
{
|
||||
int cnt, start, ret;
|
||||
disk_partition_t part;
|
||||
struct blk_desc *dev_desc;
|
||||
struct ebc_panel *plat = dev_get_platdata(dev);
|
||||
|
||||
dev_desc = rockchip_get_bootdev();
|
||||
if (!dev_desc) {
|
||||
printf("%s: Could not find device\n", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
if (part_get_info_by_name(dev_desc, PART_WAVEFORM, &part) < 0) {
|
||||
printf("Get waveform partition failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
cnt = plat->lut_pbuf_size / RK_BLK_SIZE;
|
||||
start = part.start;
|
||||
ret = blk_dread(dev_desc, start, cnt, (void *)plat->lut_pbuf);
|
||||
if (ret != cnt)
|
||||
printf("Try to read %d blocks failed, only read %d\n",
|
||||
cnt, ret);
|
||||
|
||||
flush_dcache_range((ulong)plat->lut_pbuf,
|
||||
ALIGN((ulong)plat->lut_pbuf + cnt,
|
||||
CONFIG_SYS_CACHELINE_SIZE));
|
||||
ret = epd_lut_from_mem_init(plat->lut_pbuf, &plat->lut_ops);
|
||||
if (ret < 0) {
|
||||
printf("lut init failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 aligned_image_size_4k(struct udevice *dev)
|
||||
{
|
||||
struct ebc_panel *plat = dev_get_platdata(dev);
|
||||
u32 w = plat->width;
|
||||
u32 h = plat->height;
|
||||
|
||||
return ALIGN((w * h) >> 1, 4096);
|
||||
}
|
||||
|
||||
/*
|
||||
* This driver load the grayscale image from flash,
|
||||
* and put it in the reserve memory which define in dts:
|
||||
* display_reserved: framebuffer@10000000 {
|
||||
* reg = <0x0 0x10000000 0x0 0x2000000>;
|
||||
* no-map;
|
||||
* };
|
||||
* Every image logo size must be aligned in 4K, make sure
|
||||
* kernel can use it rightly, the buffer of LOGO image is
|
||||
* put in order of below map:
|
||||
* |---uboot logo ---|
|
||||
* |---kernel logo ---|
|
||||
* |---charge_0 logo ---|
|
||||
* |---charge_1 logo ---|
|
||||
* |---charge_2 logo ---|
|
||||
* |---charge_3 logo ---|
|
||||
* |---charge_4 logo ---|
|
||||
* |---charge_5 logo ---|
|
||||
* |---battery low logo---|
|
||||
*/
|
||||
static int get_addr_by_type(struct udevice *dev, u32 logo_type)
|
||||
{
|
||||
u32 offset, indx, img_size;
|
||||
struct ebc_panel *plat = dev_get_platdata(dev);
|
||||
|
||||
if (plat->disp_pbuf_size == 0 || !plat->disp_pbuf) {
|
||||
printf("invalid display buffer, please check dts\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
indx = ffs(logo_type) - 1;
|
||||
img_size = aligned_image_size_4k(dev);
|
||||
offset = img_size * indx;
|
||||
if (offset + img_size > plat->disp_pbuf_size) {
|
||||
printf("reserve display memory size is not enough\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (logo_type) {
|
||||
case EINK_LOGO_UBOOT:
|
||||
case EINK_LOGO_KERNEL:
|
||||
case EINK_LOGO_CHARGING_0:
|
||||
case EINK_LOGO_CHARGING_1:
|
||||
case EINK_LOGO_CHARGING_2:
|
||||
case EINK_LOGO_CHARGING_3:
|
||||
case EINK_LOGO_CHARGING_4:
|
||||
case EINK_LOGO_CHARGING_5:
|
||||
case EINK_LOGO_CHARGING_LOWPOWER:
|
||||
return (plat->disp_pbuf + offset);
|
||||
default:
|
||||
printf("invalid logo type[%d]\n", logo_type);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int read_header(struct blk_desc *dev_desc,
|
||||
disk_partition_t *part,
|
||||
struct logo_info *header)
|
||||
{
|
||||
int i;
|
||||
struct logo_part_header *part_hdr = &header->part_hdr;
|
||||
|
||||
if (blk_dread(dev_desc, part->start, 1, header) != 1)
|
||||
return -EIO;
|
||||
|
||||
if (memcmp(part_hdr->magic, EINK_LOGO_PART_MAGIC, 4)) {
|
||||
printf("partition header is invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (part_hdr->logo_count == 0) {
|
||||
printf("the count of logo image is 0\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
for (i = 0; i < part_hdr->logo_count; i++) {
|
||||
struct grayscale_header *img_hdr = &header->img_hdr[i];
|
||||
|
||||
if (memcmp(img_hdr->magic, EINK_LOGO_IMAGE_MAGIC, 4)) {
|
||||
printf("image[%d] header '%s' is invalid\n", i,
|
||||
img_hdr->magic);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_grayscale(struct blk_desc *dev_desc,
|
||||
disk_partition_t *part, u32 offset,
|
||||
u32 size, void *buf)
|
||||
{
|
||||
u32 blk_start, blk_offset, blk_count;
|
||||
|
||||
blk_offset = DIV_ROUND_UP(offset, dev_desc->blksz);
|
||||
blk_start = part->start + blk_offset;
|
||||
blk_count = DIV_ROUND_UP(size, dev_desc->blksz);
|
||||
|
||||
debug("blk_offset=%d, blk_start=%d,blk_count=%d,out buf=%p\n",
|
||||
blk_offset, blk_start, blk_count, buf);
|
||||
if (blk_dread(dev_desc, blk_start, blk_count, buf) != blk_count) {
|
||||
printf("read grayscale data failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The eink kernel driver need last frame to do part refresh,
|
||||
* so we need to transfer two images to kernel, which is kernel
|
||||
* logo and the logo displayed in uboot.
|
||||
*
|
||||
* this function use logo type bitmap to indicate several logo.
|
||||
* u32 needed_logo: we only load needed logo image into ram, such as
|
||||
* uboot logo + kernel logo or charger logo + kernel
|
||||
* logo
|
||||
* u32 *real_logo: because the needed logo may not exist in logo.img,
|
||||
* so have really loaded logo in para loaded_logo.
|
||||
*/
|
||||
static int read_needed_logo_from_partition(struct udevice *dev,
|
||||
u32 needed_logo,
|
||||
u32 *loaded_logo)
|
||||
{
|
||||
int ret, i;
|
||||
disk_partition_t part;
|
||||
struct blk_desc *dev_desc;
|
||||
struct logo_info *hdr = &eink_logo_info;
|
||||
struct logo_part_header *part_hdr = &hdr->part_hdr;
|
||||
struct ebc_panel *panel = dev_get_platdata(dev);
|
||||
|
||||
dev_desc = rockchip_get_bootdev();
|
||||
if (!dev_desc) {
|
||||
printf("%s: Could not find device\n", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (part_get_info_by_name(dev_desc, PART_LOGO, &part) < 0)
|
||||
return -ENODEV;
|
||||
|
||||
ret = read_header(dev_desc, &part, hdr);
|
||||
if (ret < 0) {
|
||||
printf("eink logo read header failed,ret = %d\n", ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (part_hdr->screen_width != panel->width ||
|
||||
part_hdr->screen_height != panel->height){
|
||||
printf("logo size(%dx%d) is not same as screen size(%dx%d)\n",
|
||||
part_hdr->screen_width, part_hdr->screen_height,
|
||||
panel->width, panel->height);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*loaded_logo = 0;
|
||||
for (i = 0; i < part_hdr->logo_count; i++) {
|
||||
struct grayscale_header *img_hdr = &hdr->img_hdr[i];
|
||||
int pic_buf;
|
||||
u32 offset = img_hdr->data_offset;
|
||||
u32 size = img_hdr->data_size;
|
||||
u32 logo_type = img_hdr->logo_type;
|
||||
|
||||
debug("offset=0x%x, size=%d,logo_type=%d,w=%d,h=%d\n",
|
||||
offset, size, logo_type, img_hdr->w, img_hdr->h);
|
||||
|
||||
if (needed_logo & logo_type) {
|
||||
pic_buf = get_addr_by_type(dev, logo_type);
|
||||
|
||||
if (pic_buf <= 0) {
|
||||
printf("Get buffer failed for image %d\n",
|
||||
img_hdr->logo_type);
|
||||
return -EIO;
|
||||
}
|
||||
if (!IS_ALIGNED((ulong)pic_buf,
|
||||
ARCH_DMA_MINALIGN)) {
|
||||
printf("disp buffer is not dma aligned\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
read_grayscale(dev_desc, &part, offset, size,
|
||||
(void *)((ulong)pic_buf));
|
||||
flush_dcache_range((ulong)pic_buf,
|
||||
ALIGN((ulong)pic_buf + size,
|
||||
CONFIG_SYS_CACHELINE_SIZE));
|
||||
*loaded_logo |= logo_type;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_power_set(struct udevice *dev, int is_on)
|
||||
{
|
||||
int ret;
|
||||
struct rockchip_eink_display_priv *priv = dev_get_priv(dev);
|
||||
struct ebc_panel *panel = dev_get_platdata(dev);
|
||||
struct udevice *ebc_tcon_dev = priv->ebc_tcon_dev;
|
||||
struct rk_ebc_tcon_ops *ebc_tcon_ops = ebc_tcon_get_ops(ebc_tcon_dev);
|
||||
struct udevice *ebc_pwr_dev = priv->ebc_pwr_dev;
|
||||
struct rk_ebc_pwr_ops *pwr_ops = ebc_pwr_get_ops(ebc_pwr_dev);
|
||||
|
||||
if (is_on) {
|
||||
ret = ebc_tcon_ops->enable(ebc_tcon_dev, panel);
|
||||
if (ret) {
|
||||
printf("%s, ebc tcon enabled failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
ret = pwr_ops->power_on(ebc_pwr_dev);
|
||||
if (ret) {
|
||||
printf("%s, power on failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
ret = pwr_ops->power_down(ebc_pwr_dev);
|
||||
if (ret) {
|
||||
printf("%s, power_down failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
ret = ebc_tcon_ops->disable(ebc_tcon_dev);
|
||||
if (ret) {
|
||||
printf("%s, ebc tcon disable failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eink_display(struct udevice *dev, u32 pre_img_buf,
|
||||
u32 cur_img_buf, u32 lut_type, int update_mode)
|
||||
{
|
||||
u32 temperature, frame_num;
|
||||
struct rockchip_eink_display_priv *priv = dev_get_priv(dev);
|
||||
struct ebc_panel *plat = dev_get_platdata(dev);
|
||||
struct epd_lut_ops *lut_ops = &plat->lut_ops;
|
||||
struct udevice *ebc_pwr_dev = priv->ebc_pwr_dev;
|
||||
struct rk_ebc_pwr_ops *pwr_ops = ebc_pwr_get_ops(ebc_pwr_dev);
|
||||
struct udevice *ebc_tcon_dev = priv->ebc_tcon_dev;
|
||||
struct rk_ebc_tcon_ops *ebc_tcon_ops = ebc_tcon_get_ops(ebc_tcon_dev);
|
||||
|
||||
pwr_ops->temp_get(ebc_pwr_dev, &temperature);
|
||||
if (temperature <= 0 || temperature > 50) {
|
||||
printf("temperature = %d, out of range0~50 ,use 25\n",
|
||||
temperature);
|
||||
temperature = 25;
|
||||
}
|
||||
|
||||
if (!lut_ops->lut_get) {
|
||||
printf("get lut ops failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
lut_ops->lut_get(&plat->lut_data, lut_type, temperature);
|
||||
frame_num = plat->lut_data.frame_num;
|
||||
debug("lut_type=%d, frame num=%d, temp=%d\n", lut_type,
|
||||
frame_num, temperature);
|
||||
|
||||
ebc_tcon_ops->lut_data_set(ebc_tcon_dev, plat->lut_data.data,
|
||||
frame_num, 0);
|
||||
ebc_tcon_ops->dsp_mode_set(ebc_tcon_dev, update_mode,
|
||||
LUT_MODE, !THREE_WIN_MODE, !EINK_MODE);
|
||||
ebc_tcon_ops->image_addr_set(ebc_tcon_dev, pre_img_buf, cur_img_buf);
|
||||
ebc_tcon_ops->frame_start(ebc_tcon_dev, frame_num);
|
||||
ebc_tcon_ops->wait_for_last_frame_complete(ebc_tcon_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk_eink_display_init(void)
|
||||
{
|
||||
int ret;
|
||||
struct uclass *uc;
|
||||
struct udevice *dev;
|
||||
|
||||
if (eink_dev) {
|
||||
printf("ebc-dev is already initialized!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = uclass_get(UCLASS_EINK_DISPLAY, &uc);
|
||||
if (ret) {
|
||||
printf("can't find uclass eink\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
for (uclass_first_device(UCLASS_EINK_DISPLAY, &dev);
|
||||
dev; uclass_next_device(&dev))
|
||||
;
|
||||
|
||||
if (eink_dev) {
|
||||
printf("ebc-dev is probed success!\n");
|
||||
return 0;
|
||||
}
|
||||
printf("Can't find ebc-dev\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Eink display need current and previous image buffer, We assume
|
||||
* every type of logo has only one image, so just tell this function
|
||||
* last logo type and current logo type, it will find the right images.
|
||||
* last_logo_type: -1 means it's first displaying.
|
||||
*/
|
||||
static int rockchip_eink_show_logo(int cur_logo_type, int update_mode)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 read_logo = 0;
|
||||
u32 logo_addr;
|
||||
u32 last_logo_addr;
|
||||
struct ebc_panel *plat;
|
||||
void *reset_disp_addr = NULL;
|
||||
struct udevice *dev;
|
||||
|
||||
if (!eink_dev) {
|
||||
ret = rk_eink_display_init();
|
||||
if (ret) {
|
||||
printf("Get ebc dev failed, check dts configs\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
dev = eink_dev;
|
||||
|
||||
/*Don't need to update display*/
|
||||
if (last_logo_type == cur_logo_type) {
|
||||
debug("Same as last picture, Don't need to display\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
plat = dev_get_platdata(dev);
|
||||
|
||||
ret = ebc_power_set(dev, EBC_PWR_ON);
|
||||
if (ret) {
|
||||
printf("Eink power on failed\n");
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* The last_logo_type is 0 means it's first displaying
|
||||
*/
|
||||
if (last_logo_type == -1) {
|
||||
int size = (plat->width * plat->height) >> 1;
|
||||
|
||||
reset_disp_addr = memalign(ARCH_DMA_MINALIGN, size);
|
||||
memset(reset_disp_addr, 0xff, size);
|
||||
last_logo_addr = (u32)(u64)reset_disp_addr;
|
||||
eink_display(dev, last_logo_addr, last_logo_addr,
|
||||
WF_TYPE_RESET, 0);
|
||||
last_logo_type = 0;
|
||||
} else {
|
||||
last_logo_addr = get_addr_by_type(dev, last_logo_type);
|
||||
if (last_logo_addr < 0) {
|
||||
printf("Invalid last logo addr, exit!\n");
|
||||
goto out;
|
||||
}
|
||||
if (cur_logo_type == EINK_LOGO_RESET) {
|
||||
int size = (plat->width * plat->height) >> 1;
|
||||
|
||||
reset_disp_addr = memalign(ARCH_DMA_MINALIGN, size);
|
||||
memset(reset_disp_addr, 0xff, size);
|
||||
eink_display(dev, last_logo_addr,
|
||||
(u32)(u64)reset_disp_addr,
|
||||
WF_TYPE_GC16, update_mode);
|
||||
last_logo_type = -1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
ret = read_needed_logo_from_partition(dev, cur_logo_type,
|
||||
&read_logo);
|
||||
if (ret || !(read_logo & cur_logo_type)) {
|
||||
printf("read uboot logo failed, read_logo=%d\n", read_logo);
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
logo_addr = get_addr_by_type(dev, cur_logo_type);
|
||||
debug("logo_addr=%x, logo_type=%d\n", logo_addr, cur_logo_type);
|
||||
if (logo_addr <= 0) {
|
||||
printf("get logo buffer failed\n");
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
eink_display(dev, last_logo_addr, logo_addr, WF_TYPE_GC16, update_mode);
|
||||
last_logo_type = cur_logo_type;
|
||||
/*
|
||||
* System will boot up to kernel only when the
|
||||
* logo is uboot logo
|
||||
*/
|
||||
if (cur_logo_type == EINK_LOGO_UBOOT) {
|
||||
char logo_args[64] = {0};
|
||||
|
||||
printf("Transmit uboot logo addr(0x%x) to kernel\n", logo_addr);
|
||||
sprintf(logo_args, "ulogo_addr=0x%x", logo_addr);
|
||||
env_update("bootargs", logo_args);
|
||||
ret = read_needed_logo_from_partition(dev, EINK_LOGO_KERNEL,
|
||||
&read_logo);
|
||||
if (ret || !(read_logo & EINK_LOGO_KERNEL)) {
|
||||
printf("No invalid kernel logo in logo.img\n");
|
||||
} else {
|
||||
int klogo_addr = get_addr_by_type(dev,
|
||||
EINK_LOGO_KERNEL);
|
||||
|
||||
if (klogo_addr <= 0) {
|
||||
printf("get kernel logo buffer failed\n");
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
printf("Transmit kernel logo addr(0x%x) to kernel\n",
|
||||
klogo_addr);
|
||||
sprintf(logo_args, "klogo_addr=0x%x", klogo_addr);
|
||||
env_update("bootargs", logo_args);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (reset_disp_addr)
|
||||
free(reset_disp_addr);
|
||||
ret = ebc_power_set(dev, EBC_PWR_DOWN);
|
||||
if (ret)
|
||||
printf("Eink power down failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rockchip_eink_show_uboot_logo(void)
|
||||
{
|
||||
return rockchip_eink_show_logo(EINK_LOGO_UBOOT, EINK_UPDATE_DIFF);
|
||||
}
|
||||
|
||||
int rockchip_eink_show_charge_logo(int logo_type)
|
||||
{
|
||||
return rockchip_eink_show_logo(logo_type, EINK_UPDATE_DIFF);
|
||||
}
|
||||
|
||||
static int rockchip_eink_display_probe(struct udevice *dev)
|
||||
{
|
||||
int ret, vcom;
|
||||
struct rockchip_eink_display_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/* Before relocation we don't need to do anything */
|
||||
if (!(gd->flags & GD_FLG_RELOC))
|
||||
return 0;
|
||||
|
||||
ret = uclass_get_device_by_phandle(UCLASS_EBC, dev,
|
||||
"ebc_tcon",
|
||||
&priv->ebc_tcon_dev);
|
||||
if (ret && ret != -ENOENT) {
|
||||
dev_err(dev, "Cannot get ebc_tcon: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uclass_get_device_by_phandle(UCLASS_I2C_GENERIC, dev,
|
||||
"pmic",
|
||||
&priv->ebc_pwr_dev);
|
||||
if (ret && ret != -ENOENT) {
|
||||
dev_err(dev, "Cannot get pmic: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vcom = read_vcom_from_vendor();
|
||||
if (vcom <= 0) {
|
||||
printf("read vcom from vendor failed, use default vcom\n");
|
||||
priv->vcom = VCOM_DEFAULT_VALUE;
|
||||
} else {
|
||||
priv->vcom = vcom;
|
||||
}
|
||||
|
||||
if (priv->ebc_pwr_dev) {
|
||||
struct rk_ebc_pwr_ops *pwr_ops;
|
||||
|
||||
pwr_ops = ebc_pwr_get_ops(priv->ebc_pwr_dev);
|
||||
ret = pwr_ops->vcom_set(priv->ebc_pwr_dev, priv->vcom);
|
||||
if (ret) {
|
||||
printf("%s, vcom_set failed\n", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
// read lut to ram, and get lut ops
|
||||
ret = read_waveform(dev);
|
||||
if (ret < 0) {
|
||||
printf("read wavform failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
eink_dev = dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rockchip_eink_display_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
fdt_size_t size;
|
||||
fdt_addr_t tmp_addr;
|
||||
struct device_node *disp_mem;
|
||||
struct device_node *waveform_mem;
|
||||
struct ebc_panel *plat = dev_get_platdata(dev);
|
||||
|
||||
plat->width = dev_read_u32_default(dev, "panel,width", 0);
|
||||
plat->height = dev_read_u32_default(dev, "panel,height", 0);
|
||||
plat->sdck = dev_read_u32_default(dev, "panel,sdck", 0);
|
||||
plat->lsl = dev_read_u32_default(dev, "panel,lsl", 0);
|
||||
plat->lbl = dev_read_u32_default(dev, "panel,lbl", 0);
|
||||
plat->ldl = dev_read_u32_default(dev, "panel,ldl", 0);
|
||||
plat->lel = dev_read_u32_default(dev, "panel,lel", 0);
|
||||
plat->gdck_sta = dev_read_u32_default(dev, "panel,gdck-sta", 0);
|
||||
plat->lgonl = dev_read_u32_default(dev, "panel,lgonl", 0);
|
||||
plat->fsl = dev_read_u32_default(dev, "panel,fsl", 0);
|
||||
plat->fbl = dev_read_u32_default(dev, "panel,fbl", 0);
|
||||
plat->fdl = dev_read_u32_default(dev, "panel,fdl", 0);
|
||||
plat->fel = dev_read_u32_default(dev, "panel,fel", 0);
|
||||
plat->panel_16bit = dev_read_u32_default(dev, "panel,panel_16bit", 0);
|
||||
plat->panel_color = dev_read_u32_default(dev, "panel,panel_color", 0);
|
||||
plat->mirror = dev_read_u32_default(dev, "panel,mirror", 0);
|
||||
plat->width_mm = dev_read_u32_default(dev, "panel,width-mm", 0);
|
||||
plat->height_mm = dev_read_u32_default(dev, "panel,height-mm", 0);
|
||||
|
||||
disp_mem = of_parse_phandle(ofnode_to_np(dev_ofnode(dev)),
|
||||
"memory-region", 0);
|
||||
if (!disp_mem) {
|
||||
dev_err(dev, "Cannot get memory-region from dts\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
tmp_addr = ofnode_get_addr_size(np_to_ofnode(disp_mem), "reg", &size);
|
||||
if (tmp_addr == FDT_ADDR_T_NONE) {
|
||||
printf("get display memory address failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
plat->disp_pbuf = (u64)map_sysmem(tmp_addr, 0);
|
||||
plat->disp_pbuf_size = size;
|
||||
debug("display mem=0x%x, size=%x\n", plat->disp_pbuf,
|
||||
plat->disp_pbuf_size);
|
||||
waveform_mem = of_parse_phandle(ofnode_to_np(dev_ofnode(dev)),
|
||||
"waveform-region", 0);
|
||||
if (!waveform_mem) {
|
||||
printf("Cannot get waveform-region from dts\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
tmp_addr = ofnode_get_addr_size(np_to_ofnode(waveform_mem),
|
||||
"reg", &size);
|
||||
if (tmp_addr == FDT_ADDR_T_NONE) {
|
||||
printf("get waveform memory address failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
plat->lut_pbuf = map_sysmem(tmp_addr, 0);
|
||||
plat->lut_pbuf_size = size;
|
||||
debug("lut mem=0x%p, size=%x\n", plat->lut_pbuf, plat->lut_pbuf_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id rockchip_eink_display_ids[] = {
|
||||
{ .compatible = "rockchip,ebc-dev", },
|
||||
{}
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rk_eink_display) = {
|
||||
.name = "rockchip_eink_display",
|
||||
.id = UCLASS_EINK_DISPLAY,
|
||||
.of_match = rockchip_eink_display_ids,
|
||||
.ofdata_to_platdata = rockchip_eink_display_ofdata_to_platdata,
|
||||
.probe = rockchip_eink_display_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct rockchip_eink_display_priv),
|
||||
.platdata_auto_alloc_size = sizeof(struct ebc_panel),
|
||||
};
|
||||
|
||||
UCLASS_DRIVER(rk_eink) = {
|
||||
.id = UCLASS_EINK_DISPLAY,
|
||||
.name = "rk_eink",
|
||||
};
|
||||
|
|
@ -0,0 +1,461 @@
|
|||
/*
|
||||
* (C) Copyright 2020 Rockchip Electronics Co., Ltd
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <i2c.h>
|
||||
#include <asm/gpio.h>
|
||||
#include "rk_ebc.h"
|
||||
|
||||
#define msleep(a) udelay((a) * 1000)
|
||||
|
||||
struct tps65185_priv_data {
|
||||
struct udevice *dev;
|
||||
struct gpio_desc pwr_up_gpio;
|
||||
struct gpio_desc vcom_gpio;
|
||||
struct gpio_desc wake_up_gpio;
|
||||
u8 rev_id;
|
||||
u8 vadj;
|
||||
u8 vcom1;
|
||||
u8 vcom2;
|
||||
u8 upseq0;
|
||||
u8 upseq1;
|
||||
u8 dwnseq0;
|
||||
u8 dwnseq1;
|
||||
u8 shadow_en;
|
||||
};
|
||||
|
||||
#define REG_TMST_VALUE 0x00
|
||||
#define REG_ENABLE 0x01
|
||||
#define REG_VADJ 0x02
|
||||
#define REG_VCOM1_ADJUST 0x03
|
||||
#define REG_VCOM2_ADJUST 0x04
|
||||
#define REG_INT_ENABLE1 0x05
|
||||
#define REG_INT_ENABLE2 0x06
|
||||
#define REG_INT_STATUS1 0x07
|
||||
#define REG_INT_STATUS2 0x08
|
||||
#define REG_UPSEQ0 0x09
|
||||
#define REG_UPSEQ1 0x0a
|
||||
#define REG_DWNSEQ0 0x0b
|
||||
#define REG_DWNSEQ1 0x0c
|
||||
#define REG_TMST1 0x0d
|
||||
#define REG_TMST2 0x0e
|
||||
#define REG_PG_STATUS 0x0f
|
||||
#define REG_REVID 0x10
|
||||
#define mv_to_vcom1_reg(mv) (((mv) / 10) & 0xff)
|
||||
#define mv_to_vcom2_reg(mv) ((((mv) / 10) & 0x100) >> 8)
|
||||
|
||||
/*
|
||||
* After waking up from sleep, Papyrus
|
||||
* waits for VN to be discharged and all
|
||||
* voltage ref to startup before loading
|
||||
* the default EEPROM settings. So accessing
|
||||
* registers too early after WAKEUP could
|
||||
* cause the register to be overridden by
|
||||
* default values
|
||||
*/
|
||||
#define PAPYRUS_EEPROM_DELAY_MS 50
|
||||
/*
|
||||
* Papyrus WAKEUP pin must stay low for
|
||||
* a minimum time
|
||||
*/
|
||||
#define PAPYRUS_SLEEP_MINIMUM_MS 110
|
||||
/*
|
||||
* Temp sensor might take a little time to
|
||||
* settle even though the status bit in TMST1
|
||||
* state conversion is done - if read too early
|
||||
* 0C will be returned instead of the right temp
|
||||
*/
|
||||
#define PAPYRUS_TEMP_READ_TIME_MS 10
|
||||
|
||||
/*
|
||||
* Powerup sequence takes at least 24 ms
|
||||
* - no need to poll too frequently
|
||||
*/
|
||||
#define HW_GET_STATE_INTERVAL_MS 24
|
||||
|
||||
#define SEQ_VDD(index) (((index) % 4) << 6)
|
||||
#define SEQ_VPOS(index) (((index) % 4) << 4)
|
||||
#define SEQ_VEE(index) (((index) % 4) << 2)
|
||||
#define SEQ_VNEG(index) (((index) % 4) << 0)
|
||||
|
||||
/* power up seq delay time */
|
||||
#define UDLY_3ms(index) (0x00 << (((index) % 4) * 2))
|
||||
#define UDLY_6ms(index) (0x01 << (((index) % 4) * 2))
|
||||
#define UDLY_9ms(index) (0x10 << (((index) % 4) * 2))
|
||||
#define UDLY_12ms(index) (0x11 << (((index) % 4) * 2))
|
||||
|
||||
/* power down seq delay time */
|
||||
#define DDLY_6ms(index) (0x00 << (((index) % 4) * 2))
|
||||
#define DDLY_12ms(index) (0x01 << (((index) % 4) * 2))
|
||||
#define DDLY_24ms(index) (0x10 << (((index) % 4) * 2))
|
||||
#define DDLY_48ms(index) (0x11 << (((index) % 4) * 2))
|
||||
|
||||
#define NUMBER_PMIC_REGS 10
|
||||
// INT_ENABLE1
|
||||
#define PAPYRUS_INT_ENABLE1_ACQC_EN 1
|
||||
#define PAPYRUS_INT_ENABLE1_PRGC_EN 0
|
||||
|
||||
// INT_STATUS1
|
||||
#define PAPYRUS_INT_STATUS1_ACQC 1
|
||||
#define PAPYRUS_INT_STATUS1_PRGC 0
|
||||
|
||||
// VCOM2_ADJUST
|
||||
#define PAPYRUS_VCOM2_ACQ 7
|
||||
#define PAPYRUS_VCOM2_PROG 6
|
||||
#define PAPYRUS_VCOM2_HIZ 5
|
||||
#define V3P3_EN_MASK 0x20
|
||||
|
||||
#define PAPYRUS_V3P3OFF_DELAY_MS 20//100
|
||||
|
||||
static struct udevice *pmic_dev;
|
||||
|
||||
int tps65185_i2c_write(struct tps65185_priv_data *priv_data, u8 reg, u8 val)
|
||||
{
|
||||
int ret;
|
||||
u8 buf[2];
|
||||
struct i2c_msg msg;
|
||||
struct dm_i2c_chip *chip = dev_get_parent_platdata(priv_data->dev);
|
||||
|
||||
buf[0] = reg;
|
||||
buf[1] = val;
|
||||
msg.addr = chip->chip_addr;
|
||||
msg.flags = 0;
|
||||
msg.len = 2;
|
||||
msg.buf = buf;
|
||||
|
||||
ret = dm_i2c_xfer(priv_data->dev, &msg, 1);
|
||||
if (ret) {
|
||||
printf("tps65185 i2c write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tps65185_i2c_read(struct tps65185_priv_data *priv_data, u8 reg, u8 *val)
|
||||
{
|
||||
int ret;
|
||||
u8 data;
|
||||
struct dm_i2c_chip *chip = dev_get_parent_platdata(priv_data->dev);
|
||||
struct i2c_msg msg[] = {
|
||||
{
|
||||
.addr = chip->chip_addr,
|
||||
.flags = 0,
|
||||
.buf = (u8 *)®,
|
||||
.len = 1,
|
||||
}, {
|
||||
.addr = chip->chip_addr,
|
||||
.flags = I2C_M_RD,
|
||||
.buf = (u8 *)&data,
|
||||
.len = 1,
|
||||
}
|
||||
};
|
||||
|
||||
ret = dm_i2c_xfer(priv_data->dev, msg, 2);
|
||||
if (ret) {
|
||||
printf("tps65185 i2c read failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*val = data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tps65185_dump_registers(void)
|
||||
{
|
||||
u8 i, reg = 0;
|
||||
struct tps65185_priv_data *priv_data = dev_get_priv(pmic_dev);
|
||||
|
||||
for (i = 0; i <= REG_REVID; i++) {
|
||||
tps65185_i2c_read(priv_data, i, ®);
|
||||
printf("0x%x\t", reg);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static int tps65185_read_vcom_value(struct tps65185_priv_data *priv_data,
|
||||
u32 *vcom_read)
|
||||
{
|
||||
int ret;
|
||||
u8 vcom_reg;
|
||||
|
||||
dm_gpio_set_value(&priv_data->wake_up_gpio, 0);
|
||||
msleep(10);
|
||||
dm_gpio_set_value(&priv_data->wake_up_gpio, 1);
|
||||
msleep(10);
|
||||
ret = tps65185_i2c_read(priv_data, REG_VCOM1_ADJUST, &vcom_reg);
|
||||
if (ret) {
|
||||
printf("read vcom failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
*vcom_read = vcom_reg;
|
||||
ret = tps65185_i2c_read(priv_data, REG_VCOM2_ADJUST, &vcom_reg);
|
||||
if (ret) {
|
||||
printf("read vcom failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
*vcom_read += (vcom_reg & 0x1) << 8;
|
||||
|
||||
printf("read vcom value: %d\n", *vcom_read);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps65185_set_vcom_value(struct udevice *dev, u32 set_value)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 vcom_readback = 0;
|
||||
u8 vcom1_val, vcom2_val, int_stat = 0;
|
||||
struct tps65185_priv_data *priv_data = dev_get_priv(dev);
|
||||
|
||||
ret = tps65185_read_vcom_value(priv_data, &vcom_readback);
|
||||
if (ret < 0) {
|
||||
printf("tps65185 read vcom value failed\n");
|
||||
} else {
|
||||
if (vcom_readback == set_value / 10) {
|
||||
printf("Same as pmic default value, just return.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vcom1_val = mv_to_vcom1_reg(set_value);
|
||||
vcom2_val = mv_to_vcom2_reg(set_value);
|
||||
|
||||
dm_gpio_set_value(&priv_data->wake_up_gpio, 1);
|
||||
msleep(20);
|
||||
// Set vcom voltage
|
||||
tps65185_i2c_write(priv_data, REG_VCOM1_ADJUST, vcom1_val);
|
||||
tps65185_i2c_write(priv_data, REG_VCOM2_ADJUST, vcom2_val);
|
||||
// PROGRAMMING
|
||||
tps65185_i2c_write(priv_data, REG_VCOM2_ADJUST,
|
||||
vcom2_val | (1 << PAPYRUS_VCOM2_PROG));
|
||||
do {
|
||||
msleep(20);
|
||||
ret = tps65185_i2c_read(priv_data, REG_INT_STATUS1, &int_stat);
|
||||
if (ret) {
|
||||
printf("read status1 failed: %d\n", ret);
|
||||
break;
|
||||
}
|
||||
} while (!(int_stat & (1 << PAPYRUS_INT_STATUS1_PRGC)));
|
||||
|
||||
// VERIFICATION
|
||||
tps65185_read_vcom_value(priv_data, &vcom_readback);
|
||||
|
||||
if (vcom_readback != set_value / 10) {
|
||||
printf("vcom set failed, expect: %d, readback: %d\n",
|
||||
set_value, vcom_readback);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool tps65185_hw_power_ack(struct tps65185_priv_data *priv_data, int up)
|
||||
{
|
||||
u8 pg_status;
|
||||
int st, ret, retries_left = 10;
|
||||
|
||||
do {
|
||||
ret = tps65185_i2c_read(priv_data, REG_PG_STATUS, &pg_status);
|
||||
if (ret)
|
||||
printf("read REG_PG_STATUS failed: %d\n", ret);
|
||||
|
||||
pg_status &= 0xfa;
|
||||
if (pg_status == 0xfa && up == 1) {
|
||||
st = 1;
|
||||
} else if (pg_status == 0x00 && up == 0) {
|
||||
st = 0;
|
||||
} else {
|
||||
st = -1; /* not settled yet */
|
||||
msleep(HW_GET_STATE_INTERVAL_MS);
|
||||
}
|
||||
retries_left--;
|
||||
} while ((st == -1) && retries_left);
|
||||
|
||||
if ((st == -1) && !retries_left)
|
||||
printf("power %s settle error (PG = %02x)\n",
|
||||
up ? "up" : "down", pg_status);
|
||||
|
||||
return (st == up);
|
||||
}
|
||||
|
||||
static int tps65185_power_on(struct udevice *dev)
|
||||
{
|
||||
struct tps65185_priv_data *priv_data = dev_get_priv(dev);
|
||||
|
||||
tps65185_i2c_write(priv_data, REG_VADJ, priv_data->vadj);
|
||||
tps65185_i2c_write(priv_data, REG_UPSEQ0, priv_data->upseq0);
|
||||
tps65185_i2c_write(priv_data, REG_UPSEQ1, priv_data->upseq1);
|
||||
tps65185_i2c_write(priv_data, REG_DWNSEQ0, priv_data->dwnseq0);
|
||||
tps65185_i2c_write(priv_data, REG_DWNSEQ1, priv_data->dwnseq1);
|
||||
|
||||
priv_data->shadow_en |= V3P3_EN_MASK;
|
||||
tps65185_i2c_write(priv_data, REG_ENABLE, priv_data->shadow_en);
|
||||
msleep(PAPYRUS_V3P3OFF_DELAY_MS);
|
||||
priv_data->shadow_en = (0x80 | 0x30 | 0x0F);
|
||||
tps65185_i2c_write(priv_data, REG_ENABLE, priv_data->shadow_en);
|
||||
|
||||
tps65185_hw_power_ack(priv_data, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps65185_power_down(struct udevice *dev)
|
||||
{
|
||||
struct tps65185_priv_data *priv_data = dev_get_priv(dev);
|
||||
|
||||
priv_data->shadow_en = (0x40 | 0x20 | 0x0F);
|
||||
tps65185_i2c_write(priv_data, REG_ENABLE, priv_data->shadow_en);
|
||||
msleep(PAPYRUS_V3P3OFF_DELAY_MS);
|
||||
priv_data->shadow_en &= ~V3P3_EN_MASK;
|
||||
tps65185_i2c_write(priv_data, REG_ENABLE, priv_data->shadow_en);
|
||||
|
||||
tps65185_hw_power_ack(priv_data, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps65185_temp_get(struct udevice *dev, u32 *temp)
|
||||
{
|
||||
int ret;
|
||||
u8 read_val = 0;
|
||||
struct tps65185_priv_data *priv_data = dev_get_priv(dev);
|
||||
|
||||
tps65185_i2c_write(priv_data, REG_TMST1, 0x80);
|
||||
tps65185_i2c_write(priv_data, REG_TMST1, 0x80);
|
||||
do {
|
||||
int retry_time = 100;
|
||||
|
||||
ret = tps65185_i2c_read(priv_data, REG_TMST1, &read_val);
|
||||
if (ret < 0) {
|
||||
printf("read REG_TMST1 failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
if (retry_time-- == 0) {
|
||||
printf("read REG_TMST1 retry 100 times\n");
|
||||
break;
|
||||
}
|
||||
debug("read_val = 0x%x\n", read_val);
|
||||
} while (((read_val & 0x20) == 0 || (read_val & 0x80)));
|
||||
|
||||
mdelay(PAPYRUS_TEMP_READ_TIME_MS);
|
||||
ret = tps65185_i2c_read(priv_data, REG_TMST_VALUE, &read_val);
|
||||
if (ret) {
|
||||
printf("read REG_TMST_VALUE failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
*temp = (u32)read_val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps65185_hw_init(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
u8 rev_id = 0;
|
||||
struct tps65185_priv_data *priv_data = dev_get_priv(dev);
|
||||
|
||||
dm_gpio_set_value(&priv_data->wake_up_gpio, 0);
|
||||
mdelay(PAPYRUS_SLEEP_MINIMUM_MS);
|
||||
dm_gpio_set_value(&priv_data->wake_up_gpio, 1);
|
||||
dm_gpio_set_value(&priv_data->pwr_up_gpio, 0);
|
||||
dm_gpio_set_value(&priv_data->vcom_gpio, 1);
|
||||
mdelay(PAPYRUS_EEPROM_DELAY_MS);
|
||||
ret = tps65185_i2c_read(priv_data, REG_REVID, &rev_id);
|
||||
if (ret) {
|
||||
printf("read revid failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (rev_id > 0)
|
||||
printf("detected device with ID=%02x (TPS6518%dr%dp%d)\n",
|
||||
rev_id, rev_id & 0xF, (rev_id & 0xC0) >> 6,
|
||||
(rev_id & 0x30) >> 4);
|
||||
|
||||
tps65185_i2c_write(priv_data, REG_ENABLE, priv_data->shadow_en);
|
||||
priv_data->rev_id = rev_id;
|
||||
printf("rev_id=%x\n", rev_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tps65185_init_arg(struct tps65185_priv_data *priv_data)
|
||||
{
|
||||
priv_data->vadj = 0x03;
|
||||
|
||||
priv_data->upseq0 = SEQ_VEE(0) | SEQ_VNEG(1)
|
||||
| SEQ_VPOS(2) | SEQ_VDD(3);
|
||||
priv_data->upseq1 = UDLY_3ms(0) | UDLY_3ms(1)
|
||||
| UDLY_3ms(2) | UDLY_3ms(3);
|
||||
|
||||
priv_data->dwnseq0 = SEQ_VDD(0) | SEQ_VPOS(1)
|
||||
| SEQ_VNEG(2) | SEQ_VEE(3);
|
||||
priv_data->dwnseq1 = DDLY_6ms(0) | DDLY_6ms(1)
|
||||
| DDLY_6ms(2) | DDLY_6ms(3);
|
||||
|
||||
priv_data->vcom1 = mv_to_vcom1_reg(1560);
|
||||
priv_data->vcom2 = mv_to_vcom2_reg(1560);
|
||||
priv_data->shadow_en = 0;
|
||||
}
|
||||
|
||||
static int tps65185_probe(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct tps65185_priv_data *tps65185_priv = dev_get_priv(dev);
|
||||
|
||||
tps65185_priv->dev = dev;
|
||||
pmic_dev = dev;
|
||||
tps65185_init_arg(tps65185_priv);
|
||||
|
||||
ret = gpio_request_by_name(dev, "wakeup-gpios", 0,
|
||||
&tps65185_priv->wake_up_gpio, GPIOD_IS_OUT);
|
||||
if (ret) {
|
||||
printf("Cannot get wakeup_pin GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ret = gpio_request_by_name(dev, "powerup-gpios", 0,
|
||||
&tps65185_priv->pwr_up_gpio, GPIOD_IS_OUT);
|
||||
if (ret) {
|
||||
printf("Cannot get pwr_up_gpio GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ret = gpio_request_by_name(dev, "vcomctl-gpios", 0,
|
||||
&tps65185_priv->vcom_gpio, GPIOD_IS_OUT);
|
||||
if (ret) {
|
||||
printf("Cannot get vcom_gpio GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ret = tps65185_hw_init(dev);
|
||||
if (ret) {
|
||||
printf("Cannot init hardware for tps65185: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct rk_ebc_pwr_ops tps65185_funcs = {
|
||||
.power_on = tps65185_power_on,
|
||||
.power_down = tps65185_power_down,
|
||||
.temp_get = tps65185_temp_get,
|
||||
.vcom_set = tps65185_set_vcom_value,
|
||||
};
|
||||
|
||||
static const struct udevice_id ebc_power_of_match[] = {
|
||||
{ .compatible = "ti,tps65185" },
|
||||
{}
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(tps65185_ebc_pwr) = {
|
||||
.name = "tps65185_ebc_pwr",
|
||||
.id = UCLASS_I2C_GENERIC,
|
||||
.of_match = ebc_power_of_match,
|
||||
.probe = tps65185_probe,
|
||||
.ops = &tps65185_funcs,
|
||||
.bind = dm_scan_fdt_dev,
|
||||
.priv_auto_alloc_size = sizeof(struct tps65185_priv_data),
|
||||
};
|
||||
|
|
@ -107,6 +107,8 @@ enum uclass_id {
|
|||
UCLASS_CRYPTO, /* Crypto */
|
||||
UCLASS_ETH_PHY, /* Ethernet PHY device */
|
||||
UCLASS_MDIO, /* MDIO bus */
|
||||
UCLASS_EBC, /* EBC Controller for eink screen */
|
||||
UCLASS_EINK_DISPLAY, /* EINK screen display driver */
|
||||
UCLASS_COUNT,
|
||||
UCLASS_INVALID = -1,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* (C) Copyright 2020 Rockchip Electronics Co., Ltd
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#ifndef RK_EINK_H
|
||||
#define RK_EINK_H
|
||||
|
||||
enum type_logo {
|
||||
EINK_LOGO_RESET = 0,
|
||||
EINK_LOGO_UBOOT = 1 << 0,
|
||||
EINK_LOGO_KERNEL = 1 << 1,
|
||||
EINK_LOGO_CHARGING_0 = 1 << 2,
|
||||
EINK_LOGO_CHARGING_1 = 1 << 3,
|
||||
EINK_LOGO_CHARGING_2 = 1 << 4,
|
||||
EINK_LOGO_CHARGING_3 = 1 << 5,
|
||||
EINK_LOGO_CHARGING_4 = 1 << 6,
|
||||
EINK_LOGO_CHARGING_5 = 1 << 7,
|
||||
EINK_LOGO_CHARGING_LOWPOWER = 1 << 8,
|
||||
};
|
||||
|
||||
enum update_mode {
|
||||
EINK_UPDATE_NORMAL = 0,
|
||||
EINK_UPDATE_DIFF = 1,
|
||||
};
|
||||
|
||||
int rockchip_eink_show_uboot_logo(void);
|
||||
int rockchip_eink_show_charge_logo(int logo_type);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue