1.概述
手头有一块RK3568的开发板OK3568-C,但是还没有适配OpenHarmony,用的还是LVDS屏幕,但是官方和网上好像还没有OpenHarmony4.0的LVDS屏幕驱动的通用实现,所以决定尝试了一下适配该开发板,完成LVDS屏幕驱动的适配,点亮屏幕。
源代码:oh4.0-lvds-ok3568-c
2.具体实现
2.1 添加ok3568产品(非必须,可以跳过,直接修改原有的rk3568产品)
因为OK3568-C开发板使用的是RK3568芯片,和OpenHarmony的主线分支一样,所以添加ok3568产品的基础流程比较简单,就是复制device和vendor下面rk3568的文件夹,改为ok3568,并加入ok3568对应的设备树文件,修改build文件下面的编译配置文件(不修改编译会报错),具体流程可以参考官方和网上的其他教程。
2.2 分析原有的MIPI屏幕驱动
参考鸿蒙系统原有的MIPI屏幕驱动目录(drivers/framework/model/display/driver)下面的ili9881_st_5p5.c和hdf_drm_panel.c:
可以看出鸿蒙原有的MIPI屏幕驱动实际上参考了Linux内核的drivers/gpu/drm/panel/panel-simple.c的“simple-panel-dsi”部分,不知道为什么官方没有把panel-simple.c中剩下的“simple-panel”部分加进去。而“simple-panel”剩余的部分就是LVDS和EDP这类屏幕通用的驱动。
2.3 实现LVDS屏幕驱动
这里我们也参考Linux内核的drivers/gpu/drm/panel/panel-simple.c
2.3.1 读取设备树里面的"simple-panel"节点,获取Panel的参数
添加如下两个文件:
panel_simple_common.h
/*
*
* HDF is dual licensed: you can use it either under the terms of
* the GPL, or the BSD license, at your option.
* See the LICENSE file in the root of this repository for complete details.
*/
#ifndef PANEL_SIMPLE_COMMON_H
#define PANEL_SIMPLE_COMMON_H
#include <linux/backlight.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/regulator/consumer.h>
#include <linux/gpio/consumer.h>
#include <video/display_timing.h>
#include <video/mipi_display.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
#include <uapi/drm/drm_mode.h>
#include "securec.h"
#include "hdf_disp.h"
#include "hdf_drm_panel_simple.h"
#endif /* PANEL_SIMPLE_COMMON_H */
panel_simple_common.c
/*
*
* HDF is dual licensed: you can use it either under the terms of
* the GPL, or the BSD license, at your option.
* See the LICENSE file in the root of this repository for complete details.
*/
#include "panel_simple_common.h"
#include "gpio_if.h"
#include "hdf_bl.h"
#include "hdf_disp.h"
#include "osal.h"
static inline struct panel_simple *to_panel_simple(const struct drm_panel *panel)
{
return container_of(panel, struct panel_simple, panel);
}
static inline struct panel_simple *to_panel_simple_by_data(const struct PanelData *panel_data)
{
return container_of(panel_data, struct panel_simple, panel_data);
}
static int panel_simple_parse_cmd_seq(struct device *dev,
const u8 *data, int length,
struct panel_cmd_seq *seq)
{
struct panel_cmd_header *header;
struct panel_cmd_desc *desc;
char *buf, *d;
unsigned int i, cnt, len;
if (!seq)
return -EINVAL;
buf = (u8 *)OsalMemCalloc(length);
if (!buf)
return -ENOMEM;
memcpy_s(buf, length, data, length);
d = buf;
len = length;
cnt = 0;
while (len > sizeof(*header)) {
header = (struct panel_cmd_header *)d;
d += sizeof(*header);
len -= sizeof(*header);
if (header->payload_length > len)
return -EINVAL;
d += header->payload_length;
len -= header->payload_length;
cnt++;
}
if (len)
return -EINVAL;
seq->cmd_cnt = cnt;
seq->cmds = (struct panel_cmd_desc *)OsalMemCalloc(sizeof(*desc));
if (!seq->cmds)
return -ENOMEM;
d = buf;
len = length;
for (i = 0; i < cnt; i++) {
header = (struct panel_cmd_header *)d;
len -= sizeof(*header);
d += sizeof(*header);
desc = &seq->cmds[i];
desc->header = *header;
desc->payload = d;
d += header->payload_length;
len -= header->payload_length;
}
return 0;
}
static int32_t panel_simple_regulator_enable(struct panel_simple *p)
{
int32_t err;
if (p->power_invert) {
if (regulator_is_enabled(p->supply) > 0)
regulator_disable(p->supply);
} else {
err = regulator_enable(p->supply);
if (err < 0)
return err;
}
return 0;
}
static int32_t panel_simple_regulator_disable(struct panel_simple *p)
{
int err;
if (p->power_invert) {
if (!regulator_is_enabled(p->supply)) {
err = regulator_enable(p->supply);
if (err < 0)
return err;
}
} else {
regulator_disable(p->supply);
}
return 0;
}
int panel_simple_loader_protect(struct drm_panel *panel)
{
struct panel_simple *p = to_panel_simple(panel);
int err;
err = panel_simple_regulator_enable(p);
if (err < 0) {
HDF_LOGE("failed to enable supply: %d\n", err);
return err;
}
p->prepared = true;
p->enabled = true;
return 0;
}
EXPORT_SYMBOL(panel_simple_loader_protect);
static int panel_simple_get_hpd_gpio(struct device *dev,
struct panel_simple *p, bool from_probe)
{
int err;
p->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
if (IS_ERR(p->hpd_gpio)) {
err = PTR_ERR(p->hpd_gpio);
/*
* If we're called from probe we won't consider '-EPROBE_DEFER'
* to be an error--we'll leave the error code in "hpd_gpio".
* When we try to use it we'll try again. This allows for
* circular dependencies where the component providing the
* hpd gpio needs the panel to init before probing.
*/
if (err != -EPROBE_DEFER || !from_probe) {
HDF_LOGE("failed to get 'hpd' GPIO: %d\n", err);
return err;
}
}
return 0;
}
#define PANEL_SIMPLE_BOUNDS_CHECK(to_check, bounds, field) \
(to_check->field.typ >= bounds->field.min && \
to_check->field.typ <= bounds->field.max)
static void panel_simple_parse_panel_timing_node(struct device *dev,
struct panel_simple *panel,
const struct display_timing *ot)
{
const struct panel_desc *desc = panel->desc;
struct videomode vm;
unsigned int i;
if (WARN_ON(desc->num_modes)) {
HDF_LOGE("Reject override mode: panel has a fixed mode\n");
return;
}
if (WARN_ON(!desc->num_timings)) {
HDF_LOGE("Reject override mode: no timings specified\n");
return;
}
for (i = 0; i < panel->desc->num_timings; i++) {
const struct display_timing *dt = &panel->desc->timings[i];
if (!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hactive) ||
!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hfront_porch) ||
!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hback_porch) ||
!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hsync_len) ||
!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vactive) ||
!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vfront_porch) ||
!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vback_porch) ||
!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vsync_len))
continue;
if (ot->flags != dt->flags)
continue;
videomode_from_timing(ot, &vm);
drm_display_mode_from_videomode(&vm, &panel->override_mode);
panel->override_mode.type |= DRM_MODE_TYPE_DRIVER |
DRM_MODE_TYPE_PREFERRED;
break;
}
if (WARN_ON(!panel->override_mode.type))
HDF_LOGE("Reject override mode: No display_timing found\n");
}
static int32_t PanelOn(struct PanelData *data)
{
struct panel_simple *p = to_panel_simple_by_data(data);
if (p->enabled)
return HDF_SUCCESS;
if (p->desc->delay.enable)
OsalMSleep(p->desc->delay.enable);
p->enabled = true;
return HDF_SUCCESS;
}
static int32_t PanelOff(struct PanelData *data)
{
struct panel_simple *simplePanel = NULL;
simplePanel = to_panel_simple_by_data(data);
if (simplePanel->desc->delay.disable) {
OsalMSleep(simplePanel->desc->delay.disable);
}
return HDF_SUCCESS;
}
static int32_t PanelPrepare(struct PanelData *data)
{
struct panel_simple *p = to_panel_simple_by_data(data);
unsigned int delay;
int err;
int hpd_asserted;
if (p->prepared)
return HDF_SUCCESS;
err = panel_simple_regulator_enable(p);
if (err < 0) {
HDF_LOGE("failed to enable supply: %d\n", err);
return err;
}
gpiod_direction_output(p->enable_gpio, 1);
if (p->desc->delay.reset)
OsalMSleep(p->desc->delay.reset);
gpiod_direction_output(p->reset_gpio, 1);
if (p->desc->delay.reset)
OsalMSleep(p->desc->delay.reset);
gpiod_direction_output(p->reset_gpio, 0);
delay = p->desc->delay.prepare;
if (p->no_hpd)
delay += p->desc->delay.hpd_absent_delay;
if (delay)
OsalMSleep(delay);
// if (p->hpd_gpio) {
// if (IS_ERR(p->hpd_gpio)) {
// err = panel_simple_get_hpd_gpio(panel->dev, p, false);
// if (err)
// return err;
// }
// err = readx_poll_timeout(gpiod_get_value_cansleep, p->hpd_gpio,
// hpd_asserted, hpd_asserted,
// 1000, 2000000);
// if (hpd_asserted < 0)
// err = hpd_asserted;
// if (err) {
// HDF_LOGE("error waiting for hpd GPIO: %d\n", err);
// return err;
// }
// }
// if (p->desc->init_seq)
// if (p->dsi)
// panel_simple_xfer_dsi_cmd_seq(p, p->desc->init_seq);
if (p->desc->delay.init)
OsalMSleep(p->desc->delay.init);
p->prepared = true;
return HDF_SUCCESS;
}
static int32_t PanelUnprepare(struct PanelData *data)
{
int32_t ret;
struct panel_simple *p = NULL;
p = to_panel_simple_by_data(data);
if (!p->prepared)
return HDF_SUCCESS;
gpiod_direction_output(p->reset_gpio, 1);
gpiod_direction_output(p->enable_gpio, 0);
panel_simple_regulator_disable(p);
if (p->desc->delay.unprepare) {
OsalMSleep(p->desc->delay.unprepare);
}
return HDF_SUCCESS;
}
static int32_t PanelInit(struct PanelData *panel)
{
return 0;
}
static int32_t panel_simple_probe(struct device *dev, struct panel_simple *panel)
{
const struct panel_desc *desc = panel->desc;
struct display_timing dt;
int connector_type;
u32 bus_flags;
int32_t err;
panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd");
if (!panel->no_hpd) {
err = panel_simple_get_hpd_gpio(dev, panel, true);
if (err){
HDF_LOGE("%s Get hpd gpio fail %d", __func__, err);
goto FAIL;
}
}
panel->supply = devm_regulator_get(dev, "power");
if (IS_ERR(panel->supply)) {
HDF_LOGE("%s Get regulator fail %d", __func__, PTR_ERR(panel->supply));
goto FAIL;
}
panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS);
if (IS_ERR(panel->enable_gpio)) {
HDF_LOGE("%s get enable_gpio fail %d", __func__, PTR_ERR(panel->enable_gpio));
goto FAIL;
}
panel->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);
if (IS_ERR(panel->reset_gpio)) {
HDF_LOGE("get reset_gpio fail %d", __func__, PTR_ERR(panel->reset_gpio));
goto FAIL;
}
if (!of_get_display_timing(dev->of_node, "panel-timing", &dt))
panel_simple_parse_panel_timing_node(dev, panel, &dt);
connector_type = desc->connector_type;
/* Catch common mistakes for panels. */
switch (connector_type) {
case 0:
HDF_LOGD("Specify missing connector_type\n");
connector_type = DRM_MODE_CONNECTOR_DPI;
break;
case DRM_MODE_CONNECTOR_LVDS:
WARN_ON(desc->bus_flags &
~(DRM_BUS_FLAG_DE_LOW |
DRM_BUS_FLAG_DE_HIGH |
DRM_BUS_FLAG_DATA_MSB_TO_LSB |
DRM_BUS_FLAG_DATA_LSB_TO_MSB));
WARN_ON(desc->bus_format != MEDIA_BUS_FMT_RGB666_1X7X3_SPWG &&
desc->bus_format != MEDIA_BUS_FMT_RGB888_1X7X4_SPWG &&
desc->bus_format != MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA);
WARN_ON(desc->bus_format == MEDIA_BUS_FMT_RGB666_1X7X3_SPWG &&
desc->bpc != 6);
WARN_ON((desc->bus_format == MEDIA_BUS_FMT_RGB888_1X7X4_SPWG ||
desc->bus_format == MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA) &&
desc->bpc != 8);
break;
case DRM_MODE_CONNECTOR_eDP:
if (desc->bus_format == 0)
HDF_LOGW("Specify missing bus_format\n");
if (desc->bpc != 6 && desc->bpc != 8)
HDF_LOGW("Expected bpc in {6,8} but got: %u\n", desc->bpc);
break;
case DRM_MODE_CONNECTOR_DSI:
if (desc->bpc != 6 && desc->bpc != 8)
HDF_LOGW("Expected bpc in {6,8} but got: %u\n", desc->bpc);
break;
case DRM_MODE_CONNECTOR_DPI:
bus_flags = DRM_BUS_FLAG_DE_LOW |
DRM_BUS_FLAG_DE_HIGH |
DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
DRM_BUS_FLAG_DATA_MSB_TO_LSB |
DRM_BUS_FLAG_DATA_LSB_TO_MSB |
DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE |
DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE;
if (desc->bus_flags & ~bus_flags)
HDF_LOGW("Unexpected bus_flags(%d)\n", desc->bus_flags & ~bus_flags);
if (!(desc->bus_flags & bus_flags))
HDF_LOGW("Specify missing bus_flags\n");
if (desc->bus_format == 0)
HDF_LOGW("Specify missing bus_format\n");
if (desc->bpc != 6 && desc->bpc != 8)
HDF_LOGW("Expected bpc in {6,8} but got: %u\n", desc->bpc);
break;
default:
HDF_LOGW("Specify a valid connector_type: %d\n", desc->connector_type);
connector_type = DRM_MODE_CONNECTOR_DPI;
break;
}
HDF_LOGI("%s success", __func__);
return HDF_SUCCESS;
FAIL:
return HDF_FAILURE;
}
static bool of_child_node_is_present(const struct device_node *node,
const char *name)
{
struct device_node *child;
child = of_get_child_by_name(node, name);
of_node_put(child);
return !!child;
}
static int panel_simple_of_get_desc_data(struct device *dev,
struct panel_desc *desc)
{
struct device_node *np = dev->of_node;
u32 bus_flags;
const void *data;
int len;
int err;
if (of_child_node_is_present(np, "display-timings")) {
struct drm_display_mode *mode;
mode = (struct drm_display_mode *)OsalMemCalloc(sizeof(*mode));
if (!mode)
return -ENOMEM;
err = of_get_drm_display_mode(np, mode, &bus_flags,
OF_USE_NATIVE_MODE);
if (!err) {
desc->modes = mode;
desc->num_modes = 1;
desc->bus_flags = bus_flags;
}
} else if (of_child_node_is_present(np, "panel-timing")) {
struct display_timing *timing;
struct videomode vm;
timing = (struct display_timing *)OsalMemCalloc(sizeof(*timing));
if (!timing)
return -ENOMEM;
if (!of_get_display_timing(np, "panel-timing", timing)) {
desc->timings = timing;
desc->num_timings = 1;
bus_flags = 0;
vm.flags = timing->flags;
drm_bus_flags_from_videomode(&vm, &bus_flags);
desc->bus_flags = bus_flags;
}
}
if (desc->num_modes || desc->num_timings) {
of_property_read_u32(np, "bpc", &desc->bpc);
of_property_read_u32(np, "bus-format", &desc->bus_format);
of_property_read_u32(np, "width-mm", &desc->size.width);
of_property_read_u32(np, "height-mm", &desc->size.height);
}
of_property_read_u32(np, "prepare-delay-ms", &desc->delay.prepare);
of_property_read_u32(np, "enable-delay-ms", &desc->delay.enable);
of_property_read_u32(np, "disable-delay-ms", &desc->delay.disable);
of_property_read_u32(np, "unprepare-delay-ms", &desc->delay.unprepare);
of_property_read_u32(np, "reset-delay-ms", &desc->delay.reset);
of_property_read_u32(np, "init-delay-ms", &desc->delay.init);
data = of_get_property(np, "panel-init-sequence", &len);
if (data) {
desc->init_seq = (struct panel_cmd_seq *)OsalMemCalloc(sizeof(*desc->init_seq));
if (!desc->init_seq)
return -ENOMEM;
err = panel_simple_parse_cmd_seq(dev, data, len,
desc->init_seq);
if (err) {
HDF_LOGE("failed to parse init sequence\n");
return err;
}
}
data = of_get_property(np, "panel-exit-sequence", &len);
if (data) {
desc->exit_seq = (struct panel_cmd_seq *)OsalMemCalloc(sizeof(*desc->exit_seq));
if (!desc->exit_seq)
return -ENOMEM;
err = panel_simple_parse_cmd_seq(dev, data, len,
desc->exit_seq);
if (err) {
HDF_LOGE("failed to parse exit sequence\n");
return err;
}
}
return 0;
}
#define BLK_PWM_INDEX 2
#define PWM_MAX_PERIOD 40000
/* backlight setting */
#define MIN_LEVEL 0
#define MAX_LEVEL 255
#define DEFAULT_LEVEL 127
static struct PanelInfo g_panelInfo = {
.width = 720, /* width */
.height = 1280, /* height */
.hbp = 40, /* horizontal back porch */
.hfp = 40, /* horizontal front porch */
.hsw = 10, /* horizontal sync width */
.vbp = 15, /* vertical back porch */
.vfp = 10, /* vertical front porch */
.vsw = 36, /* vertical sync width */
.clockFreq = 75000000, /* clock */
.pWidth = 68, /* physical width */
.pHeight = 121, /* physical height */
.connectorType = DRM_MODE_CONNECTOR_DPI, /* DRM_MODE_CONNECTOR_DPI=17 */
.blk = { BLK_PWM, MIN_LEVEL, MAX_LEVEL, DEFAULT_LEVEL },
};
static void PanelDataInit(struct panel_simple *panel, struct HdfDeviceObject *object)
{
struct PanelData *panel_data = &panel->panel_data;
panel_data->object = object;
panel_data->init = PanelInit;
panel_data->on = PanelOn;
panel_data->off = PanelOff;
panel_data->prepare = PanelPrepare;
panel_data->unprepare = PanelUnprepare;
panel_data->info = &g_panelInfo;
panel_data->priv = panel;
}
static int32_t PanelEntryInit(struct HdfDeviceObject *object)
{
struct device_node *panelNode = NULL;
struct platform_device *pdev = NULL;
struct panel_simple *simplePanel = NULL;
struct panel_desc *desc = NULL;
int err;
bool hasPanel = false;
HDF_LOGI("PanelEntryInit");
while((panelNode = of_find_compatible_node(panelNode, NULL, "simple-panel")) != NULL){
hasPanel = true;
pdev = of_find_device_by_node(panelNode);
if (pdev == NULL) {
HDF_LOGE("%s of_find_device_by_node fail", __func__);
goto FAIL;
}
desc = (struct panel_desc *)OsalMemCalloc(sizeof(*desc));
if (desc == NULL) {
HDF_LOGE("%s panel_desc malloc fail", __func__);
goto FAIL;
}
err = panel_simple_of_get_desc_data(&pdev->dev, desc);
if (err) {
HDF_LOGE("%s failed to get desc data: %d\n", __func__, err);
goto FAIL;
}
simplePanel = (struct panel_simple *)OsalMemCalloc(sizeof(struct panel_simple));
if (simplePanel == NULL) {
HDF_LOGE("%s simplePanel malloc fail", __func__);
goto FAIL;
}
simplePanel->desc = desc;
simplePanel->panel.dev = &pdev->dev;
err = panel_simple_probe(&pdev->dev, simplePanel);
if (err) {
HDF_LOGE("%s failed to panel_simple_probe: %d\n", __func__, err);
goto FAIL;
}
PanelDataInit(simplePanel, object);
if (RegisterPanel(&simplePanel->panel_data) != HDF_SUCCESS) {
HDF_LOGE("RegisterPanel fail");
goto FAIL;
}
of_node_put(panelNode);
}
if (!hasPanel) {
HDF_LOGE("%s panel simple not found!!!", __func__);
goto FAIL;
}
HDF_LOGI("%s success", __func__);
return HDF_SUCCESS;
FAIL:
OsalMemFree(desc);
OsalMemFree(simplePanel);
of_node_put(panelNode);
return HDF_FAILURE;
}
struct HdfDriverEntry g_commonPanelSimpleDevEntry = {
.moduleVersion = 1,
.moduleName = "PANEL_SIMPLE_COMMON",
.Init = PanelEntryInit,
};
HDF_INIT(g_commonPanelSimpleDevEntry);
2.3.2 在DRM显示框架中注册上一步获取到的Panel对象
添加如下两个文件:
hdf_drm_panel_simple.h
/*
* Copyright (c) 2020-2021 Huawei Device Co., Ltd.
*
* HDF is dual licensed: you can use it either under the terms of
* the GPL, or the BSD license, at your option.
* See the LICENSE file in the root of this repository for complete details.
*/
#ifndef HDF_DRM_PANEL_SIMPLE_H
#define HDF_DRM_PANEL_SIMPLE_H
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <video/display_timing.h>
#include <video/mipi_display.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_panel.h>
#include <drm/drm_dsc.h>
#include "hdf_disp.h"
#include "hdf_bl.h"
struct panel_cmd_header {
u8 data_type;
u8 delay;
u8 payload_length;
} __packed;
struct panel_cmd_desc {
struct panel_cmd_header header;
u8 *payload;
};
struct panel_cmd_seq {
struct panel_cmd_desc *cmds;
unsigned int cmd_cnt;
};
/**
* @modes: Pointer to array of fixed modes appropriate for this panel. If
* only one mode then this can just be the address of this the mode.
* NOTE: cannot be used with "timings" and also if this is specified
* then you cannot override the mode in the device tree.
* @num_modes: Number of elements in modes array.
* @timings: Pointer to array of display timings. NOTE: cannot be used with
* "modes" and also these will be used to validate a device tree
* override if one is present.
* @num_timings: Number of elements in timings array.
* @bpc: Bits per color.
* @size: Structure containing the physical size of this panel.
* @delay: Structure containing various delay values for this panel.
* @bus_format: See MEDIA_BUS_FMT_... defines.
* @bus_flags: See DRM_BUS_FLAG_... defines.
*/
struct panel_desc {
const struct drm_display_mode *modes;
unsigned int num_modes;
const struct display_timing *timings;
unsigned int num_timings;
unsigned int bpc;
/**
* @width: width (in millimeters) of the panel's active display area
* @height: height (in millimeters) of the panel's active display area
*/
struct {
unsigned int width;
unsigned int height;
} size;
/**
* @prepare: the time (in milliseconds) that it takes for the panel to
* become ready and start receiving video data
* @hpd_absent_delay: Add this to the prepare delay if we know Hot
* Plug Detect isn't used.
* @enable: the time (in milliseconds) that it takes for the panel to
* display the first valid frame after starting to receive
* video data
* @disable: the time (in milliseconds) that it takes for the panel to
* turn the display off (no content is visible)
* @unprepare: the time (in milliseconds) that it takes for the panel
* to power itself down completely
* @reset: the time (in milliseconds) that it takes for the panel
* to reset itself completely
* @init: the time (in milliseconds) that it takes for the panel to
* send init command sequence after reset deassert
*/
struct {
unsigned int prepare;
unsigned int hpd_absent_delay;
unsigned int enable;
unsigned int disable;
unsigned int unprepare;
unsigned int reset;
unsigned int init;
} delay;
u32 bus_format;
u32 bus_flags;
int connector_type;
struct panel_cmd_seq *init_seq;
struct panel_cmd_seq *exit_seq;
};
struct panel_simple {
struct drm_panel panel;
struct mipi_dsi_device *dsi;
bool prepared;
bool enabled;
bool power_invert;
bool no_hpd;
const struct panel_desc *desc;
struct regulator *supply;
struct gpio_desc *enable_gpio;
struct gpio_desc *reset_gpio;
struct gpio_desc *hpd_gpio;
struct drm_display_mode override_mode;
enum drm_panel_orientation orientation;
struct PanelData panel_data;
uint32_t index;
struct DispManager *manager;
};
#endif /* HDF_DRM_PANEL_SIMPLE_H */
hdf_drm_panel_simple.c
/*
*
* HDF is dual licensed: you can use it either under the terms of
* the GPL, or the BSD license, at your option.
* See the LICENSE file in the root of this repository for complete details.
*/
#include "hdf_drm_panel_simple.h"
#include <drm/drm_device.h>
#include <drm/drm_atomic_helper.h>
#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <video/mipi_display.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>
#include <uapi/drm/drm_mode.h>
#include "osal_mem.h"
#include "osal.h"
static inline struct panel_simple *to_panel_simple(const struct drm_panel *panel)
{
return container_of(panel, struct panel_simple, panel);
}
static int HdfDrmPanelSimpleUnprepare(struct drm_panel *panel)
{
struct panel_simple *simpePanel = to_panel_simple(panel);
struct PanelData *panelData;
struct DispManager *manager = GetDispManager();
HDF_LOGD("HdfDrmPanelSimpleUnprepare");
if (simpePanel->index >= PANEL_MAX) {
HDF_LOGE("panel num out of PANEL_MAX");
return HDF_FAILURE;
}
OsalMutexLock(&manager->dispMutex);
panelData = manager->panelManager->panel[simpePanel->index];
panelData->unprepare(panelData);
OsalMutexUnlock(&manager->dispMutex);
return HDF_SUCCESS;
}
static int HdfDrmPanelSimplePrepare(struct drm_panel *panel)
{
struct panel_simple *simpePanel = to_panel_simple(panel);
struct PanelData *panelData;
struct DispManager *manager = GetDispManager();
HDF_LOGD("HdfDrmPanelSimplePrepare");
if (simpePanel->index >= PANEL_MAX) {
HDF_LOGE("panel num out of PANEL_MAX");
return HDF_FAILURE;
}
OsalMutexLock(&manager->dispMutex);
panelData = manager->panelManager->panel[simpePanel->index];
panelData->prepare(panelData);
OsalMutexUnlock(&manager->dispMutex);
return HDF_SUCCESS;
}
static int HdfDrmPanelSimpleDisable(struct drm_panel *panel)
{
struct panel_simple *simpePanel = to_panel_simple(panel);
struct PanelData *panelData;
struct DispManager *manager = GetDispManager();
HDF_LOGD("HdfDrmPanelSimpleDisable");
if (simpePanel->index >= PANEL_MAX) {
HDF_LOGE("panel num out of PANEL_MAX");
return HDF_FAILURE;
}
OsalMutexLock(&manager->dispMutex);
panelData = manager->panelManager->panel[simpePanel->index];
panelData->off(panelData);
OsalMutexUnlock(&manager->dispMutex);
return HDF_SUCCESS;
}
static int HdfDrmPanelSimpleEnable(struct drm_panel *panel)
{
struct panel_simple *simpePanel = to_panel_simple(panel);
struct PanelData *panelData;
struct DispManager *manager = GetDispManager();
HDF_LOGD("HdfDrmPanelSimpleEnable");
if (simpePanel->index >= PANEL_MAX) {
HDF_LOGE("panel num out of PANEL_MAX");
return HDF_FAILURE;
}
panelData = manager->panelManager->panel[simpePanel->index];
OsalMutexLock(&manager->dispMutex);
panelData->on(panelData);
OsalMutexUnlock(&manager->dispMutex);
return HDF_SUCCESS;
}
#define MIN_LEVEL 0
#define MAX_LEVEL 255
#define DEFAULT_LEVEL 100
static int32_t SetPanelInfo(struct panel_simple *simplePanel, struct drm_display_mode *mode)
{
HDF_LOGI("SetPanelInfo enter");
struct PanelData *panel = &simplePanel->panel_data;
panel->info->clockFreq = mode->clock * 1000;
panel->info->width = mode->hdisplay;
panel->info->height = mode->vdisplay;
panel->info->hbp = mode->htotal - mode->hsync_end;
panel->info->hfp = mode->hsync_start - mode->hdisplay;
panel->info->hsw = mode->hsync_end - mode->hsync_start;
panel->info->vbp = mode->vtotal - mode->vsync_end;
panel->info->vfp = mode->vsync_start - mode->vdisplay;
panel->info->vsw = mode->vsync_end - mode->vsync_start;
// panel->info->intfType = LCD_24BIT;
// panel->info->intfSync = 0;
// panel->info->frameRate = 0;
panel->info->blk.type = BLK_PWM;
panel->info->blk.minLevel = MIN_LEVEL;
panel->info->blk.maxLevel = MAX_LEVEL;
panel->info->blk.defLevel = DEFAULT_LEVEL;
return HDF_SUCCESS;
}
static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel,
struct drm_connector *connector)
{
struct drm_display_mode *mode;
unsigned int i, num = 0;
for (i = 0; i < panel->desc->num_timings; i++) {
const struct display_timing *dt = &panel->desc->timings[i];
struct videomode vm;
videomode_from_timing(dt, &vm);
mode = drm_mode_create(connector->dev);
if (!mode) {
HDF_LOGE("failed to add mode %ux%u\n",
dt->hactive.typ, dt->vactive.typ);
continue;
}
drm_display_mode_from_videomode(&vm, mode);
mode->type |= DRM_MODE_TYPE_DRIVER;
if (panel->desc->num_timings == 1)
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
num++;
HDF_LOGI("panel_simple_get_timings_modes SetPanelInfo:%d", i);
SetPanelInfo(panel, mode);
}
return num;
}
static unsigned int panel_simple_get_display_modes(struct panel_simple *panel,
struct drm_connector *connector)
{
struct drm_display_mode *mode;
unsigned int i, num = 0;
HDF_LOGI("panel_simple_get_display_modes enter");
for (i = 0; i < panel->desc->num_modes; i++) {
const struct drm_display_mode *m = &panel->desc->modes[i];
mode = drm_mode_duplicate(connector->dev, m);
if (!mode) {
HDF_LOGE("failed to add mode %ux%u@%u\n",
m->hdisplay, m->vdisplay,
drm_mode_vrefresh(m));
continue;
}
mode->type |= DRM_MODE_TYPE_DRIVER;
if (panel->desc->num_modes == 1)
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_set_name(mode);
drm_mode_probed_add(connector, mode);
num++;
HDF_LOGI("panel_simple_get_display_modes SetPanelInfo:%d num:%d", i, num);
SetPanelInfo(panel, mode);
}
return num;
}
static int panel_simple_get_non_edid_modes(struct panel_simple *panel,
struct drm_connector *connector)
{
struct drm_display_mode *mode;
bool has_override = panel->override_mode.type;
unsigned int num = 0;
if (!panel->desc)
return 0;
if (has_override) {
mode = drm_mode_duplicate(connector->dev,
&panel->override_mode);
if (mode) {
drm_mode_probed_add(connector, mode);
num = 1;
HDF_LOGI("panel_simple_get_non_edid_modes SetPanelInfo");
SetPanelInfo(panel, mode);
} else {
HDF_LOGE("failed to add override mode\n");
}
}
/* Only add timings if override was not there or failed to validate */
if (num == 0 && panel->desc->num_timings)
num = panel_simple_get_timings_modes(panel, connector);
/*
* Only add fixed modes if timings/override added no mode.
*
* We should only ever have either the display timings specified
* or a fixed mode. Anything else is rather bogus.
*/
WARN_ON(panel->desc->num_timings && panel->desc->num_modes);
if (num == 0)
num = panel_simple_get_display_modes(panel, connector);
if (panel->desc->bpc)
connector->display_info.bpc = panel->desc->bpc;
if (panel->desc->size.width)
connector->display_info.width_mm = panel->desc->size.width;
if (panel->desc->size.height)
connector->display_info.height_mm = panel->desc->size.height;
if (panel->desc->bus_format)
drm_display_info_set_bus_formats(&connector->display_info,
&panel->desc->bus_format, 1);
if (panel->desc->bus_flags)
connector->display_info.bus_flags = panel->desc->bus_flags;
return num;
}
static int HdfDrmPanelSimpleGetModes(struct drm_panel *panel, struct drm_connector *connector)
{
struct panel_simple *p = to_panel_simple(panel);
int num = 0;
HDF_LOGI("HdfDrmPanelSimpleGetModes");
if (panel == NULL) {
HDF_LOGE("panel is NULL");
return 0;
}
if (connector == NULL) {
HDF_LOGE("connector is NULL");
return 0;
}
// /* probe EDID if a DDC bus is available */
// if (p->ddc) {
// struct edid *edid = drm_get_edid(connector, p->ddc);
// drm_connector_update_edid_property(connector, edid);
// if (edid) {
// num += drm_add_edid_modes(connector, edid);
// kfree(edid);
// }
// }
/* add hard-coded panel modes */
num += panel_simple_get_non_edid_modes(p, connector);
// /* set up connector's "panel orientation" property */
// drm_connector_set_panel_orientation(connector, p->orientation);
return num;
}
static int HdfDrmPanelSimpleGetTimings(struct drm_panel *panel,
unsigned int num_timings,
struct display_timing *timings)
{
struct panel_simple *p = to_panel_simple(panel);
unsigned int i;
if (p->desc->num_timings < num_timings)
num_timings = p->desc->num_timings;
if (timings)
for (i = 0; i < num_timings; i++)
timings[i] = p->desc->timings[i];
return p->desc->num_timings;
}
static struct drm_panel_funcs g_hdfDrmPanelFuncs = {
.disable = HdfDrmPanelSimpleDisable,
.unprepare = HdfDrmPanelSimpleUnprepare,
.prepare = HdfDrmPanelSimplePrepare,
.enable = HdfDrmPanelSimpleEnable,
.get_modes = HdfDrmPanelSimpleGetModes,
.get_timings = HdfDrmPanelSimpleGetTimings,
};
static ssize_t SuspendStore(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int32_t ret;
struct panel_simple *simpePanel = dev_get_drvdata(dev);
ret = HdfDrmPanelSimpleDisable(&simpePanel->panel);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s HdfDrmPanelSimpleDisable fail", __func__);
return count;
}
ret = HdfDrmPanelSimpleUnprepare(&simpePanel->panel);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s HdfDrmPanelSimpleUnprepare fail", __func__);
return count;
}
return count;
}
static DEVICE_ATTR(suspend, S_IWUSR, NULL, SuspendStore);
static ssize_t ResumeStore(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int32_t ret;
struct panel_simple *simpePanel = dev_get_drvdata(dev);
ret = HdfDrmPanelSimplePrepare(&simpePanel->panel);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s HdfDrmPanelSimplePrepare fail", __func__);
return count;
}
ret = HdfDrmPanelSimpleEnable(&simpePanel->panel);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s HdfDrmPanelSimpleEnable fail", __func__);
return count;
}
return count;
}
static DEVICE_ATTR(resume, S_IWUSR, NULL, ResumeStore);
static ssize_t BacklightStore(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int32_t ret;
unsigned long level;
struct PanelData *panelData = NULL;
struct panel_simple *simpePanel = dev_get_drvdata(dev);
struct DispManager *manager = GetDispManager();
ret = kstrtoul(buf, 0, &level);
if (ret != 0) {
return ret;
}
HDF_LOGI("%s enter", __func__);
OsalMutexLock(&manager->dispMutex);
panelData = manager->panelManager->panel[simpePanel->index];
OsalMutexUnlock(&manager->dispMutex);
ret = UpdateBrightness(panelData->blDev, level);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s UpdateBrightness fail", __func__);
}
return count;
}
static DEVICE_ATTR(backlight, S_IWUSR, NULL, BacklightStore);
#define ATTR_NUM 3
static struct device_attribute *g_panelAttrs[] = {
&dev_attr_suspend,
&dev_attr_resume,
&dev_attr_backlight,
NULL,
};
static int32_t HdfDrmPanelSimpleEntryInit(struct HdfDeviceObject *object)
{
(void)object;
uint32_t ret;
uint32_t i;
uint32_t j;
uint32_t panelNum;
struct panel_simple *simplePanel = NULL;
struct DispManager *manager = NULL;
struct drm_panel *panel = NULL;
struct device *dev = NULL;
manager = GetDispManager();
if (manager == NULL) {
HDF_LOGE("%s manager is null", __func__);
return HDF_FAILURE;
}
panelNum = manager->panelManager->panelNum;
for (i = 0; i < panelNum; i++) {
simplePanel = (struct panel_simple *)manager->panelManager->panel[i]->priv;
simplePanel->index = i;
panel = &simplePanel->panel;
dev = panel->dev;
drm_panel_init(panel, dev, &g_hdfDrmPanelFuncs, simplePanel->desc->connector_type);
ret = drm_panel_of_backlight(panel);
if (ret){
HDF_LOGE("%s drm_panel_of_backlight failed %d", __func__, ret);
drm_panel_remove(panel);
return ret;
}
drm_panel_add(panel);
dev_set_drvdata(dev, simplePanel);
for (j = 0; j < ATTR_NUM; j++) {
if (device_create_file(dev, g_panelAttrs[j]) != 0) {
HDF_LOGE("%s line = %d device_create_file fail", __func__, __LINE__);
}
}
HDF_LOGI("%s panel[%d] registered success", __func__, i);
}
HDF_LOGI("%s success", __func__);
return HDF_SUCCESS;
}
struct HdfDriverEntry g_hdfDrmPanelSimpleEntry = {
.moduleVersion = 1,
.moduleName = "HDF_DRM_PANEL_SIMPLE",
.Init = HdfDrmPanelSimpleEntryInit,
};
HDF_INIT(g_hdfDrmPanelSimpleEntry);
2.3.3 修改panel驱动的编译配置文件
前两步添加的文件需要加到编译配置文件里面才可以进行编译,如下:
Makefile
2.3.4 修改device_info驱动配置文件
device_info驱动配置文件原来加载的是MIPI屏幕驱动,这里我们改成加载我们编写的LVDS屏幕驱动,如下:
device_info.hcs
3. 编译运行
3.1 编译我们修改过的源码
./build.sh --product-name ok3568 --target-cpu arm64
3.2 烧录镜像文件并运行查看效果
4. 可能遇到的问题
4.1 pwm背光调节没有反应
原因: RK3568背光功能默认使用原生pwm驱动,并且pwm路径已经写死了,如下:
解决办法: 修改设备树里面lvds屏幕所使用的 “pwm-backlight” 节点的名字为backlight,如下
或者修改drm_connector.cpp文件中的背光调节功能的文件路径。
5. 参考
标准系统方案之瑞芯微RK3568移植案例