302 lines
7.9 KiB
C
302 lines
7.9 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)))
|
|
|
|
/* eink register define */
|
|
#define EINK_IP_ENABLE 0x00
|
|
#define EINK_SFT_UPDATE 0x04
|
|
#define EINK_SIT_UPDATE 0x08
|
|
#define EINK_PRE_IMAGE_BUF_ADDR 0x0c
|
|
#define EINK_CUR_IMAGE_BUF_ADDR 0x10
|
|
#define EINK_IMAGE_PROCESS_BUF_ADDR 0x14
|
|
#define EINK_LINE_DATA_ADDR_OFFSET 0x18
|
|
#define EINK_IMAGE_WIDTH 0x1c
|
|
#define EINK_IMAGE_HEIGHT 0x20
|
|
#define EINK_DATA_FORMAT 0x24
|
|
#define EINK_IP_STATUS 0x28
|
|
#define EINK_IP_VERSION 0x2c
|
|
#define EINK_IP_CLR_INT 0x30
|
|
#define EINK_INT_SETTING0 0x34
|
|
#define EINK_INT_SETTING1 0x38
|
|
#define EINK_INT_SETTING2 0x3c
|
|
#define EINK_INT_SETTING3 0x40
|
|
#define EINK_INT_SETTING4 0x44
|
|
#define EINK_INT_SETTING5 0x48
|
|
#define EINK_INT_SETTING6 0x4c
|
|
#define EINK_INT_SETTING7 0x50
|
|
#define EINK_WF_SETTING0 0x54
|
|
#define EINK_WF_SETTING1 0x58
|
|
#define EINK_WF_SETTING2 0x5c
|
|
#define EINK_WF_SETTING3 0x60
|
|
#define EINK_WF_SETTING4 0x64
|
|
#define EINK_WF_SETTING5 0x68
|
|
#define EINK_WF_SETTING6 0x6c
|
|
#define EINK_WF_SETTING7 0x70
|
|
|
|
struct eink_reg_data {
|
|
int addr;
|
|
int value;
|
|
};
|
|
|
|
static const struct eink_reg_data PANEL_1200x825_INIT[] = {
|
|
{ EINK_SFT_UPDATE, 0x00030001 },
|
|
{ EINK_SIT_UPDATE, 0x00050000 },
|
|
{ EINK_LINE_DATA_ADDR_OFFSET, 0x000004b0 }, //width
|
|
{ EINK_IMAGE_WIDTH, 0x000004af }, //width - 1
|
|
{ EINK_IMAGE_HEIGHT, 0x00000338 }, //height - 1
|
|
|
|
{ EINK_INT_SETTING0, 0x0e56676f },
|
|
{ EINK_INT_SETTING1, 0x40674408 },
|
|
{ EINK_INT_SETTING2, 0xd7eb7743 },
|
|
{ EINK_INT_SETTING3, 0x19414d35 },
|
|
{ EINK_INT_SETTING4, 0x12561c00 },
|
|
{ EINK_INT_SETTING5, 0x05552e0a },
|
|
{ EINK_INT_SETTING6, 0x4a400e10 },
|
|
{ EINK_INT_SETTING7, 0x15496e2b },
|
|
|
|
{ EINK_WF_SETTING0, 0xb3f33a52 },
|
|
{ EINK_WF_SETTING1, 0x2042b122 },
|
|
{ EINK_WF_SETTING2, 0xbdb0f3be },
|
|
{ EINK_WF_SETTING3, 0xe289a0ca },
|
|
{ EINK_WF_SETTING4, 0xb0d3b2c8 },
|
|
{ EINK_WF_SETTING5, 0x3a32ab20 },
|
|
{ EINK_WF_SETTING6, 0xa69a634c },
|
|
{ EINK_WF_SETTING7, 0xd87af2c0 },
|
|
};
|
|
|
|
static const struct eink_reg_data PANEL_1872x1404_INIT[] = {
|
|
{ EINK_SFT_UPDATE, 0x00030001 },
|
|
{ EINK_SIT_UPDATE, 0x00050000 },
|
|
{ EINK_LINE_DATA_ADDR_OFFSET, 0x00000750 }, //width
|
|
{ EINK_IMAGE_WIDTH, 0x0000074f }, //width - 1
|
|
{ EINK_IMAGE_HEIGHT, 0x0000057c }, //height -1
|
|
|
|
{ EINK_INT_SETTING0, 0x0e56676f },
|
|
{ EINK_INT_SETTING1, 0x40674408 },
|
|
{ EINK_INT_SETTING2, 0xb14a4643 },
|
|
{ EINK_INT_SETTING3, 0x19414d35 },
|
|
{ EINK_INT_SETTING4, 0x12561c00 },
|
|
{ EINK_INT_SETTING5, 0x05552e0a },
|
|
{ EINK_INT_SETTING6, 0x4a400e10 },
|
|
{ EINK_INT_SETTING7, 0x15496e2b },
|
|
|
|
{ EINK_WF_SETTING0, 0xb3f33a52 },
|
|
{ EINK_WF_SETTING1, 0x2042b122 },
|
|
{ EINK_WF_SETTING2, 0x34b0708b },
|
|
{ EINK_WF_SETTING3, 0xe289a0ca },
|
|
{ EINK_WF_SETTING4, 0xb0d3b2c8 },
|
|
{ EINK_WF_SETTING5, 0x3a32ab20 },
|
|
{ EINK_WF_SETTING6, 0x2f9ae079 },
|
|
{ EINK_WF_SETTING7, 0xd87af2c0 },
|
|
};
|
|
|
|
static inline void tcon_write(struct eink_tcon *tcon, unsigned int reg,
|
|
unsigned int value)
|
|
{
|
|
regmap_write(tcon->regmap_base, reg, value);
|
|
}
|
|
|
|
static inline unsigned int tcon_read(struct eink_tcon *tcon, unsigned int reg)
|
|
{
|
|
unsigned int value;
|
|
|
|
regmap_read(tcon->regmap_base, reg, &value);
|
|
|
|
return value;
|
|
}
|
|
|
|
static inline void tcon_update_bits(struct eink_tcon *tcon, unsigned int reg,
|
|
unsigned int mask, unsigned int val)
|
|
{
|
|
regmap_update_bits(tcon->regmap_base, reg, mask, val);
|
|
}
|
|
|
|
static int tcon_enable(struct eink_tcon *tcon, struct ebc_panel *panel)
|
|
{
|
|
int reg_num = 0;
|
|
int i;
|
|
const struct eink_reg_data *pre_init_reg;
|
|
|
|
clk_prepare_enable(tcon->pclk);
|
|
clk_prepare_enable(tcon->hclk);
|
|
pm_runtime_get_sync(tcon->dev);
|
|
|
|
if ((panel->width == 1872) && (panel->height == 1404)) {
|
|
pre_init_reg = PANEL_1872x1404_INIT;
|
|
reg_num = ARRAY_SIZE(PANEL_1872x1404_INIT);
|
|
} else if ((panel->width == 1200) && (panel->height == 825)) {
|
|
pre_init_reg = PANEL_1200x825_INIT;
|
|
reg_num = ARRAY_SIZE(PANEL_1200x825_INIT);
|
|
} else {
|
|
pre_init_reg = PANEL_1872x1404_INIT;
|
|
reg_num = ARRAY_SIZE(PANEL_1872x1404_INIT);
|
|
}
|
|
for (i = 0; i < reg_num; i++) {
|
|
tcon_write(tcon, pre_init_reg[i].addr, pre_init_reg[i].value);
|
|
}
|
|
|
|
enable_irq(tcon->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tcon_disable(struct eink_tcon *tcon)
|
|
{
|
|
disable_irq(tcon->irq);
|
|
|
|
pm_runtime_put_sync(tcon->dev);
|
|
clk_disable_unprepare(tcon->hclk);
|
|
clk_disable_unprepare(tcon->pclk);
|
|
}
|
|
|
|
static void tcon_image_addr_set(struct eink_tcon *tcon, u32 pre_image_buf_addr,
|
|
u32 cur_image_buf_addr, u32 image_process_buf_addr)
|
|
{
|
|
tcon_write(tcon, EINK_PRE_IMAGE_BUF_ADDR, pre_image_buf_addr);
|
|
tcon_write(tcon, EINK_CUR_IMAGE_BUF_ADDR, cur_image_buf_addr);
|
|
tcon_write(tcon, EINK_IMAGE_PROCESS_BUF_ADDR, image_process_buf_addr);
|
|
}
|
|
|
|
static void tcon_frame_start(struct eink_tcon *tcon)
|
|
{
|
|
tcon_write(tcon, EINK_IP_ENABLE, 1);
|
|
}
|
|
|
|
static irqreturn_t tcon_irq_hanlder(int irq, void *dev_id)
|
|
{
|
|
struct eink_tcon *tcon = (struct eink_tcon *)dev_id;
|
|
u32 intr_status;
|
|
|
|
intr_status = tcon_read(tcon, EINK_IP_STATUS);
|
|
|
|
if (intr_status & 0x1) {
|
|
tcon_update_bits(tcon, EINK_IP_CLR_INT, 0x1, 0x1);
|
|
|
|
if (tcon->dsp_end_callback)
|
|
tcon->dsp_end_callback();
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct regmap_config eink_regmap_config = {
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
};
|
|
|
|
static int eink_tcon_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct eink_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);
|
|
eink_regmap_config.max_register = resource_size(res) - 4;
|
|
eink_regmap_config.name = "rockchip,eink_tcon";
|
|
tcon->regmap_base = devm_regmap_init_mmio(dev, tcon->regs, &eink_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->pclk = devm_clk_get(dev, "pclk");
|
|
if (IS_ERR(tcon->pclk)) {
|
|
ret = PTR_ERR(tcon->pclk);
|
|
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->image_addr_set = tcon_image_addr_set;
|
|
tcon->frame_start = tcon_frame_start;
|
|
platform_set_drvdata(pdev, tcon);
|
|
|
|
pm_runtime_enable(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int eink_tcon_remove(struct platform_device *pdev)
|
|
{
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id eink_tcon_of_match[] = {
|
|
{ .compatible = "rockchip,rk3568-eink-tcon" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, eink_tcon_of_match);
|
|
|
|
static struct platform_driver eink_tcon_driver = {
|
|
.driver = {
|
|
.name = "rk-eink-tcon",
|
|
.of_match_table = eink_tcon_of_match,
|
|
},
|
|
.probe = eink_tcon_probe,
|
|
.remove = eink_tcon_remove,
|
|
};
|
|
module_platform_driver(eink_tcon_driver);
|
|
|
|
MODULE_AUTHOR("Zorro Liu <zorro.liu@rock-chips.com>");
|
|
MODULE_DESCRIPTION("ROCKCHIP EINK tcon driver");
|
|
MODULE_LICENSE("GPL v2");
|