linuxOS_D21X/source/linux-5.10/drivers/video/artinchip/disp/aic_fb.c
2025-08-14 15:17:16 +08:00

1340 lines
32 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020-2022 ArtInChip Technology Co., Ltd.
* Authors: Ning Fang <ning.fang@artinchip.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/console.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/of_address.h>
#include <linux/memblock.h>
#include <linux/component.h>
#include <linux/pm_runtime.h>
#include "video/artinchip_fb.h"
#include "aic_fb.h"
#include "hw/de_hw.h"
#define AICFB_NAME "aicfb"
#define AICFB_ON 1
#define AICFB_OFF 0
#define MAX_FB_NUM 1
enum aicfb_port_dir {
AICFB_PORT_IN,
AICFB_PORT_OUT
};
struct aicfb_format {
const char *name;
u32 bits_per_pixel;
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
enum mpp_pixel_format format;
};
struct aicfb_dt {
u32 rotation;
u32 disp_buf_num;
u32 width;
u32 height;
u32 width_virtual;
u32 height_virtual;
u32 stride;
u32 size; /* The final framebuffer size to be requested */
struct aicfb_format *format;
struct aicfb_disp_prop disp_prop;
struct device_node *fb_np; /* node pointer of framebuffer */
struct device_node *de_np; /* node pointer of display engine */
struct device_node *di_np; /* node pointer of display interface */
struct device_node *panel_np; /* node pointer of panel */
};
struct aicfb_data {
struct fb_info *info[MAX_FB_NUM];
struct device *dev;
struct aicfb_dt dt_lists[MAX_FB_NUM];
bool fb_valid[MAX_FB_NUM];
};
struct aicfb_format aicfb_format_lists[] = {
{"a8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {24, 8}, MPP_FMT_ARGB_8888},
{"a8b8g8r8", 32, {0, 8}, {8, 8}, {16, 8}, {24, 8}, MPP_FMT_ABGR_8888},
{"r8g8b8a8", 32, {24, 8}, {16, 8}, {8, 8}, {0, 8}, MPP_FMT_RGBA_8888},
{"b8g8r8a8", 32, {8, 8}, {16, 8}, {24, 8}, {0, 8}, MPP_FMT_BGRA_8888},
{"x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, MPP_FMT_XRGB_8888},
{"x8b8g8r8", 32, {0, 8}, {8, 8}, {16, 8}, {0, 0}, MPP_FMT_XBGR_8888},
{"r8g8b8x8", 32, {24, 8}, {16, 8}, {8, 8}, {0, 0}, MPP_FMT_RGBX_8888},
{"b8g8r8x8", 32, {8, 8}, {16, 8}, {24, 8}, {0, 0}, MPP_FMT_BGRX_8888},
{"r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, MPP_FMT_RGB_888},
{"b8g8r8", 24, {0, 8}, {8, 8}, {16, 8}, {0, 0}, MPP_FMT_BGR_888},
{"r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, MPP_FMT_RGB_565},
{"b5g6r5", 16, {0, 5}, {5, 6}, {11, 5}, {0, 0}, MPP_FMT_BGR_565},
{"a1r5g5b5", 16, {10, 5}, {5, 5}, {0, 5}, {15, 1}, MPP_FMT_ARGB_1555},
{"a1b5g5r5", 16, {0, 5}, {5, 5}, {10, 5}, {15, 1}, MPP_FMT_ABGR_1555},
{"r5g5b5a1", 16, {11, 5}, {6, 5}, {1, 5}, {0, 1}, MPP_FMT_RGBA_5551},
{"b5g5r5a1", 16, {1, 5}, {6, 5}, {11, 5}, {0, 1}, MPP_FMT_BGRA_5551},
{"a4r4g4b4", 16, {8, 4}, {4, 4}, {0, 4}, {12, 4}, MPP_FMT_ARGB_4444},
{"a4b4g4r4", 16, {0, 4}, {4, 4}, {8, 4}, {12, 4}, MPP_FMT_ABGR_4444},
{"r4g4b4a4", 16, {12, 4}, {8, 4}, {4, 4}, {0, 4}, MPP_FMT_RGBA_4444},
{"b4g4r4a4", 16, {8, 4}, {8, 4}, {12, 4}, {0, 4}, MPP_FMT_BGRA_4444},
};
phys_addr_t uboot_logo_base;
phys_addr_t uboot_logo_size;
static int uboot_logo_on;
static int __init aicfb_uboot_mem_free(void)
{
int err;
if (uboot_logo_size) {
void *start = phys_to_virt(uboot_logo_base);
void *end = phys_to_virt(uboot_logo_base + uboot_logo_size);
err = memblock_free(uboot_logo_base, uboot_logo_size);
if (err < 0) {
pr_err("%s: Freeing memblock failed: %d\n",
__func__, err);
return err;
}
free_reserved_area(start, end, -1, "logo");
}
return 0;
}
late_initcall(aicfb_uboot_mem_free);
static void aicfb_videomode_to_fbdata(struct aicfb_dt *dt, struct videomode *vm)
{
struct aicfb_format *f = dt->format;
if (!dt->width)
dt->width = vm->hactive;
if (!dt->height)
dt->height = vm->vactive;
if (dt->rotation == 90 || dt->rotation == 270) {
u32 tmp = dt->width;
dt->width = dt->height;
dt->height = tmp;
}
if (dt->width_virtual < dt->width)
dt->width_virtual = dt->width;
if (dt->height_virtual < dt->height)
dt->height_virtual = dt->height;
if (dt->rotation && !dt->disp_buf_num) {
if (dt->height_virtual == dt->height)
dt->disp_buf_num = 1;
else
dt->disp_buf_num = 2;
}
if (!dt->stride)
dt->stride = ALIGN_8B(dt->width_virtual * f->bits_per_pixel / 8);
dt->size = dt->height_virtual * dt->stride;
}
static void aicfb_format_to_var(struct fb_var_screeninfo *var,
struct aicfb_dt *dt)
{
struct aicfb_format *f = dt->format;
var->bits_per_pixel = f->bits_per_pixel;
var->red = f->red;
var->green = f->green;
var->blue = f->blue;
var->transp = f->transp;
var->yres = dt->height;
var->yres_virtual = dt->height_virtual;
var->xres = dt->width;
var->xres_virtual = dt->width_virtual;
var->rotate = dt->rotation;
}
static inline bool need_set_hsbc(struct aicfb_disp_prop *disp_prop)
{
if (disp_prop->bright != 50 ||
disp_prop->contrast != 50 ||
disp_prop->saturation != 50 ||
disp_prop->hue != 50)
return true;
return false;
}
static inline bool check_rotation_degree(u32 degree)
{
switch (degree) {
case 0:
case 90:
case 180:
case 270:
return true;
default:
break;
};
pr_err("Invalid rotation degree\n");
return false;
}
static int aic_fb_open(struct fb_info *info, int user)
{
return 0;
}
static int aic_fb_release(struct fb_info *info, int user)
{
struct aicfb_info *fbi = (struct aicfb_info *)info->par;
return fbi->de->release_dmabuf();
}
static unsigned int fb_index;
static int aic_fb_rotate(struct fb_info *info, struct fb_var_screeninfo *var,
struct aicfb_layer_data *layer, unsigned int offset)
{
struct aicfb_info *fbi = (struct aicfb_info *)info->par;
struct ge_bitblt blt = {0};
s32 ret = 0;
if (!check_rotation_degree(var->rotate))
return -EINVAL;
/* source buffer */
blt.src_buf.buf_type = MPP_PHY_ADDR;
blt.src_buf.phy_addr[0] = fbi->fb_start_dma + offset;
blt.src_buf.stride[0] = info->fix.line_length;
blt.src_buf.size.width = info->var.xres;
blt.src_buf.size.height = info->var.yres;
blt.src_buf.format = layer->buf.format;
/* destination buffer */
blt.dst_buf.buf_type = MPP_PHY_ADDR;
blt.dst_buf.phy_addr[0] = layer->buf.phy_addr[0];
blt.dst_buf.stride[0] = layer->buf.stride[0];
blt.dst_buf.size.width = layer->buf.size.width;
blt.dst_buf.size.height = layer->buf.size.height;
blt.dst_buf.format = layer->buf.format;
mutex_lock(&fbi->mutex);
fbi->fb_rotate = var->rotate;
mutex_unlock(&fbi->mutex);
switch (fbi->fb_rotate) {
case 0:
blt.ctrl.flags = 0;
break;
case 90:
blt.ctrl.flags = MPP_ROTATION_90;
break;
case 180:
blt.ctrl.flags = MPP_ROTATION_180;
break;
case 270:
blt.ctrl.flags = MPP_ROTATION_270;
break;
default:
pr_err("Invalid rotation degree\n");
return -EINVAL;
};
if (fbi->set_hsbc) {
u32 csc_coef[12];
int bright;
int contrast;
int sat;
int hue;
mutex_lock(&fbi->mutex);
bright = fbi->disp_prop->bright;
contrast = fbi->disp_prop->contrast;
sat = fbi->disp_prop->saturation;
hue = fbi->disp_prop->hue;
mutex_unlock(&fbi->mutex);
get_rgb_hsbc_csc_coefs(bright, contrast, sat, hue, csc_coef);
ret = aic_ge_bitblt_with_hsbc(&blt, csc_coef);
} else {
ret = aic_ge_bitblt(&blt);
}
return ret;
}
static int aic_fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct aicfb_info *fbi = (struct aicfb_info *)info->par;
struct aicfb_layer_data layer = {0};
struct de_funcs *de = fbi->de;
u32 offset;
offset = var->xoffset * info->var.bits_per_pixel / 8 +
var->yoffset * ALIGN_8B(info->var.bits_per_pixel / 8 * var->xres);
layer.layer_id = AICFB_LAYER_TYPE_UI;
layer.rect_id = 0;
de->get_layer_config(&layer);
layer.enable = 1;
if (var->reserved[0]) {
layer.buf.fd[0] = var->reserved[0];
layer.buf.flags = MPP_BUF_PAN_DISPLAY_DMABUF;
return de->update_layer_config(&layer);
}
layer.buf.flags = 0;
layer.buf.phy_addr[0] = fbi->fb_start_dma + offset;
if (fbi->fb_rotate || fbi->set_hsbc) {
layer.buf.phy_addr[0] = fbi->fb_start_dma + fbi->fb_size + offset;
if (fbi->disp_buf_num == 2 &&
info->var.yres == info->var.yres_virtual) {
layer.buf.phy_addr[0] += fbi->fb_size * fb_index;
fb_index = !fb_index;
}
if (aic_fb_rotate(info, var, &layer, offset))
pr_err("GE Bitblt FB error\n");
}
de->update_layer_config(&layer);
return 0;
}
int aic_fb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
if (var->xoffset + var->xres > var->xres_virtual)
return -EINVAL;
if (var->yoffset + var->yres > var->yres_virtual)
return -EINVAL;
return 0;
}
int aic_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct aicfb_info *fbi = (struct aicfb_info *)info->par;
unsigned long mmio_pgoff;
unsigned long start;
u32 len;
if (fbi->dma_coherent)
return dma_mmap_attrs(info->dev->parent, vma, fbi->fb_start,
fbi->fb_start_dma, fbi->fb_size,
DMA_ATTR_WRITE_COMBINE);
/*
* Ugh. This can be either the frame buffer mapping, or
* if pgoff points past it, the mmio mapping.
*/
start = info->fix.smem_start;
len = info->fix.smem_len;
mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
if (vma->vm_pgoff >= mmio_pgoff) {
if (info->var.accel_flags) {
mutex_unlock(&info->mm_lock);
return -EINVAL;
}
vma->vm_pgoff -= mmio_pgoff;
start = info->fix.mmio_start;
len = info->fix.mmio_len;
}
mutex_unlock(&info->mm_lock);
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
/*
* The framebuffer needs to be accessed decrypted, be sure
* SME protection is removed
*/
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
return vm_iomap_memory(vma, start, len);
}
struct fb_ops aicfb_ops = {
.owner = THIS_MODULE,
.fb_open = aic_fb_open,
.fb_release = aic_fb_release,
.fb_pan_display = aic_fb_pan_display,
.fb_ioctl = aic_fb_ioctl,
.fb_check_var = aic_fb_check_var,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_mmap = aic_fb_mmap,
};
static void aicfb_fb_info_setup(struct fb_info *info, struct aicfb_data *fdt)
{
struct aicfb_info *fbi = (struct aicfb_info *)info->par;
struct aicfb_dt *dt = &fdt->dt_lists[0];
info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK |
FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN;
info->node = 0;
strcpy(info->fix.id, fbi->name);
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.type_aux = 0;
info->fix.xpanstep = 0;
if (info->var.yres_virtual == info->var.yres)
info->fix.ypanstep = 0;
else
info->fix.ypanstep = 1;
info->fix.ywrapstep = 0;
info->fix.accel = FB_ACCEL_NONE;
info->fix.smem_start = fbi->fb_start_dma;
info->fix.smem_len = fbi->fb_size;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.line_length = dt->stride;
info->fbops = &aicfb_ops;
info->pseudo_palette = fbi->pseudo_palette;
info->screen_base = fbi->fb_start;
info->screen_size = fbi->fb_size;
fbi->fb_rotate = dt->rotation;
fbi->disp_buf_num = dt->disp_buf_num;
if (fbi->fb_rotate == 90 || fbi->fb_rotate == 270) {
fbi->screen_size.width = dt->height;
fbi->screen_size.height = dt->width;
} else {
fbi->screen_size.width = dt->width;
fbi->screen_size.height = dt->height;
}
}
static int compare_of(struct device *dev, void *data)
{
return dev->of_node == data;
}
static int aicfb_add_match_component(struct platform_device *pdev,
struct component_match **match, u32 id)
{
struct aicfb_data *fbd = dev_get_drvdata(&pdev->dev);
/* match de */
component_match_add(&pdev->dev, match, compare_of,
fbd->dt_lists[id].de_np);
/* match encoder */
component_match_add(&pdev->dev, match, compare_of,
fbd->dt_lists[id].di_np);
/* match panel */
component_match_add(&pdev->dev, match, compare_of,
fbd->dt_lists[id].panel_np);
return 0;
}
/**
* of_get_fb_by_id() - get the fb device node matching a given id
* @parent: pointer to the parent device node
* @id: id of the fb
*
*/
struct device_node *of_get_fb_by_id(struct device_node *parent, u32 id)
{
struct device_node *fb_node = NULL;
for_each_child_of_node(parent, fb_node) {
u32 fb_id = 0;
if (!of_node_name_eq(fb_node, "fb"))
continue;
of_property_read_u32(fb_node, "reg", &fb_id);
if (id == fb_id)
break;
}
return fb_node;
}
static struct device_node *of_get_remote_by_port(struct device_node *parent)
{
struct device_node *remote = NULL;
struct device_node *port;
for_each_child_of_node(parent, port) {
if (of_node_name_eq(port, "port")) {
remote = of_graph_get_remote_port_parent(port->child);
break;
}
of_node_put(port);
}
of_node_put(port);
return remote;
}
static struct device_node *of_get_remote_by_port_id(struct device_node *parent,
enum aicfb_port_dir id)
{
struct device_node *remote = NULL;
struct device_node *port;
port = of_graph_get_port_by_id(parent, id);
if (!port)
return NULL;
remote = of_graph_get_remote_port_parent(port->child);
of_node_put(port);
return remote;
}
static int aicfb_parse_fb_rotation(struct device_node *np, struct aicfb_dt *dt)
{
int ret;
ret = of_property_read_u32(np, "rotation-degress", &dt->rotation);
if (ret) {
dt->rotation = 0;
dt->disp_buf_num = 0;
return 0;
}
if (!check_rotation_degree(dt->rotation))
return -EINVAL;
if (of_property_read_u32(np, "disp-buf-num", &dt->disp_buf_num))
dt->disp_buf_num = 0;
return 0;
}
static int aicfb_parse_fb_size(struct device_node *np, struct aicfb_dt *dt)
{
const char *format;
int i;
if (of_property_read_u32(np, "width", &dt->width))
dt->width = 0;
if (of_property_read_u32(np, "height", &dt->height))
dt->height = 0;
if (of_property_read_u32(np, "width-virtual", &dt->width_virtual))
dt->width_virtual = 0;
if (of_property_read_u32(np, "height-virtual", &dt->height_virtual))
dt->height_virtual = 0;
if (of_property_read_u32(np, "stride", &dt->stride))
dt->stride = 0;
dt->format = NULL;
if (of_property_read_string(np, "format", &format)) {
dt->format = &aicfb_format_lists[0];
} else {
for (i = 0; i < ARRAY_SIZE(aicfb_format_lists); i++) {
if (strcmp(format, aicfb_format_lists[i].name))
continue;
dt->format = &aicfb_format_lists[i];
break;
}
}
if (!dt->format) {
pr_err("fb0 invalid format:%s\n", format);
return -EINVAL;
}
return 0;
}
static void aicfb_parse_disp_prop(struct device_node *np, struct aicfb_dt *dt)
{
if (of_property_read_u32(np, "disp-bright", &dt->disp_prop.bright))
dt->disp_prop.bright = 50;
if (of_property_read_u32(np, "disp-contrast", &dt->disp_prop.contrast))
dt->disp_prop.contrast = 50;
if (of_property_read_u32(np, "disp-saturation", &dt->disp_prop.saturation))
dt->disp_prop.saturation = 50;
if (of_property_read_u32(np, "disp-hue", &dt->disp_prop.hue))
dt->disp_prop.hue = 50;
}
static int aicfb_parse_dt_by_fb_id(struct platform_device *pdev, u32 id)
{
struct device_node *root_np = pdev->dev.of_node;
struct aicfb_data *aicfb = dev_get_drvdata(&pdev->dev);
struct aicfb_dt *dt = &aicfb->dt_lists[id];
struct device_node *np, *node;
int ret;
if (!root_np) {
dev_err(&pdev->dev, "Can't find device node\n");
goto err_fb;
}
/* parse fb device node */
np = of_get_fb_by_id(root_np, id);
if (!np) {
dev_err(&pdev->dev, "Can't find fb@%u\n", id);
goto err_fb;
}
dt->fb_np = np;
/* parse fb param */
if (aicfb_parse_fb_rotation(np, dt))
goto err_param;
if (aicfb_parse_fb_size(np, dt))
goto err_param;
aicfb_parse_disp_prop(np, dt);
/* get de device node */
dt->de_np = of_get_remote_by_port(np);
if (!dt->de_np) {
dev_err(&pdev->dev, "Can't get de%u device node\n", id);
goto err_de;
}
/* get encoder device node */
dt->di_np = of_get_remote_by_port_id(dt->de_np, AICFB_PORT_OUT);
if (!dt->di_np) {
dev_err(&pdev->dev, "Can't get encoder%u device node\n", id);
goto err_en;
}
/* get panel device node */
dt->panel_np = of_get_remote_by_port_id(dt->di_np, AICFB_PORT_OUT);
if (!dt->panel_np) {
dev_err(&pdev->dev, "Can't get panel%u device node\n", id);
goto err_panel;
}
ret = of_property_read_u32(np, "artinchip,uboot-logo-on", &uboot_logo_on);
if (ret) {
dev_err(&pdev->dev, "Can't parse uboot-logo-on property\n");
goto err_logo;
}
node = of_find_node_by_name(NULL, "aic-logo");
if (node) {
struct resource r;
ret = of_address_to_resource(node, 0, &r);
if (ret) {
dev_err(&pdev->dev, "Can't get resource\n");
goto err_res;
}
if (uboot_logo_on) {
uboot_logo_base = r.start;
uboot_logo_size = resource_size(&r);
dev_info(&pdev->dev,
"logo: base=%#llx, size=%#llx\n",
uboot_logo_base, uboot_logo_size);
}
}
return 0;
err_res:
of_node_put(node);
err_logo:
of_node_put(dt->panel_np);
dt->panel_np = NULL;
err_panel:
of_node_put(dt->di_np);
dt->di_np = NULL;
err_en:
of_node_put(dt->de_np);
dt->de_np = NULL;
err_de:
err_param:
of_node_put(dt->fb_np);
dt->fb_np = NULL;
err_fb:
return -EINVAL;
}
static int aicfb_alloc_fb(struct device *dev, u32 id)
{
struct aicfb_data *fbd = dev_get_drvdata(dev);
struct aicfb_info *fbi;
fbd->info[id] = framebuffer_alloc(sizeof(struct aicfb_info), dev);
if (!fbd->info[id])
return -ENOMEM;
fbi = (struct aicfb_info *)fbd->info[id]->par;
sprintf(fbi->name, "%s%d", AICFB_NAME, id);
return 0;
}
static int aicfb_find_panel(struct aicfb_data *fbd, u32 id)
{
struct platform_device *panel_pdev;
struct aicfb_info *fbi;
panel_pdev = of_find_device_by_node(fbd->dt_lists[id].panel_np);
if (!panel_pdev) {
dev_err(fbd->dev, "Failed to find panel node for fb%d\n", id);
return -EINVAL;
}
fbi = (struct aicfb_info *)fbd->info[id]->par;
fbi->panel = dev_get_drvdata(&panel_pdev->dev);
fbi->panel_dev = get_device(&panel_pdev->dev);
return 0;
}
static int aicfb_find_de(struct aicfb_data *fbd, u32 id)
{
struct platform_device *de_pdev;
struct aicfb_info *fbi;
de_pdev = of_find_device_by_node(fbd->dt_lists[id].de_np);
if (!de_pdev) {
dev_err(fbd->dev, "Failed to find de node for fb%d\n", id);
return -EINVAL;
}
fbi = (struct aicfb_info *)fbd->info[id]->par;
fbi->de = dev_get_drvdata(&de_pdev->dev);
fbi->de_dev = get_device(&de_pdev->dev);
return 0;
}
static int aicfb_find_di(struct aicfb_data *fbd, u32 id)
{
struct platform_device *di_pdev;
struct aicfb_info *fbi;
di_pdev = of_find_device_by_node(fbd->dt_lists[id].di_np);
if (!di_pdev) {
dev_err(fbd->dev, "Failed to find di node for fb%d\n", id);
return -EINVAL;
}
fbi = (struct aicfb_info *)fbd->info[id]->par;
fbi->di = dev_get_drvdata(&di_pdev->dev);
fbi->di_dev = get_device(&di_pdev->dev);
return 0;
}
static void aicfb_get_panel_info(struct aicfb_info *fbi,
struct videomode **vm)
{
struct de_funcs *de = fbi->de;
struct di_funcs *di = fbi->di;
struct aic_panel *panel = fbi->panel;
panel->funcs->get_video_mode(panel, vm);
de->set_mode(panel, *vm);
di->attach_panel(panel);
}
static void aicfb_register_panel_callback(struct aicfb_info *fbi)
{
struct aic_panel *p = fbi->panel;
struct aic_panel_callbacks cb;
cb.di_enable = fbi->di->enable;
cb.di_disable = fbi->di->disable;
cb.di_send_cmd = fbi->di->send_cmd;
cb.di_set_videomode = fbi->di->set_videomode;
cb.timing_enable = fbi->de->timing_enable;
cb.timing_disable = fbi->de->timing_disable;
p->funcs->register_callback(fbi->panel, &cb);
}
static void aicfb_enable_panel(struct aicfb_info *fbi, u32 on)
{
struct aic_panel *p = fbi->panel;
if (on == AICFB_ON) {
p->funcs->prepare(p);
p->funcs->enable(p);
} else {
p->funcs->disable(p);
p->funcs->unprepare(p);
}
}
static void aicfb_enable_clk(struct aicfb_info *fbi, u32 on)
{
ulong pixclk;
struct de_funcs *de = fbi->de;
struct di_funcs *di = fbi->di;
if (on == AICFB_ON) {
de->clk_enable();
pixclk = de->pixclk_rate();
di->pixclk2mclk(pixclk);
di->clk_enable();
de->pixclk_enable();
} else {
di->clk_disable();
de->clk_disable();
}
}
static void aicfb_update_alpha(struct aicfb_info *fbi)
{
struct aicfb_alpha_config alpha = {0};
struct de_funcs *de = fbi->de;
alpha.layer_id = AICFB_LAYER_TYPE_UI;
de->get_alpha_config(&alpha);
/* TODO: set the alpha config */
de->update_alpha_config(&alpha);
}
static void aicfb_update_layer(struct aicfb_info *fbi, struct aicfb_dt *dt)
{
struct aicfb_layer_data layer = {0};
struct de_funcs *de = fbi->de;
layer.layer_id = AICFB_LAYER_TYPE_UI;
layer.rect_id = 0;
de->get_layer_config(&layer);
layer.enable = 1;
switch (fbi->fb_rotate) {
case 0:
layer.buf.phy_addr[0] = fbi->fb_start_dma;
layer.buf.size.width = dt->width;
layer.buf.size.height = dt->height;
layer.buf.stride[0] = dt->stride;
break;
case 90:
case 270:
{
unsigned int stride = ALIGN_8B(dt->height * dt->format->bits_per_pixel / 8);
layer.buf.phy_addr[0] = fbi->fb_start_dma + fbi->fb_size;
layer.buf.size.width = dt->height;
layer.buf.size.height = dt->width;
layer.buf.stride[0] = stride;
break;
}
case 180:
layer.buf.phy_addr[0] = fbi->fb_start_dma + fbi->fb_size;
layer.buf.size.width = dt->width;
layer.buf.size.height = dt->height;
layer.buf.stride[0] = dt->stride;
break;
default:
pr_err("Invalid rotation degree\n");
return;
}
layer.buf.crop_en = 0;
layer.buf.format = dt->format->format;
layer.buf.flags = 0;
de->update_layer_config(&layer);
}
static void aicfb_update_disp_prop(struct aicfb_info *fbi)
{
struct de_funcs *de = fbi->de;
de->set_display_prop(fbi->disp_prop);
}
static void aicfb_de_timing_enable(struct aicfb_info *fbi)
{
struct de_funcs *de = fbi->de;
de->timing_enable(false);
}
static inline int aicfb_calc_fb_size(struct aicfb_info *fbi,
struct aicfb_dt *dt)
{
fbi->fb_size = dt->size;
if (!dt->rotation && !fbi->set_hsbc)
return fbi->fb_size;
if (dt->disp_buf_num == 2 && dt->height_virtual == dt->height)
return fbi->fb_size * 3;
return fbi->fb_size * 2;
}
static int aicfb_pm_suspend(struct device *dev);
static int aicfb_pm_resume(struct device *dev);
static ssize_t reset_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
bool enable;
int ret;
ret = kstrtobool(buf, &enable);
if (ret)
return ret;
if (enable) {
aicfb_pm_suspend(dev);
aic_delay_ms(20);
aicfb_pm_resume(dev);
}
return size;
}
static DEVICE_ATTR_WO(reset);
static void aicfb_disable_de_di(struct aicfb_info *fbi)
{
struct aicfb_layer_data layer = {0};
struct de_funcs *de = fbi->de;
struct di_funcs *di = fbi->di;
layer.layer_id = AICFB_LAYER_TYPE_UI;
layer.rect_id = 0;
de->get_layer_config(&layer);
layer.enable = 0;
de->update_layer_config(&layer);
di->disable();
de->timing_disable();
aicfb_enable_clk(fbi, AICFB_OFF);
}
static void aicfb_enable_de_di(struct aicfb_info *fbi)
{
struct aicfb_layer_data layer = {0};
struct de_funcs *de = fbi->de;
struct di_funcs *di = fbi->di;
aicfb_enable_clk(fbi, AICFB_ON);
layer.layer_id = AICFB_LAYER_TYPE_UI;
layer.rect_id = 0;
de->get_layer_config(&layer);
layer.enable = 1;
de->update_layer_config(&layer);
di->enable();
if (di->send_cmd)
di->send_cmd(0, NULL, 0);
de->timing_enable(true);
}
static ssize_t reset_de_di_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct aicfb_data *fbd = dev_get_drvdata(dev);
struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par;
bool enable;
int ret;
ret = kstrtobool(buf, &enable);
if (ret)
return ret;
if (!enable)
return size;
mutex_lock(&fbi->mutex);
aicfb_disable_de_di(fbi);
aic_delay_ms(20);
aicfb_enable_de_di(fbi);
mutex_unlock(&fbi->mutex);
return size;
}
static DEVICE_ATTR_WO(reset_de_di);
#define timing_config_attr(field) \
static ssize_t \
field##_show(struct device *dev, struct device_attribute *attr, \
char *buf) \
{ \
struct aicfb_data *fbd = dev_get_drvdata(dev); \
struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par;\
struct videomode *vm = fbi->panel->vm; \
\
return sprintf(buf, "%d\n", (u32)vm->field); \
} \
static ssize_t \
field##_store(struct device *dev, struct device_attribute *attr, \
const char *buf, size_t size) \
{ \
struct aicfb_data *fbd = dev_get_drvdata(dev); \
struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par;\
struct aic_panel *panel = fbi->panel; \
struct de_funcs *de = fbi->de; \
u32 field; \
int ret; \
\
ret = kstrtou32(buf, 0, &field); \
if (ret) \
return ret; \
\
panel->vm->field = field; \
de->set_mode(panel, panel->vm); \
\
return size; \
} \
static DEVICE_ATTR_RW(field);
timing_config_attr(pixelclock);
timing_config_attr(hactive);
timing_config_attr(hfront_porch);
timing_config_attr(hback_porch);
timing_config_attr(hsync_len);
timing_config_attr(vactive);
timing_config_attr(vfront_porch);
timing_config_attr(vback_porch);
timing_config_attr(vsync_len);
static ssize_t
di_type_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct aicfb_data *fbd = dev_get_drvdata(dev);
struct aicfb_dt *dt = &fbd->dt_lists[0];
s8 *type[] = {"rgb", "lvds", "dsi", "dbi"};
s32 i;
for (i = 0; i < ARRAY_SIZE(type); i++)
if (strncasecmp(dt->di_np->name, type[i], strlen(type[i])) == 0)
break;
return sprintf(buf, "display interface type: %d (%s)\n", i + 1, dt->di_np->name);
}
static DEVICE_ATTR_RO(di_type);
static struct attribute *aic_fb_attrs[] = {
&dev_attr_reset.attr,
&dev_attr_reset_de_di.attr,
&dev_attr_pixelclock.attr,
&dev_attr_hactive.attr,
&dev_attr_hfront_porch.attr,
&dev_attr_hback_porch.attr,
&dev_attr_hsync_len.attr,
&dev_attr_vactive.attr,
&dev_attr_vfront_porch.attr,
&dev_attr_vback_porch.attr,
&dev_attr_vsync_len.attr,
&dev_attr_di_type.attr,
NULL
};
static const struct attribute_group aic_fb_attr_group = {
.attrs = aic_fb_attrs,
.name = "debug",
};
static int aicfb_bind(struct device *dev)
{
struct aicfb_data *fbd = dev_get_drvdata(dev);
struct aicfb_dt *dt = &fbd->dt_lists[0];
struct aicfb_info *fbi;
struct videomode *vm;
u32 fb_size;
int ret;
ret = component_bind_all(dev, dev);
if (ret) {
dev_err(dev, "component_bind_all failed\n");
goto err_bind;
}
/* alloc fb0_info */
ret = aicfb_alloc_fb(dev, 0);
if (ret) {
dev_err(dev, "alloc fb0_info failed\n");
goto err_fb0;
}
fbi = (struct aicfb_info *)fbd->info[0]->par;
/* set display properties */
fbi->disp_prop = &dt->disp_prop;
fbi->set_hsbc = need_set_hsbc(fbi->disp_prop);
/* Find the component device, and get video mode */
aicfb_find_de(fbd, 0);
aicfb_find_di(fbd, 0);
aicfb_find_panel(fbd, 0);
aicfb_get_panel_info(fbi, &vm);
aicfb_register_panel_callback(fbi);
/* Initialize fb_var_screeninfo */
aicfb_videomode_to_fbdata(dt, vm);
aicfb_format_to_var(&fbd->info[0]->var, dt);
/* request fb0 framebuffer */
fb_size = aicfb_calc_fb_size(fbi, dt);
fbi->fb_start = dma_alloc_attrs(dev, PAGE_ALIGN(fb_size),
&fbi->fb_start_dma, GFP_KERNEL,
DMA_ATTR_WRITE_COMBINE);
if (fbi->fb_start == NULL) {
ret = -ENOMEM;
goto err_dma_alloc;
}
fbi->dma_coherent = true;
dev_info(dev, "%d allocated for %s, %#llx/%#llx\n",
fb_size, fbi->name, (u64)fbi->fb_start, fbi->fb_start_dma);
aicfb_fb_info_setup(fbd->info[0], fbd);
ret = fb_alloc_cmap(&fbd->info[0]->cmap, 256, 0);
if (ret < 0) {
dev_err(dev, "Failed to alloc cmap\n");
goto err_cmap;
}
/* Turn on the component device */
aicfb_enable_clk(fbi, AICFB_ON);
aicfb_update_alpha(fbi);
aicfb_update_disp_prop(fbi);
/* Avoid enable panel again */
if (uboot_logo_on && uboot_logo_size) {
void *src = phys_to_virt(uboot_logo_base);
void *dst = NULL;
if (fbi->fb_rotate)
dst = phys_to_virt(fbi->fb_start_dma + fbi->fb_size);
else
dst = fbi->fb_start;
memcpy(dst, src, uboot_logo_size);
/*
* When the logo data is ready, update UI layer.
* If update UI layer first, the screen will tear.
*/
aicfb_update_layer(fbi, dt);
/* Ensure that display engine interrupts are enabled */
aicfb_de_timing_enable(fbi);
} else {
aicfb_update_layer(fbi, dt);
aicfb_enable_panel(fbi, AICFB_ON);
}
ret = register_framebuffer(fbd->info[0]);
if (ret < 0) {
dev_err(dev, "Failed to register fb0: %d\n", ret);
ret = -ENXIO;
goto err_register;
}
dev_info(dev, "loaded to /dev/fb%d <%s>.\n",
fbd->info[0]->node, fbd->info[0]->fix.id);
ret = sysfs_create_group(&dev->kobj, &aic_fb_attr_group);
if (ret) {
dev_err(dev, "Failed to create %s node.\n",
aic_fb_attr_group.name);
return ret;
}
mutex_init(&fbi->mutex);
return 0;
err_register:
fb_dealloc_cmap(&fbd->info[0]->cmap);
err_cmap:
dma_free_coherent(dev, PAGE_ALIGN(fbi->fb_size), fbi->fb_start,
fbi->fb_start_dma);
err_dma_alloc:
framebuffer_release(fbd->info[0]);
err_fb0:
component_unbind_all(dev, dev);
err_bind:
return ret;
}
static void aicfb_unbind(struct device *dev)
{
struct aicfb_data *aicfb = dev_get_drvdata(dev);
component_unbind_all(dev, dev);
unregister_framebuffer(aicfb->info[0]);
framebuffer_release(aicfb->info[0]);
sysfs_remove_group(&dev->kobj, &aic_fb_attr_group);
}
static const struct component_master_ops aic_component_ops = {
.bind = aicfb_bind,
.unbind = aicfb_unbind,
};
static int aicfb_probe(struct platform_device *pdev)
{
struct component_match *match = NULL;
int ret;
struct aicfb_data *fbd;
fbd = devm_kzalloc(&pdev->dev, sizeof(*fbd), GFP_KERNEL);
if (!fbd)
return -ENOMEM;
dev_dbg(&pdev->dev, "%s()\n", __func__);
/* parse fb0 dt */
dev_set_drvdata(&pdev->dev, fbd);
ret = aicfb_parse_dt_by_fb_id(pdev, 0);
if (ret)
return ret;
/* add fb0 component */
aicfb_add_match_component(pdev, &match, 0);
ret = component_master_add_with_match(&pdev->dev,
&aic_component_ops, match);
if (ret) {
dev_err(&pdev->dev, "component_master_add_with_match failed\n");
return ret;
}
fbd->dev = &pdev->dev;
return 0;
}
void aicfb_release_device_node(struct device *dev, u32 id)
{
struct aicfb_data *fbd = dev_get_drvdata(dev);
struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[id]->par;
put_device(fbi->de_dev);
put_device(fbi->di_dev);
put_device(fbi->panel_dev);
of_node_put(fbd->dt_lists[id].fb_np);
of_node_put(fbd->dt_lists[id].de_np);
of_node_put(fbd->dt_lists[id].di_np);
of_node_put(fbd->dt_lists[id].panel_np);
}
static int aicfb_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &aic_component_ops);
aicfb_release_device_node(&pdev->dev, 0);
return 0;
}
static int aicfb_pm_suspend(struct device *dev)
{
struct aicfb_data *fbd = dev_get_drvdata(dev);
struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par;
struct aicfb_layer_data layer = {0};
struct de_funcs *de = fbi->de;
mutex_lock(&fbi->mutex);
aicfb_enable_panel(fbi, AICFB_OFF);
layer.layer_id = AICFB_LAYER_TYPE_UI;
layer.rect_id = 0;
de->get_layer_config(&layer);
layer.enable = 0;
de->update_layer_config(&layer);
aicfb_enable_clk(fbi, AICFB_OFF);
mutex_unlock(&fbi->mutex);
return 0;
}
static int aicfb_pm_resume(struct device *dev)
{
struct aicfb_data *fbd = dev_get_drvdata(dev);
struct aicfb_info *fbi = (struct aicfb_info *)fbd->info[0]->par;
struct aicfb_layer_data layer = {0};
struct de_funcs *de = fbi->de;
mutex_lock(&fbi->mutex);
aicfb_enable_clk(fbi, AICFB_ON);
aicfb_update_alpha(fbi);
layer.layer_id = AICFB_LAYER_TYPE_UI;
layer.rect_id = 0;
de->get_layer_config(&layer);
layer.enable = 1;
de->update_layer_config(&layer);
aicfb_enable_panel(fbi, AICFB_ON);
mutex_unlock(&fbi->mutex);
return 0;
}
#ifdef CONFIG_PM
static UNIVERSAL_DEV_PM_OPS(aicfb_pm_ops, aicfb_pm_suspend,
aicfb_pm_resume, NULL);
#endif
static const struct of_device_id aicfb_match_table[] = {
{.compatible = "artinchip,aic-framebuffer",},
{},
};
MODULE_DEVICE_TABLE(of, aicfb_match_table);
static struct platform_driver aicfb_driver = {
.probe = aicfb_probe,
.remove = aicfb_remove,
.driver = {
.name = AICFB_NAME,
.of_match_table = aicfb_match_table,
#ifdef CONFIG_PM
.pm = &aicfb_pm_ops,
#endif
},
};
module_platform_driver(aicfb_driver);
MODULE_AUTHOR("Ning Fang <ning.fang@artinchip.com>");
MODULE_DESCRIPTION("AIC framebuffer driver");
MODULE_ALIAS("platform:" AICFB_NAME);
MODULE_LICENSE("GPL v2");