OpenHarmony4.0适配LVDS屏幕驱动

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移植案例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/312028.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

yapi无法注册解决,使用yapi pro即可注册,接口文档生成,java,json

1.气屎我了&#xff0c;直接用yapi pro就可以用&#xff0c;害的我弄了半天 2.地址&#xff1a;https://yapi.pro/login 3.yapi pro比较卡顿。开启无痕模式轻松解决该问题&#xff08;手动狗头&#xff09;祝你开启新大陆 yapi pro yapi

京东年度数据报告-2023全年度笔记本十大热门品牌销量(销额)榜单

2023年度&#xff0c;在电脑办公市场整体销售下滑的环境下&#xff0c;笔记本市场的整体销售也不景气。 根据鲸参谋平台的数据显示&#xff0c;京东平台上笔记本的年度销量为650万&#xff0c;同比下滑约16%&#xff1b;销售额约为330亿&#xff0c;同比下滑约19%。同时&#…

Kotlin程序设计(三)高级用法

Kotlin程序设计高级篇 在学习了前面的内容之后&#xff0c;相信各位小伙伴应该对Kotlin这门语言有了一些全新的认识&#xff0c;我们已经了解了大部分的基本内容&#xff0c;从本章开始&#xff0c;就是对我们之前所学的基本内容的进一步提升。 泛型 在前面我们学习了最重要…

社交通证经济学:Web3时代的社交奖励系统

Web3时代的到来带来了区块链技术和去中心化的新范式&#xff0c;社交媒体也在这场变革中经历着深刻的改变。 社交通证经济学作为Web3时代社交媒体的创新实践&#xff0c;重新定义了用户在平台上的价值和奖励体系。本文将深入探讨Web3时代社交通证经济学的背景、工作原理以及对…

律师小程序,在线咨询,在线问答小程序修复头像

应用介绍 演示前端小程序&#xff1a; #小程序://问卜易学咨询/cVtT0ndctaecDKd 律师小程序是一种智能化的服务平台&#xff0c;提供了多种有益的功能。首先&#xff0c;它能够实现在线法律咨询&#xff0c;用户可以通过文字、语音或视频与律师实时沟通&#xff0c;获得专业意见…

个人事务备忘录管理微信小程序

介绍 UniApp是一款使用Vue.js开发所有前端应用的框架&#xff0c;能够同时在iOS、Android、H5、小程序等多个平台上运行&#xff1b;所以本系统可以是一个安卓app&#xff0c;也可以是微信小程序 系统包括以下功能&#xff1a; 备忘录 管理个人事务 记事本 事务分类 日记编写…

GO自研微服务框架-路由实现

