417 lines
13 KiB
C
417 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2020 Rockchip Electronics Co., Ltd.
|
|
*
|
|
* Author: Zorro Liu <zorro.liu@rock-chips.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include "ebc_tcon.h"
|
|
|
|
#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 piont
|
|
#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_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_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)
|
|
|
|
enum ebc_win_data_fmt {
|
|
Y_DATA_4BPP = 0,
|
|
Y_DATA_8BPP = 1,
|
|
RGB888 = 2,
|
|
RGB565 = 3,
|
|
};
|
|
|
|
static inline void tcon_write(struct ebc_tcon *tcon, unsigned int reg,
|
|
unsigned int value)
|
|
{
|
|
regmap_write(tcon->regmap_base, reg, value);
|
|
}
|
|
|
|
static inline unsigned int tcon_read(struct ebc_tcon *tcon, unsigned int reg)
|
|
{
|
|
unsigned int value;
|
|
|
|
regmap_read(tcon->regmap_base, reg, &value);
|
|
|
|
return value;
|
|
}
|
|
|
|
static inline void tcon_update_bits(struct ebc_tcon *tcon, unsigned int reg,
|
|
unsigned int mask, unsigned int val)
|
|
{
|
|
regmap_update_bits(tcon->regmap_base, reg, mask, val);
|
|
}
|
|
|
|
static inline void tcon_cfg_done(struct ebc_tcon *tcon)
|
|
{
|
|
regmap_write(tcon->regmap_base, EBC_CONFIG_DONE, REG_LOAD_GLOBAL_EN);
|
|
}
|
|
|
|
static int tcon_enable(struct ebc_tcon *tcon, struct ebc_panel *panel)
|
|
{
|
|
clk_prepare_enable(tcon->hclk);
|
|
clk_prepare_enable(tcon->dclk);
|
|
pm_runtime_get_sync(tcon->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->vir_height) | WIN_VIR_WIDTH(panel->vir_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);
|
|
|
|
enable_irq(tcon->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tcon_disable(struct ebc_tcon *tcon)
|
|
{
|
|
disable_irq(tcon->irq);
|
|
/* output low */
|
|
tcon_update_bits(tcon, EBC_DSP_START, DSP_OUT_LOW | DSP_FRM_START_MASK, DSP_OUT_LOW);
|
|
|
|
pm_runtime_put_sync(tcon->dev);
|
|
clk_disable_unprepare(tcon->dclk);
|
|
clk_disable_unprepare(tcon->hclk);
|
|
}
|
|
|
|
static void tcon_dsp_mode_set(struct ebc_tcon *tcon, int update_mode, int display_mode, int three_win_mode, int eink_mode)
|
|
{
|
|
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);
|
|
}
|
|
|
|
static void tcon_image_addr_set(struct ebc_tcon *tcon, u32 pre_image_addr, u32 cur_image_addr)
|
|
{
|
|
tcon_write(tcon, EBC_WIN_MST0, pre_image_addr);
|
|
tcon_write(tcon, EBC_WIN_MST1, cur_image_addr);
|
|
tcon_cfg_done(tcon);
|
|
}
|
|
|
|
static void tcon_frame_addr_set(struct ebc_tcon *tcon, u32 frame_addr)
|
|
{
|
|
tcon_write(tcon, EBC_WIN_MST2, frame_addr);
|
|
tcon_cfg_done(tcon);
|
|
}
|
|
|
|
static int tcon_lut_data_set(struct ebc_tcon *tcon, unsigned int *lut_data, int frame_count, int lut_32)
|
|
{
|
|
int i;
|
|
int lut_size;
|
|
|
|
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 void tcon_frame_start(struct ebc_tcon *tcon, int frame_total)
|
|
{
|
|
tcon_write(tcon, EBC_INT_STATUS, LINE_FLAG_INT_MASK | DSP_FRM_INT_MASK | FRM_END_INT_MASK);
|
|
/* always set frm start bit 0 before real frame start */
|
|
tcon_update_bits(tcon, EBC_DSP_START, DSP_FRM_TOTAL_MASK | DSP_FRM_START_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);
|
|
}
|
|
|
|
static irqreturn_t tcon_irq_hanlder(int irq, void *dev_id)
|
|
{
|
|
struct ebc_tcon *tcon = (struct ebc_tcon *)dev_id;
|
|
u32 intr_status;
|
|
|
|
intr_status = tcon_read(tcon, EBC_INT_STATUS);
|
|
|
|
if (intr_status & DSP_END_INT) {
|
|
tcon_update_bits(tcon, EBC_INT_STATUS, DSP_END_INT_CLR, DSP_END_INT_CLR);
|
|
|
|
if (tcon->dsp_end_callback)
|
|
tcon->dsp_end_callback();
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static bool tcon_is_volatile_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case EBC_DSP_START:
|
|
case EBC_EPD_CTRL:
|
|
case EBC_DSP_CTRL:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct regmap_config ebc_regmap_config = {
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.cache_type = REGCACHE_RBTREE,
|
|
.volatile_reg = tcon_is_volatile_reg,
|
|
};
|
|
|
|
static int ebc_tcon_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct ebc_tcon *tcon;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL);
|
|
if (!tcon)
|
|
return -ENOMEM;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
tcon->regs = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(tcon->regs))
|
|
return PTR_ERR(tcon->regs);
|
|
|
|
tcon->len = resource_size(res);
|
|
ebc_regmap_config.max_register = resource_size(res) - 4;
|
|
ebc_regmap_config.name = "rockchip,ebc_tcon";
|
|
tcon->regmap_base = devm_regmap_init_mmio(dev, tcon->regs, &ebc_regmap_config);
|
|
if (IS_ERR(tcon->regmap_base))
|
|
return PTR_ERR(tcon->regmap_base);
|
|
|
|
tcon->hclk = devm_clk_get(dev, "hclk");
|
|
if (IS_ERR(tcon->hclk)) {
|
|
ret = PTR_ERR(tcon->hclk);
|
|
dev_err(dev, "failed to get hclk clock: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
tcon->dclk = devm_clk_get(dev, "dclk");
|
|
if (IS_ERR(tcon->dclk)) {
|
|
ret = PTR_ERR(tcon->dclk);
|
|
dev_err(dev, "failed to get dclk clock: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
tcon->irq = platform_get_irq(pdev, 0);
|
|
if (tcon->irq < 0) {
|
|
dev_err(dev, "No IRQ resource!\n");
|
|
return tcon->irq;
|
|
}
|
|
|
|
irq_set_status_flags(tcon->irq, IRQ_NOAUTOEN);
|
|
ret = devm_request_irq(dev, tcon->irq, tcon_irq_hanlder,
|
|
0, dev_name(dev), tcon);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to requeset irq: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
tcon->dev = dev;
|
|
tcon->enable = tcon_enable;
|
|
tcon->disable = tcon_disable;
|
|
tcon->dsp_mode_set = tcon_dsp_mode_set;
|
|
tcon->image_addr_set = tcon_image_addr_set;
|
|
tcon->frame_addr_set = tcon_frame_addr_set;
|
|
tcon->lut_data_set = tcon_lut_data_set;
|
|
tcon->frame_start = tcon_frame_start;
|
|
platform_set_drvdata(pdev, tcon);
|
|
|
|
pm_runtime_enable(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ebc_tcon_remove(struct platform_device *pdev)
|
|
{
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id ebc_tcon_of_match[] = {
|
|
{ .compatible = "rockchip,rk3568-ebc-tcon" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ebc_tcon_of_match);
|
|
|
|
static struct platform_driver ebc_tcon_driver = {
|
|
.driver = {
|
|
.name = "rk-ebc-tcon",
|
|
.of_match_table = ebc_tcon_of_match,
|
|
},
|
|
.probe = ebc_tcon_probe,
|
|
.remove = ebc_tcon_remove,
|
|
};
|
|
module_platform_driver(ebc_tcon_driver);
|
|
|
|
MODULE_AUTHOR("Zorro Liu <zorro.liu@rock-chips.com>");
|
|
MODULE_DESCRIPTION("ROCKCHIP EBC tcon driver");
|
|
MODULE_LICENSE("GPL v2");
|