1324 lines
35 KiB
C
1324 lines
35 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
|
|
/* sunxi_drm_drv.c
|
|
*
|
|
* Copyright (C) 2023 Allwinnertech Co., Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*/
|
|
#include <linux/version.h>
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_uapi.h>
|
|
#include <drm/drm_modes.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0)
|
|
#include <drm/drm_gem_cma_helper.h>
|
|
#else
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
|
|
#include <drm/drm_fbdev_dma.h>
|
|
#endif
|
|
#include <drm/drm_gem_dma_helper.h>
|
|
#endif
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
#include <drm/drm_vblank.h>
|
|
#include <drm/drm_file.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/component.h>
|
|
#include <linux/platform_device.h>
|
|
#include "sunxi_drm_drv.h"
|
|
#include "sunxi_drm_crtc.h"
|
|
#include "sunxi_drm_gem.h"
|
|
#include "sunxi_drm_debug.h"
|
|
|
|
#define DRIVER_NAME "sunxi-drm"
|
|
#define DRIVER_DESC "allwinnertech SoC DRM"
|
|
#define DRIVER_DATE "20230901"
|
|
#define DRIVER_MAJOR 3
|
|
#define DRIVER_MINOR 0
|
|
|
|
struct sunxi_init_connecting {
|
|
struct list_head list;
|
|
struct drm_crtc *crtc;
|
|
struct drm_connector *connector;
|
|
struct drm_display_mode *mode;
|
|
bool done;
|
|
/* TODO add eotf colorspace etc */
|
|
};
|
|
|
|
struct display_boot_info {
|
|
unsigned int de_id;
|
|
unsigned int tcon_id;
|
|
unsigned int tcon_top_id;
|
|
unsigned int connector_type; /* DRM_MODE_CONNECTOR_ */
|
|
unsigned int hw_id; /* DRM_MODE_CONNECTOR_ */
|
|
struct drm_display_mode mode;
|
|
/* unsigned int mode;
|
|
unsigned int format;
|
|
unsigned int bits;
|
|
unsigned int colorspace;
|
|
unsigned int eotf; */
|
|
|
|
enum de_format_space px_fmt_space;
|
|
enum de_yuv_sampling yuv_sampling;
|
|
enum de_eotf eotf;
|
|
enum de_color_space color_space;
|
|
enum de_color_range color_range;
|
|
enum de_data_bits data_bits;
|
|
|
|
struct sunxi_logo_info logo;
|
|
struct list_head list;
|
|
};
|
|
|
|
struct sunxi_drm_pri {
|
|
struct list_head connecting_head;
|
|
struct list_head boot_info_head;
|
|
};
|
|
|
|
extern struct platform_driver sunxi_de_platform_driver;
|
|
extern struct platform_driver sunxi_hdmi_platform_driver;
|
|
extern struct platform_driver sunxi_drm_edp_platform_driver;
|
|
extern struct platform_driver sunxi_tcon_platform_driver;
|
|
extern struct platform_driver sunxi_tcon_top_platform_driver;
|
|
extern struct platform_driver sunxi_dsi_platform_driver;
|
|
extern struct platform_driver sunxi_lvds_platform_driver;
|
|
extern struct platform_driver sunxi_rgb_platform_driver;
|
|
extern struct platform_driver sunxi_dsi_combo_phy_platform_driver;
|
|
|
|
int sunxi_fbdev_init(struct drm_device *drm, struct display_channel_state *out_state);
|
|
int sunxi_drm_plane_property_create(struct sunxi_drm_private *private);
|
|
|
|
static inline struct sunxi_drm_device *connector_to_sunxi_drm_device(struct drm_connector *connector)
|
|
{
|
|
return container_of(connector, struct sunxi_drm_device, connector);
|
|
}
|
|
|
|
static void sunxi_drm_gem_fb_destroy(struct drm_framebuffer *fb)
|
|
{
|
|
sunxidrm_debug_trace_framebuffer_unmap(fb);
|
|
drm_gem_fb_destroy(fb);
|
|
}
|
|
|
|
static const struct drm_framebuffer_funcs sunxi_drm_gem_fb_funcs = {
|
|
.destroy = sunxi_drm_gem_fb_destroy,
|
|
.create_handle = drm_gem_fb_create_handle,
|
|
};
|
|
|
|
struct drm_framebuffer *
|
|
sunxi_drm_gem_fb_create(struct drm_device *dev, struct drm_file *file,
|
|
const struct drm_mode_fb_cmd2 *mode_cmd)
|
|
{
|
|
return drm_gem_fb_create_with_funcs(dev, file, mode_cmd,
|
|
&sunxi_drm_gem_fb_funcs);
|
|
}
|
|
|
|
static int sunxi_drm_atomic_helper_commit(struct drm_device *dev,
|
|
struct drm_atomic_state *state,
|
|
bool nonblock)
|
|
{
|
|
struct drm_connector *conn;
|
|
struct drm_connector_state *old_conn_state, *new_conn_state;
|
|
struct drm_plane *plane;
|
|
struct drm_plane_state *old_plane_state, *new_plane_state;
|
|
int i;
|
|
|
|
/*
|
|
* Copy from drm_atomic_helper_setup_commit(linux-6.6.46)
|
|
*
|
|
* Avoid memory leak in such case:
|
|
* drm framework would miss a drm_crtc_commit_put which has get from
|
|
* "new_crtc_state->event->base.completion = &commit->flip_done", and
|
|
* should be put when crtc_send_vblank_event. But it is misiing because
|
|
* of check in "drm_atomic_helper_setup_commit" that lead commit abort,
|
|
* so we would never call crtc_send_vblank_event to release it.
|
|
*
|
|
* So we just copy the judgement code which would lead commit abort to
|
|
* here, to do judge earlier to avoid memory alloc if this commit would
|
|
* abort later, to avoid memory leak issue.
|
|
*/
|
|
for_each_oldnew_connector_in_state(state, conn, old_conn_state, new_conn_state, i) {
|
|
/*
|
|
* Userspace is not allowed to get ahead of the previous
|
|
* commit with nonblocking ones.
|
|
*/
|
|
if (nonblock && old_conn_state->commit &&
|
|
!try_wait_for_completion(&old_conn_state->commit->flip_done)) {
|
|
drm_dbg_dp(conn->dev,
|
|
"[CONNECTOR:%d:%s] busy because of previous commit\n",
|
|
conn->base.id, conn->name);
|
|
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) {
|
|
/*
|
|
* Userspace is not allowed to get ahead of the previous
|
|
* commit with nonblocking ones.
|
|
*/
|
|
if (nonblock && old_plane_state->commit &&
|
|
!try_wait_for_completion(&old_plane_state->commit->flip_done)) {
|
|
drm_dbg_dp(plane->dev,
|
|
"[PLANE:%d:%s] busy because of previous commit\n",
|
|
plane->base.id, plane->name);
|
|
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
return drm_atomic_helper_commit(dev, state, nonblock);
|
|
}
|
|
|
|
static const struct drm_mode_config_funcs sunxi_drm_mode_config_funcs = {
|
|
.atomic_check = drm_atomic_helper_check,
|
|
.atomic_commit = sunxi_drm_atomic_helper_commit,
|
|
/* .output_poll_changed = drm_fb_helper_output_poll_changed, */
|
|
.fb_create = sunxi_drm_gem_fb_create,
|
|
};
|
|
|
|
static void sunxi_drm_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
|
|
|
|
{
|
|
struct drm_device *dev = old_state->dev;
|
|
|
|
drm_atomic_helper_commit_modeset_disables(dev, old_state);
|
|
|
|
drm_atomic_helper_commit_modeset_enables(dev, old_state);
|
|
|
|
drm_atomic_helper_commit_planes(dev, old_state,
|
|
0);
|
|
|
|
drm_atomic_helper_fake_vblank(old_state);
|
|
|
|
drm_atomic_helper_commit_hw_done(old_state);
|
|
|
|
/* drm_atomic_helper_wait_for_vblanks(dev, old_state); */
|
|
|
|
drm_atomic_helper_cleanup_planes(dev, old_state);
|
|
}
|
|
|
|
|
|
static const struct drm_mode_config_helper_funcs sunxi_mode_config_helpers = {
|
|
.atomic_commit_tail = sunxi_drm_atomic_helper_commit_tail,
|
|
};
|
|
|
|
static void sunxi_drm_mode_config_init(struct drm_device *dev)
|
|
{
|
|
dev->mode_config.min_width = 0;
|
|
dev->mode_config.min_height = 0;
|
|
dev->mode_config.normalize_zpos = true;
|
|
|
|
/* max_width be decided by the de bufferline */
|
|
dev->mode_config.max_width = 8192;
|
|
dev->mode_config.max_height = 8192;
|
|
dev->mode_config.funcs = &sunxi_drm_mode_config_funcs;
|
|
dev->mode_config.helper_private = &sunxi_mode_config_helpers;
|
|
}
|
|
|
|
void sunxi_drm_unload(struct drm_device *dev)
|
|
{
|
|
drm_mode_config_cleanup(dev);
|
|
}
|
|
|
|
struct drm_connector *drm_get_disp_connector(struct drm_device *dev, int index)
|
|
{
|
|
struct drm_connector_list_iter iter;
|
|
struct drm_connector *connector;
|
|
|
|
index++;
|
|
drm_connector_list_iter_begin(dev, &iter);
|
|
drm_client_for_each_connector_iter(connector, &iter) {
|
|
if (connector->index == index)
|
|
break;
|
|
}
|
|
drm_connector_list_iter_end(&iter);
|
|
|
|
return (connector && connector->index == index) ? connector : NULL;
|
|
}
|
|
|
|
static int sunxi_de_pq_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
|
|
{
|
|
unsigned long *ubuffer = data;
|
|
struct drm_connector *conn = NULL;
|
|
struct sunxi_drm_device *sdrm;
|
|
u32 gamma_size = 0;
|
|
struct gamma_para *gamma_tmp = NULL;
|
|
u32 *k_lut = NULL;
|
|
u32 *u_lut;
|
|
void *para = NULL;
|
|
int ret = 0, cmd;
|
|
int pq_type = ubuffer[0];
|
|
int disp = ubuffer[1];
|
|
void __user *user_para = (void __user *)ubuffer[2];
|
|
int para_size = ubuffer[3];
|
|
|
|
/* note: if new pq_type add, should not modify ubuffer define,
|
|
* but rearrange to ubuffer defined before ioctl
|
|
*/
|
|
switch (pq_type) {
|
|
case PQ_SET_REG:
|
|
DRM_ERROR("==========disp = %d ===========\n", disp);
|
|
if (disp > 2 || disp < 0)
|
|
break;
|
|
|
|
para = kzalloc(para_size, GFP_KERNEL);
|
|
if (!para) {
|
|
DRM_ERROR("lcd para alloc failed\n");
|
|
ret = -ENOMEM;
|
|
goto OUT;
|
|
}
|
|
|
|
if (copy_from_user(para, (void __user *)user_para, para_size)) {
|
|
DRM_ERROR("regs copy from user failed\n");
|
|
ret = -EINVAL;
|
|
goto OUT;
|
|
}
|
|
|
|
conn = drm_get_disp_connector(dev, disp);
|
|
if (!conn) {
|
|
DRM_ERROR("Can not get disp:%d\n", disp);
|
|
goto OUT;
|
|
}
|
|
sdrm = connector_to_sunxi_drm_device(conn);
|
|
if (sdrm->set_disp_para)
|
|
sdrm->set_disp_para(dev, para);
|
|
break;
|
|
case PQ_GET_REG:
|
|
para = kzalloc(para_size, GFP_KERNEL);
|
|
if (!para) {
|
|
DRM_ERROR("lcd para alloc failed\n");
|
|
ret = -ENOMEM;
|
|
goto OUT;
|
|
}
|
|
|
|
DRM_ERROR("==========disp = %d ===========\n", disp);
|
|
if (disp > 2 || disp < 0)
|
|
break;
|
|
|
|
conn = drm_get_disp_connector(dev, disp);
|
|
if (!conn) {
|
|
DRM_ERROR("Can not get LCD%d\n", disp);
|
|
goto OUT;
|
|
}
|
|
sdrm = connector_to_sunxi_drm_device(conn);
|
|
if (sdrm->get_disp_para)
|
|
sdrm->get_disp_para(dev, para);
|
|
|
|
if (copy_to_user((void __user *)user_para, para, para_size)) {
|
|
DRM_ERROR("regs copy from user failed\n");
|
|
ret = -EINVAL;
|
|
goto OUT;
|
|
}
|
|
break;
|
|
case PQ_ENABLE:
|
|
/* noting to do */
|
|
break;
|
|
case PQ_COLOR_MATRIX:
|
|
case PQ_GAMMA:
|
|
case PQ_FCM:
|
|
case PQ_DCI:
|
|
case PQ_DLC:
|
|
case PQ_DEBAND:
|
|
case PQ_SHARP35X:
|
|
case PQ_SNR:
|
|
case PQ_GTM:
|
|
case PQ_ASU:
|
|
para = (void *)kzalloc(para_size, GFP_KERNEL);
|
|
if (!para) {
|
|
DRM_ERROR("pq para alloc fail\n");
|
|
ret = -ENOMEM;
|
|
goto OUT;
|
|
}
|
|
|
|
if (copy_from_user(para, (void __user *)user_para,
|
|
para_size)) {
|
|
DRM_ERROR("regs copy from user failed\n");
|
|
ret = -EINVAL;
|
|
goto OUT;
|
|
}
|
|
/* no mater what kinds of pq para, must follows with an int cmd first */
|
|
cmd = (*(int *)para);
|
|
|
|
if (pq_type == PQ_GAMMA) {
|
|
gamma_tmp = para;
|
|
gamma_size = gamma_tmp->size;
|
|
u_lut = gamma_tmp->lut;
|
|
k_lut = kzalloc(sizeof(u32) * gamma_size, GFP_KERNEL);
|
|
if (k_lut == NULL) {
|
|
DRM_ERROR("kzalloc struct gamma_lut failed!\n");
|
|
ret = -ENOMEM;
|
|
goto OUT;
|
|
}
|
|
if (cmd != PQ_READ) {
|
|
if (copy_from_user(k_lut, (void __user *)u_lut,
|
|
sizeof(u32) * gamma_size)) {
|
|
DRM_ERROR("gamma lut copy from user failed\n");
|
|
ret = -EINVAL;
|
|
goto OUT;
|
|
}
|
|
}
|
|
/* set para userspace lut to kernel lut */
|
|
gamma_tmp->lut = k_lut;
|
|
}
|
|
|
|
ret = sunxi_drm_crtc_pq_proc(dev, disp, pq_type, para);
|
|
if (ret)
|
|
goto OUT;
|
|
|
|
if (cmd == PQ_READ) {
|
|
if (pq_type == PQ_GAMMA) {
|
|
if (copy_to_user((void __user *)u_lut, k_lut,
|
|
sizeof(u32) * gamma_size)) {
|
|
DRM_ERROR("gamma lut copy to user failed\n");
|
|
ret = -EINVAL;
|
|
goto OUT;
|
|
}
|
|
/* reset para kernel lut to userspace lut */
|
|
gamma_tmp->lut = u_lut;
|
|
}
|
|
if (copy_to_user((void __user *)user_para, para,
|
|
para_size)) {
|
|
DRM_ERROR("copy to user REG failed\n");
|
|
ret = -EFAULT;
|
|
goto OUT;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
DRM_ERROR("pq cmd not found %d\n", pq_type);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
OUT:
|
|
if (para)
|
|
kfree(para);
|
|
if (k_lut)
|
|
kfree(k_lut);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct drm_ioctl_desc sunxi_drm_ioctls[] = {
|
|
DRM_IOCTL_DEF_DRV(SUNXI_PQ_PROC, sunxi_de_pq_ioctl, 0),
|
|
};
|
|
|
|
#if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 1, 0)
|
|
DEFINE_DRM_GEM_CMA_FOPS(sunxi_drm_driver_fops);
|
|
#else
|
|
DEFINE_DRM_GEM_DMA_FOPS(sunxi_drm_driver_fops);
|
|
#endif
|
|
static struct drm_driver sunxi_drm_driver = {
|
|
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
|
|
.fops = &sunxi_drm_driver_fops,
|
|
.ioctls = sunxi_drm_ioctls,
|
|
.num_ioctls = ARRAY_SIZE(sunxi_drm_ioctls),
|
|
.name = DRIVER_NAME,
|
|
.desc = DRIVER_DESC,
|
|
.date = DRIVER_DATE,
|
|
.major = DRIVER_MAJOR,
|
|
.minor = DRIVER_MINOR,
|
|
.gem_create_object = sunxi_gem_create_object,
|
|
#if LINUX_VERSION_CODE <= KERNEL_VERSION(6, 1, 0)
|
|
DRM_GEM_CMA_DRIVER_OPS_VMAP,
|
|
#else
|
|
DRM_GEM_DMA_DRIVER_OPS_VMAP,
|
|
#endif
|
|
};
|
|
|
|
#define DRV_PTR(drv, cond) (IS_ENABLED(cond) ? &drv : NULL)
|
|
static struct platform_driver *sunxi_drm_sub_drivers[] = {
|
|
DRV_PTR(sunxi_tcon_top_platform_driver, CONFIG_AW_DRM_TCON_TOP),
|
|
DRV_PTR(sunxi_de_platform_driver, CONFIG_AW_DRM_DE),
|
|
DRV_PTR(sunxi_dsi_combo_phy_platform_driver, CONFIG_AW_DRM_DSI_COMBOPHY),
|
|
DRV_PTR(sunxi_dsi_platform_driver, CONFIG_AW_DRM_DSI),
|
|
DRV_PTR(sunxi_lvds_platform_driver, CONFIG_AW_DRM_LVDS),
|
|
DRV_PTR(sunxi_rgb_platform_driver, CONFIG_AW_DRM_RGB),
|
|
DRV_PTR(sunxi_hdmi_platform_driver, CONFIG_AW_DRM_HDMI_TX),
|
|
DRV_PTR(sunxi_drm_edp_platform_driver, CONFIG_AW_DRM_EDP),
|
|
DRV_PTR(sunxi_tcon_platform_driver, CONFIG_AW_DRM_TCON),
|
|
/* TODO add tv */
|
|
};
|
|
|
|
static int compare_dev(struct device *dev, void *data)
|
|
{
|
|
return dev == (struct device *)data;
|
|
}
|
|
|
|
static struct component_match *sunxi_drm_match_add(struct device *dev)
|
|
{
|
|
struct component_match *match = NULL;
|
|
int i;
|
|
|
|
DRM_INFO("%s start\n", __FUNCTION__);
|
|
for (i = 0; i < ARRAY_SIZE(sunxi_drm_sub_drivers); ++i) {
|
|
struct platform_driver *drv = sunxi_drm_sub_drivers[i];
|
|
struct device *p = NULL, *d;
|
|
|
|
if (!drv)
|
|
continue;
|
|
|
|
while ((d = platform_find_device_by_driver(p, &drv->driver))) {
|
|
put_device(p);
|
|
device_link_add(dev, d, DL_FLAG_STATELESS);
|
|
component_match_add(dev, &match, compare_dev, d);
|
|
p = d;
|
|
}
|
|
put_device(p);
|
|
}
|
|
DRM_INFO("%s finish\n", __FUNCTION__);
|
|
|
|
return match ?: ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
static int sunxi_drm_property_create(struct sunxi_drm_private *private)
|
|
{
|
|
struct drm_device *dev = &private->base;
|
|
struct drm_property *prop;
|
|
int ret;
|
|
prop = drm_property_create(dev,
|
|
DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
|
|
"FEATURE", 0);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_feature = prop;
|
|
|
|
prop = drm_property_create(dev, DRM_MODE_PROP_BLOB,
|
|
"FRONTEND_DATA", 0);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_frontend_data = prop;
|
|
|
|
prop = drm_property_create(dev, DRM_MODE_PROP_BLOB,
|
|
"SUNXI_CTM", 0);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_sunxi_ctm = prop;
|
|
|
|
prop = drm_property_create(dev, DRM_MODE_PROP_BLOB,
|
|
"BACKEND_DATA", 0);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_backend_data = prop;
|
|
|
|
prop = drm_property_create_signed_range(dev, DRM_MODE_PROP_ATOMIC,
|
|
"EOTF", 0, 20);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_eotf = prop;
|
|
|
|
prop = drm_property_create_signed_range(dev, DRM_MODE_PROP_ATOMIC,
|
|
"COLOR_SPACE", 0, 20);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_color_space = prop;
|
|
|
|
prop = drm_property_create_signed_range(dev, DRM_MODE_PROP_ATOMIC,
|
|
"COLOR_FORMAT", 0, 20);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_color_format = prop;
|
|
|
|
prop = drm_property_create_signed_range(dev, DRM_MODE_PROP_ATOMIC,
|
|
"COLOR_DEPTH", 0, 20);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_color_depth = prop;
|
|
|
|
prop = drm_property_create_signed_range(dev, DRM_MODE_PROP_ATOMIC,
|
|
"COLOR_RANGE", 0, 20);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_color_range = prop;
|
|
|
|
/* create frame rate change property */
|
|
prop = drm_property_create_range(dev, 0, "FRAME_RATE_CHANGE", 0, 1);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_frame_rate_change = prop;
|
|
|
|
/*
|
|
* create image crop for afbc compressed buffer, top_crop and left_crop,
|
|
* top_crop : 0 ~ 15
|
|
* left_crop: 0 ~ 63
|
|
* value = (top_crop << 16) | left_crop
|
|
*/
|
|
prop = drm_property_create_range(dev, 0, "compressed_image_crop", 0, 0x000F003F);
|
|
if (!prop)
|
|
return -ENOMEM;
|
|
private->prop_compressed_image_crop = prop;
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
ret = drm_mode_create_tv_properties(dev, 0, NULL);
|
|
#else
|
|
ret = drm_mode_create_tv_properties_legacy(dev, 0, NULL);
|
|
#endif
|
|
if (ret)
|
|
return ret;
|
|
return sunxi_drm_plane_property_create(private);
|
|
}
|
|
|
|
static int sunxi_drm_offline_mode_pre_init(struct drm_device *drm)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(drm);
|
|
struct sunxi_init_connecting *c;
|
|
struct drm_crtc *crtc;
|
|
unsigned int scn_w, scn_h;
|
|
|
|
c = list_first_entry_or_null(&pri->priv->connecting_head, struct sunxi_init_connecting, list);
|
|
if (!c) {
|
|
DRM_ERROR("offline mode: init connecting not found %s\n", __func__);
|
|
return -1;
|
|
}
|
|
scn_w = c->mode->hdisplay;
|
|
scn_h = c->mode->vdisplay;
|
|
crtc = c->crtc;
|
|
|
|
return sunxi_drm_crtc_offline_mode_pre_init(crtc, scn_w, scn_h);
|
|
}
|
|
|
|
static int __maybe_unused commit_init_connecting(struct drm_device *drm)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(drm);
|
|
struct drm_modeset_acquire_ctx ctx;
|
|
struct drm_atomic_state *state;
|
|
struct drm_crtc_state *crtc_state;
|
|
struct drm_connector_state *new_conn_state;
|
|
struct drm_plane_state *plane_state;
|
|
struct drm_crtc *crtc;
|
|
struct sunxi_init_connecting *c;
|
|
struct display_channel_state channel;
|
|
struct display_channel_state *c_ref = &channel;
|
|
struct display_channel_state *c_commit = NULL;
|
|
int ret;
|
|
|
|
memset(c_ref, 0, sizeof(*c_ref));
|
|
sunxi_drm_offline_mode_pre_init(drm);
|
|
sunxi_fbdev_init(drm, &channel);
|
|
drm_modeset_acquire_init(&ctx, 0);
|
|
state = drm_atomic_state_alloc(drm);
|
|
if (!state) {
|
|
ret = -ENOMEM;
|
|
goto out_ctx;
|
|
}
|
|
|
|
state->acquire_ctx = &ctx;
|
|
retry:
|
|
list_for_each_entry(c, &pri->priv->connecting_head, list) {
|
|
crtc = c->crtc;
|
|
crtc_state = drm_atomic_get_crtc_state(state, crtc);
|
|
if (IS_ERR(crtc_state)) {
|
|
ret = PTR_ERR(crtc_state);
|
|
goto out_state;
|
|
}
|
|
|
|
ret = drm_atomic_set_mode_for_crtc(crtc_state, c->mode);
|
|
if (ret != 0) {
|
|
ret = PTR_ERR(new_conn_state);
|
|
goto out_state;
|
|
}
|
|
|
|
crtc_state->active = true;
|
|
new_conn_state = drm_atomic_get_connector_state(state, c->connector);
|
|
if (IS_ERR(new_conn_state)) {
|
|
ret = PTR_ERR(new_conn_state);
|
|
goto out_state;
|
|
}
|
|
|
|
ret = drm_atomic_set_crtc_for_connector(new_conn_state, crtc);
|
|
if (ret) {
|
|
DRM_ERROR("connector set crtc fail\n");
|
|
goto out_state;
|
|
}
|
|
if (c_ref->base.fb && crtc == c_ref->base.crtc) {
|
|
plane_state = drm_atomic_get_plane_state(state, c_ref->base.plane);
|
|
c_commit = to_display_channel_state(plane_state);
|
|
if (IS_ERR(plane_state)) {
|
|
ret = PTR_ERR(plane_state);
|
|
goto out_state;
|
|
}
|
|
/* do not use memcpy, ref_cnt */
|
|
plane_state->crtc_x = c_ref->base.crtc_x;
|
|
plane_state->crtc_y = c_ref->base.crtc_y;
|
|
plane_state->crtc_w = c_ref->base.crtc_w;
|
|
plane_state->crtc_h = c_ref->base.crtc_h;
|
|
plane_state->src_x = c_ref->base.src_x;
|
|
plane_state->src_y = c_ref->base.src_y;
|
|
plane_state->src_w = c_ref->base.src_w;
|
|
plane_state->src_h = c_ref->base.src_h;
|
|
plane_state->alpha = c_ref->base.alpha;
|
|
plane_state->pixel_blend_mode = c_ref->base.pixel_blend_mode;
|
|
plane_state->rotation = c_ref->base.rotation;
|
|
c_commit->eotf = c_ref->eotf;
|
|
c_commit->color_space = c_ref->color_space;
|
|
c_commit->color_range = c_ref->color_range;
|
|
drm_atomic_set_fb_for_plane(plane_state, c_ref->base.fb);
|
|
ret = drm_atomic_set_crtc_for_plane(plane_state, c_ref->base.crtc);
|
|
if (ret)
|
|
goto out_state;
|
|
c_ref->base.fb = NULL;
|
|
}
|
|
}
|
|
ret = drm_atomic_commit(state);
|
|
|
|
out_state:
|
|
if (ret == -EDEADLK)
|
|
goto backoff;
|
|
|
|
drm_atomic_state_put(state);
|
|
out_ctx:
|
|
drm_modeset_drop_locks(&ctx);
|
|
drm_modeset_acquire_fini(&ctx);
|
|
|
|
return ret;
|
|
|
|
backoff:
|
|
drm_atomic_state_clear(state);
|
|
drm_modeset_backoff(&ctx);
|
|
|
|
goto retry;
|
|
}
|
|
|
|
static int get_boot_display_info(struct drm_device *drm)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(drm);
|
|
struct display_boot_info *info;
|
|
struct drm_display_mode *mode;
|
|
struct device_node *routing;
|
|
struct device_node *mode_node;
|
|
char name[16];
|
|
int i, ret;
|
|
u32 read_val = 0;
|
|
|
|
INIT_LIST_HEAD(&pri->priv->boot_info_head);
|
|
for (i = 0; ; i++) {
|
|
snprintf(name, 16, "booting-%d", i);
|
|
|
|
routing = of_get_child_by_name(drm->dev->of_node, name);
|
|
if (!routing)
|
|
return 0;
|
|
|
|
info = kmalloc(sizeof(*info), GFP_KERNEL | __GFP_ZERO);
|
|
ret = of_property_read_u32_array(routing, "logo", &info->logo.phy_addr, 4);
|
|
if (ret) {
|
|
of_node_put(routing);
|
|
kfree(info);
|
|
return 0;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(routing, "route", &info->de_id, 5);
|
|
if (ret) {
|
|
of_node_put(routing);
|
|
kfree(info);
|
|
return 0;
|
|
}
|
|
|
|
/* parse add mode info */
|
|
mode = &info->mode;
|
|
mode_node = of_get_child_by_name(routing, "mode");
|
|
if (!mode_node) {
|
|
DRM_ERROR("find mode node failed\n");
|
|
return 0;
|
|
}
|
|
|
|
#define of_mode_read(name, value) \
|
|
do { \
|
|
of_property_read_u32(mode_node, name, &read_val); \
|
|
value = read_val; \
|
|
} while (0)
|
|
|
|
of_mode_read("clock", mode->clock);
|
|
of_mode_read("hdisplay", mode->hdisplay);
|
|
of_mode_read("vdisplay", mode->vdisplay);
|
|
of_mode_read("hsync_start", mode->hsync_start);
|
|
of_mode_read("vsync_start", mode->vsync_start);
|
|
of_mode_read("hsync_end", mode->hsync_end);
|
|
of_mode_read("vsync_end", mode->vsync_end);
|
|
of_mode_read("htotal", mode->htotal);
|
|
of_mode_read("vtotal", mode->vtotal);
|
|
of_mode_read("vscan", mode->vscan);
|
|
of_mode_read("flags", mode->flags);
|
|
of_mode_read("hskew", mode->hskew);
|
|
of_mode_read("type", mode->type);
|
|
of_mode_read("width-mm", mode->width_mm);
|
|
of_mode_read("height-mm", mode->height_mm);
|
|
#undef of_mode_read
|
|
|
|
drm_mode_set_name(mode);
|
|
drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
|
|
|
|
DRM_DEBUG_DRIVER("boot mode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode));
|
|
|
|
list_add_tail(&info->list, &pri->priv->boot_info_head);
|
|
|
|
of_node_put(mode_node);
|
|
of_node_put(routing);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int init_connecting(struct drm_device *drm, struct drm_crtc **crtcs, unsigned int crtc_cnt,
|
|
struct drm_connector **connectors, unsigned int connector_cnt)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(drm);
|
|
struct sunxi_init_connecting *c;
|
|
struct sunxi_drm_device *sdrm;
|
|
struct drm_crtc *crtc_ = NULL;
|
|
struct drm_connector *connector_ = NULL;
|
|
struct display_boot_info *info;
|
|
struct drm_display_mode *mode = NULL;
|
|
int i, id, modes_count, init_cnt = 0, mode_found = 0;
|
|
|
|
if (!crtc_cnt || !connector_cnt) {
|
|
DRM_ERROR("connector or crtc null\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&pri->priv->connecting_head);
|
|
list_for_each_entry(info, &pri->priv->boot_info_head, list) {
|
|
connector_ = NULL;
|
|
crtc_ = NULL;
|
|
mode = NULL;
|
|
for (i = 0; i < crtc_cnt; i++) {
|
|
id = sunxi_drm_crtc_get_hw_id(crtcs[i]);
|
|
if (id == info->de_id) {
|
|
crtc_ = crtcs[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!crtc_)
|
|
return -ENODEV;
|
|
|
|
DRM_DEBUG_DRIVER("%s %d connector cnt %d want hw_id %d type %d\n",
|
|
__func__, __LINE__, connector_cnt, info->hw_id, info->connector_type);
|
|
for (i = 0; i < connector_cnt; i++) {
|
|
sdrm = container_of(connectors[i], struct sunxi_drm_device, connector);
|
|
DRM_DEBUG_DRIVER("%s %d name:%s type:%d hw_id:%d\n",
|
|
__func__, __LINE__, connectors[i]->name,
|
|
connectors[i]->connector_type, sdrm->hw_id);
|
|
if (info->connector_type == connectors[i]->connector_type &&
|
|
info->hw_id == sdrm->hw_id) {
|
|
mutex_lock(&drm->mode_config.mutex);
|
|
modes_count = connectors[i]->funcs->fill_modes(connectors[i], 8192, 8192);
|
|
|
|
list_for_each_entry(mode, &connectors[i]->modes, head) {
|
|
if (mode && drm_mode_equal(&info->mode, mode)) {
|
|
mode_found = 1;
|
|
break;
|
|
}
|
|
};
|
|
|
|
if (mode_found || !modes_count)
|
|
mode = &info->mode;
|
|
else
|
|
mode = list_first_entry_or_null(&connectors[i]->modes, struct drm_display_mode, head);
|
|
DRM_DEBUG_DRIVER("%s use mode %dx%d\n", __FUNCTION__, mode->hdisplay, mode->vdisplay);
|
|
mutex_unlock(&drm->mode_config.mutex);
|
|
|
|
if (mode) {
|
|
drm_connector_get(connectors[i]);
|
|
connector_ = connectors[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!connector_)
|
|
return -ENODEV;
|
|
|
|
c = kmalloc(sizeof(*c), GFP_KERNEL | __GFP_ZERO);
|
|
c->crtc = crtc_;
|
|
c->connector = connector_;
|
|
c->mode = mode;
|
|
list_add_tail(&c->list, &pri->priv->connecting_head);
|
|
init_cnt++;
|
|
}
|
|
|
|
/* no bootloader connecting info, use the first one we found */
|
|
if (init_cnt == 0) {
|
|
drm_connector_get(connectors[0]);
|
|
mutex_lock(&drm->mode_config.mutex);
|
|
modes_count = connectors[0]->funcs->fill_modes(connectors[0], 8192, 8192);
|
|
/* DRM_INFO("%s found mode %d\n", __FUNCTION__, modes_count); */
|
|
mode = list_first_entry_or_null(&connectors[0]->modes, struct drm_display_mode, head);
|
|
if (mode) {
|
|
drm_connector_get(connectors[0]);
|
|
c = kmalloc(sizeof(*c), GFP_KERNEL | __GFP_ZERO);
|
|
c->crtc = crtcs[0];
|
|
c->connector = connectors[0];
|
|
c->mode = mode;
|
|
list_add_tail(&c->list, &pri->priv->connecting_head);
|
|
} else
|
|
DRM_ERROR("none mode found %s\n", __func__);
|
|
mutex_unlock(&drm->mode_config.mutex);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int sunxi_drm_get_logo_info(struct drm_device *dev, struct sunxi_logo_info *logo,
|
|
unsigned int *scn_w, unsigned int *scn_h)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(dev);
|
|
struct display_boot_info *info;
|
|
struct sunxi_init_connecting *c;
|
|
/* offline mode add */
|
|
struct drm_crtc *crtc;
|
|
void *vir_addr;
|
|
unsigned long buff_size;
|
|
|
|
|
|
info = list_first_entry_or_null(&pri->priv->boot_info_head, struct display_boot_info, list);
|
|
if (info)
|
|
memcpy(logo, &info->logo, sizeof(*logo));
|
|
|
|
c = list_first_entry_or_null(&pri->priv->connecting_head, struct sunxi_init_connecting, list);
|
|
if (!c) {
|
|
DRM_ERROR("init connecting not found %s\n", __func__);
|
|
return -1;
|
|
}
|
|
*scn_w = c->mode->hdisplay;
|
|
*scn_h = c->mode->vdisplay;
|
|
|
|
crtc = c->crtc;
|
|
if (sunxi_drm_crtc_get_offline_mode_info(crtc, &vir_addr, &buff_size) >= 0) {
|
|
logo->offline_vaddr = vir_addr;
|
|
} else {
|
|
logo->offline_vaddr = NULL;
|
|
}
|
|
|
|
if (!info) {
|
|
logo->phy_addr = 0;
|
|
logo->width = c->mode->hdisplay;
|
|
logo->height = c->mode->vdisplay;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sunxi_drm_get_device_max_fps(struct drm_device *drm)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(drm);
|
|
struct sunxi_init_connecting *c;
|
|
struct drm_display_mode *mode;
|
|
int max_vrefrsh = 0;
|
|
|
|
list_for_each_entry(c, &pri->priv->connecting_head, list) {
|
|
list_for_each_entry(mode, &c->connector->modes, head) {
|
|
max_vrefrsh = max_vrefrsh > drm_mode_vrefresh(mode) ?
|
|
max_vrefrsh : drm_mode_vrefresh(mode);
|
|
};
|
|
}
|
|
|
|
return max_vrefrsh == 0 ? 60 : max_vrefrsh;
|
|
}
|
|
|
|
unsigned int sunxi_drm_get_de_max_freq(struct drm_device *drm)
|
|
{
|
|
struct drm_mode_config *config = &drm->mode_config;
|
|
struct drm_crtc *crtc;
|
|
unsigned int de_max_freq = 300000000;
|
|
unsigned tmp_freq = 0;
|
|
|
|
/* get max freq from exist de, and limit minimum to 300M */
|
|
list_for_each_entry(crtc, &config->crtc_list, head) {
|
|
tmp_freq = sunxi_drm_crtc_get_clk_freq(crtc);
|
|
de_max_freq = (de_max_freq > tmp_freq) ? de_max_freq : tmp_freq;
|
|
}
|
|
|
|
return de_max_freq;
|
|
}
|
|
|
|
|
|
bool sunxi_drm_check_if_need_sw_enable(struct drm_connector *connector)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(connector->dev);
|
|
struct sunxi_init_connecting *c;
|
|
struct display_boot_info *info;
|
|
|
|
info = list_first_entry_or_null(&pri->priv->boot_info_head, struct display_boot_info, list);
|
|
if (!info)
|
|
return false;
|
|
|
|
list_for_each_entry(c, &pri->priv->connecting_head, list) {
|
|
if (!c->done && connector == c->connector)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool sunxi_drm_check_device_boot_enabled(struct drm_device *drm,
|
|
unsigned int connector_type, unsigned int hw_id)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(drm);
|
|
struct display_boot_info *info;
|
|
|
|
list_for_each_entry(info, &pri->priv->boot_info_head, list) {
|
|
if ((info->connector_type == connector_type) &&
|
|
(info->hw_id == hw_id))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool sunxi_drm_check_tcon_top_boot_enabled(struct drm_device *drm, unsigned int tcon_top_id)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(drm);
|
|
struct display_boot_info *info;
|
|
|
|
list_for_each_entry(info, &pri->priv->boot_info_head, list) {
|
|
if (info->tcon_top_id == tcon_top_id)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool sunxi_drm_check_de_boot_enabled(struct drm_device *drm, unsigned int de_id)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(drm);
|
|
struct display_boot_info *info;
|
|
|
|
list_for_each_entry(info, &pri->priv->boot_info_head, list) {
|
|
if (info->de_id == de_id)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void sunxi_drm_signal_sw_enable_done(struct drm_crtc *crtc)
|
|
{
|
|
struct sunxi_drm_private *pri = to_sunxi_drm_private(crtc->dev);
|
|
struct sunxi_init_connecting *c;
|
|
list_for_each_entry(c, &pri->priv->connecting_head, list) {
|
|
if (crtc == c->crtc) {
|
|
WARN_ON(c->done);
|
|
c->done = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int __maybe_unused setup_bootloader_connecting_state(struct drm_device *drm)
|
|
{
|
|
struct drm_connector_list_iter conn_iter;
|
|
struct drm_connector *connector, **connectors = NULL;
|
|
unsigned int connector_count = 0;
|
|
unsigned int num_crtc = drm->mode_config.num_crtc;
|
|
struct drm_crtc *crtc, **crtcs;
|
|
int i = 0, ret;
|
|
|
|
/* count = get_boot_display_info(drm); */
|
|
|
|
/* get all connector */
|
|
drm_connector_list_iter_begin(drm, &conn_iter);
|
|
drm_client_for_each_connector_iter(connector, &conn_iter) {
|
|
struct drm_connector **tmp;
|
|
|
|
tmp = krealloc(connectors, (connector_count + 1) * sizeof(*connectors), GFP_KERNEL);
|
|
if (!tmp) {
|
|
ret = -ENOMEM;
|
|
goto free_connectors;
|
|
}
|
|
|
|
connectors = tmp;
|
|
drm_connector_get(connector);
|
|
connectors[connector_count++] = connector;
|
|
}
|
|
drm_connector_list_iter_end(&conn_iter);
|
|
if (!connector_count) {
|
|
DRM_ERROR("connector conut zero\n");
|
|
return -1;
|
|
}
|
|
|
|
/* get all crtc */
|
|
crtcs = kcalloc(num_crtc, sizeof(*crtcs), GFP_KERNEL);
|
|
drm_for_each_crtc(crtc, drm)
|
|
crtcs[i++] = crtc;
|
|
|
|
/* setup connecting */
|
|
ret = init_connecting(drm, crtcs, num_crtc, connectors, connector_count);
|
|
if (ret)
|
|
goto free_connectors;
|
|
|
|
free_connectors:
|
|
for (i = 0; i < connector_count; i++)
|
|
drm_connector_put(connectors[i]);
|
|
kfree(connectors);
|
|
kfree(crtcs);
|
|
return ret;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PROC_FS)
|
|
static struct proc_dir_entry *proc_entry;
|
|
|
|
static int sunxi_drm_procfs_status_show(struct seq_file *m, void *data)
|
|
{
|
|
struct drm_device *drm_dev = (struct drm_device *)m->private;
|
|
struct drm_printer p = drm_seq_file_printer(m);
|
|
|
|
drm_state_dump(drm_dev, &p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_drm_procfs_create(void)
|
|
{
|
|
proc_entry = proc_mkdir("sunxi-drm", NULL);
|
|
if (IS_ERR_OR_NULL(proc_entry)) {
|
|
pr_err("Couldn't create sunxi-drm procfs directory !\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_drm_procfs_init(struct drm_device *drm)
|
|
{
|
|
proc_create_single_data("debug", 444, proc_entry, sunxidrm_debug_show, NULL);
|
|
|
|
proc_create_single_data("status", 444, proc_entry,
|
|
sunxi_drm_procfs_status_show, drm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sunxi_drm_procfs_term(void)
|
|
{
|
|
remove_proc_subtree("sunxi-drm", NULL);
|
|
proc_entry = NULL;
|
|
}
|
|
|
|
struct proc_dir_entry *sunxi_drm_get_procfs_dir(void)
|
|
{
|
|
return proc_entry;
|
|
}
|
|
|
|
#else
|
|
struct proc_dir_entry *sunxi_drm_get_procfs_dir(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int sunxi_drm_bind(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct drm_device *drm;
|
|
struct sunxi_drm_private *private;
|
|
struct drm_encoder *encoder;
|
|
unsigned int clone_mask = 0;
|
|
|
|
DRM_INFO("%s start\n", __FUNCTION__);
|
|
// private = devm_drm_dev_alloc(dev, &sunxi_drm_driver,
|
|
// struct sunxi_drm_private, base);
|
|
private = __devm_drm_dev_alloc(dev, &sunxi_drm_driver,
|
|
sizeof(*private) + sizeof(struct sunxi_drm_pri),
|
|
offsetof(struct sunxi_drm_private, base));
|
|
drm = &private->base;
|
|
if (IS_ERR(private))
|
|
return PTR_ERR(private);
|
|
|
|
private->priv = ((void *)private) + sizeof(*private);
|
|
ret = drmm_mode_config_init(drm);
|
|
if (ret) {
|
|
DRM_ERROR("drmm_mode_config_init fail %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
sunxi_drm_mode_config_init(drm);
|
|
sunxi_drm_property_create(private);
|
|
|
|
get_boot_display_info(drm);
|
|
|
|
ret = component_bind_all(dev, drm);
|
|
if (ret)
|
|
goto mode_config_clean;
|
|
|
|
/*
|
|
* We assume that all encoders can clone each other,
|
|
* Otherwise writeback will fail in kernels after version 6.6.
|
|
*/
|
|
drm_for_each_encoder(encoder, drm) {
|
|
clone_mask |= BIT(drm_encoder_index(encoder));
|
|
}
|
|
drm_for_each_encoder(encoder, drm) {
|
|
encoder->possible_clones = clone_mask;
|
|
}
|
|
|
|
dev_set_drvdata(dev, drm);
|
|
drm_mode_config_reset(drm);
|
|
|
|
/* Enable connectors polling * */
|
|
drm_kms_helper_poll_init(drm);
|
|
ret = setup_bootloader_connecting_state(drm);
|
|
if (ret < 0) {
|
|
DRM_ERROR("setup bootloader connecting failed.Skip commit_init_connecting.\n");
|
|
goto dev_register;
|
|
}
|
|
commit_init_connecting(drm);
|
|
|
|
dev_register:
|
|
ret = drm_dev_register(drm, 0);
|
|
|
|
#if IS_ENABLED(CONFIG_PROC_FS)
|
|
ret = sunxi_drm_procfs_init(drm);
|
|
#endif
|
|
|
|
DRM_INFO("%s ok\n", __FUNCTION__);
|
|
return 0;
|
|
|
|
mode_config_clean:
|
|
drm_mode_config_cleanup(drm);
|
|
DRM_INFO("%s fail ret = %d\n", __FUNCTION__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void sunxi_drm_unbind(struct device *dev)
|
|
{
|
|
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
|
|
|
#if IS_ENABLED(CONFIG_PROC_FS)
|
|
sunxi_drm_procfs_term();
|
|
#endif
|
|
dev_set_drvdata(dev, NULL);
|
|
drm_dev_unregister(drm_dev);
|
|
drm_kms_helper_poll_fini(drm_dev);
|
|
drm_atomic_helper_shutdown(drm_dev);
|
|
component_unbind_all(dev, drm_dev);
|
|
}
|
|
|
|
static const struct component_master_ops sunxi_drm_ops = {
|
|
.bind = sunxi_drm_bind,
|
|
.unbind = sunxi_drm_unbind,
|
|
};
|
|
|
|
static int sunxi_drm_platform_probe(struct platform_device *pdev)
|
|
{
|
|
struct component_match *match;
|
|
|
|
DRM_INFO("%s start\n", __FUNCTION__);
|
|
match = sunxi_drm_match_add(&pdev->dev);
|
|
if (IS_ERR(match)) {
|
|
DRM_INFO("sunxi_drm_match_add fail\n");
|
|
return PTR_ERR(match);
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PROC_FS)
|
|
sunxi_drm_procfs_create();
|
|
#endif
|
|
|
|
return component_master_add_with_match(&pdev->dev, &sunxi_drm_ops,
|
|
match);
|
|
}
|
|
|
|
static int sunxi_drm_platform_remove(struct platform_device *pdev)
|
|
{
|
|
component_master_del(&pdev->dev, &sunxi_drm_ops);
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PM_SLEEP)
|
|
static int sunxi_drm_suspend(struct device *dev)
|
|
{
|
|
struct drm_device *drm = dev_get_drvdata(dev);
|
|
|
|
return drm_mode_config_helper_suspend(drm);
|
|
}
|
|
|
|
static int sunxi_drm_resume(struct device *dev)
|
|
{
|
|
struct drm_device *drm = dev_get_drvdata(dev);
|
|
|
|
return drm_mode_config_helper_resume(drm);
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops sunxi_drm_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(sunxi_drm_suspend,
|
|
sunxi_drm_resume)
|
|
};
|
|
|
|
static const struct of_device_id sunxi_of_match[] = {
|
|
{
|
|
.compatible = "allwinner,sunxi-drm",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sunxi_of_match);
|
|
|
|
static struct platform_driver sunxi_drm_platform_driver = {
|
|
.probe = sunxi_drm_platform_probe,
|
|
.remove = sunxi_drm_platform_remove,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "sunxi-drm",
|
|
.of_match_table = sunxi_of_match,
|
|
.pm = &sunxi_drm_pm_ops,
|
|
},
|
|
};
|
|
|
|
static void sunxi_drm_unregister_drivers(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(sunxi_drm_sub_drivers); ++i) {
|
|
struct platform_driver *drv = sunxi_drm_sub_drivers[i];
|
|
if (!drv)
|
|
continue;
|
|
|
|
platform_driver_unregister(drv);
|
|
}
|
|
}
|
|
|
|
static int sunxi_drm_register_drivers(void)
|
|
{
|
|
int i, ret;
|
|
for (i = 0; i < ARRAY_SIZE(sunxi_drm_sub_drivers); ++i) {
|
|
struct platform_driver *drv = sunxi_drm_sub_drivers[i];
|
|
if (!drv)
|
|
continue;
|
|
|
|
ret = platform_driver_register(drv);
|
|
if (ret) {
|
|
DRM_ERROR("driver register fail %d %ld\n", i,
|
|
ARRAY_SIZE(sunxi_drm_sub_drivers));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* add drm master device */
|
|
ret = platform_driver_register(&sunxi_drm_platform_driver);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __init sunxi_drm_drv_init(void)
|
|
{
|
|
int ret;
|
|
ret = sunxi_drm_register_drivers();
|
|
if (ret)
|
|
sunxi_drm_unregister_drivers();
|
|
return ret;
|
|
}
|
|
|
|
static void __exit sunxi_drm_drv_exit(void)
|
|
{
|
|
sunxi_drm_unregister_drivers();
|
|
}
|
|
|
|
module_init(sunxi_drm_drv_init);
|
|
module_exit(sunxi_drm_drv_exit);
|
|
|
|
MODULE_IMPORT_NS(DMA_BUF);
|
|
MODULE_DESCRIPTION("Allwinnertech SoC DRM Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION("V1.1.6");
|
|
MODULE_AUTHOR("chenqingjia <chenqingjia@allwinnertech.com>");
|