路由实现 1.不用框架 不用框架的路由实现 package mainimport ("fmt""log""net/http" )func main() {http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {fmt.Fprintf(writer, "%s 欢迎来到…

白嫖啦,微软面向初学者的机器学习课程

网址&#xff1a; https://microsoft.github.io/ML-For-Beginners/#/ Microsoft 提供了一个名为 "Machine Learning for Beginners" 的课程&#xff0c;这是一个为期12周、包含26节课的课程&#xff0c;旨在帮助初学者了解机器学习的基本概念。这个课程由 Azure Clou…

全网最细RocketMQ源码二:Producer

入口 这里分析源码用的入口是&#xff1a; org.apache.rocketmq.example.quickstart package org.apache.rocketmq.example.quickstart;public class Producer {public static void main(String[] args) throws MQClientException, InterruptedException {/** Instantiate wi…

有效的回文

常用方法就是双指针。使用两个指针从字符串的两端向中间移动&#xff0c;同时比较对应位置的字符&#xff0c;直到两个指针相遇。由于题目忽略非字母和非数字的字符且忽略大小写&#xff0c;所以跳过那些字符&#xff0c;并将字母转换为小写&#xff08;或大写&#xff09;进行…

java基础day04 -- 命令行运行java文件

package com.exmaple;/*** 命令行参数*/ public class ArgsOfMain {public static void main(String[] args) {//增强for循环for(String arg : args){System.out.println(arg);}} }当我打开idea终端运行javac命令完成后&#xff08;需要配置java环境变量&#xff0c;注意idea使…

FineBI实战项目一(16):下订单总用户数分析开发

点击新建组件&#xff0c;创建下订单总用户数组件。 选择自定义图表&#xff0c;选择文本&#xff0c;拖拽要分析的字段到文本中。 修改指针值名称。 将指针值名称修改为用户数。 进入仪表板&#xff0c;拖拽刚刚的组件进入仪表板&#xff0c;然后在再编辑标题。 效果如下&…

【排序】归并排序(C语言实现)

文章目录 1. 递归版的归并排序1.1 归并排序的思想2. 递归版的归并排序的实现 2. 非递归版的归并排序 1. 递归版的归并排序 1.1 归并排序的思想 归并排序&#xff08;MERGE - SORT&#xff09;是建立在归并操作上的一种有效的排序算法, 该算法是采用分治法&#xff08;Divide a…

快速了解VR全景拍摄技术运用在旅游景区的优势

豆腐脑加了糖、烤红薯加了勺&#xff0c;就连索菲亚大教堂前都有了“人造月亮”&#xff0c;在这个冬季&#xff0c;“尔滨”把各地游客宠上了天。面对更多的游客无法实地游玩&#xff0c;哈尔滨冰雪世界再添新玩法&#xff0c;借助VR全景拍摄技术对冬季经典冰雪体验项目进行全…

vue上传文件加进度条,fake-progress一起使用

el-upload上传过程中加进度条&#xff0c;进度条el-progress配合fake-progress一起使用&#xff0c;效果如下&#xff1a; 安装 npm install fake-progress 在用到的文件里面引用 import Fakeprogress from "fake-progress"; 这个进度条主要是假的进度条&#xff…

129基于matlab的粒子群算法、遗传算法、鲸鱼算法、改进鲸鱼算法优化最小二乘支持向量机(lssvm)的gam正则化参数和sig2RBF函数的参数

基于matlab的粒子群算法、遗传算法、鲸鱼算法、改进鲸鱼算法优化最小二乘支持向量机&#xff08;lssvm&#xff09;的gam正则化参数和sig2RBF函数的参数。输出适应度曲线&#xff0c;测试机和训练集准确率。程序已调通&#xff0c;可直接运行。 129 matlabLSSVM优化算法 (xiaoh…

14.kubernetes部署Dashboard

Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中,也可以对容器应用排错,还能管理集群资源。 你可以使用 Dashboard 获取运行在集群中的应用的概览信息,也可以创建或者修改 Kubernetes 资源 (如 Deployment,Job,D…

云服务器租用价格表,阿里云腾讯云华为云2024年优惠对比

作为多年站长使市面上大多数的云厂商的云服务器都使用过&#xff0c;很多特价云服务器都是新用户专享的&#xff0c;本文有老用户特价云服务器&#xff0c;阿腾云atengyun.com有多个网站、小程序等&#xff0c;国内头部云厂商阿里云、腾讯云、华为云、UCloud、京东云都有用过&a…

基础篇_开发命令行程序(输入输出,类型、变量、运算符,条件语句,循环语句,方法,package与jar)

文章目录 一. 输入输出1. System.out2. System.in3. Scanner4. 变量名5. 关键字 二. 类型、变量、运算符1. 字符与字符串字符值与字符串值转义字符文本块 2. 类型何为类型数字类型字符类型 3. 变量与运算符变量运算符 4. 练习 - 房贷计算器Math.pow()数字格式化查阅 Javadoc 三…

虽然是个去年的旧新闻,但这透露了IBM的新去向

引言&#xff1a;老树盘根发新芽&#xff0c;只为云数添新彩。 【科技明说 &#xff5c; 科技热点关注】 就在2023年12月25日左右&#xff0c;外媒有消息被传入国内&#xff0c;IBM正在斥资21.3亿欧元收购德国企业软件公司Software AG旗下的两个iPaaS企业技术平台。 具体包括&…