From 1f085af24db6752b0028d1aea4a451f772922b12 Mon Sep 17 00:00:00 2001 From: Romuald JEANNE Date: Thu, 3 Nov 2022 15:27:37 +0100 Subject: [PATCH 07/22] v5.15-stm32mp-r2 DRM Signed-off-by: Romuald JEANNE --- drivers/gpu/drm/bridge/sii902x.c | 100 +- drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 5 +- drivers/gpu/drm/drm_atomic_uapi.c | 4 + drivers/gpu/drm/drm_bridge.c | 10 +- drivers/gpu/drm/drm_connector.c | 62 + drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + .../gpu/drm/panel/panel-orisetech-otm8009a.c | 101 +- drivers/gpu/drm/panel/panel-raydium-rm68200.c | 7 +- drivers/gpu/drm/panel/panel-rocktech-hx8394.c | 397 ++++++ drivers/gpu/drm/panel/panel-simple.c | 16 + drivers/gpu/drm/stm/drv.c | 6 + drivers/gpu/drm/stm/dw_mipi_dsi-stm.c | 118 +- drivers/gpu/drm/stm/ltdc.c | 1118 ++++++++++++++--- drivers/gpu/drm/stm/ltdc.h | 25 +- drivers/video/backlight/gpio_backlight.c | 7 +- drivers/video/fbdev/simplefb.c | 21 +- include/drm/bridge/dw_mipi_dsi.h | 4 +- include/drm/drm_connector.h | 14 + 19 files changed, 1763 insertions(+), 262 deletions(-) create mode 100644 drivers/gpu/drm/panel/panel-rocktech-hx8394.c diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c index 89558e581530..69208ead5384 100644 --- a/drivers/gpu/drm/bridge/sii902x.c +++ b/drivers/gpu/drm/bridge/sii902x.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -162,6 +163,11 @@ #define SII902X_AUDIO_PORT_INDEX 3 +/* CEC device */ +#define SII902X_CEC_I2C_ADDR 0x30 + +#define SII902X_CEC_SETUP 0x8e + struct sii902x { struct i2c_client *i2c; struct regmap *regmap; @@ -170,6 +176,7 @@ struct sii902x { struct gpio_desc *reset_gpio; struct i2c_mux_core *i2cmux; struct regulator_bulk_data supplies[2]; + struct edid *edid; /* * Mutex protects audio and video functions from interfering * each other, by keeping their i2c command sequences atomic. @@ -280,6 +287,8 @@ static int sii902x_get_modes(struct drm_connector *connector) mutex_lock(&sii902x->mutex); + kfree(sii902x->edid); + sii902x->edid = NULL; edid = drm_get_edid(connector, sii902x->i2cmux->adapter[0]); drm_connector_update_edid_property(connector, edid); if (edid) { @@ -287,7 +296,7 @@ static int sii902x_get_modes(struct drm_connector *connector) output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI; num = drm_add_edid_modes(connector, edid); - kfree(edid); + sii902x->edid = edid; } ret = drm_display_info_set_bus_formats(&connector->display_info, @@ -337,6 +346,7 @@ static void sii902x_bridge_disable(struct drm_bridge *bridge) static void sii902x_bridge_enable(struct drm_bridge *bridge) { struct sii902x *sii902x = bridge_to_sii902x(bridge); + u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI; mutex_lock(&sii902x->mutex); @@ -346,6 +356,14 @@ static void sii902x_bridge_enable(struct drm_bridge *bridge) regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN, 0); + if (sii902x->edid) { + if (drm_detect_hdmi_monitor(sii902x->edid)) + output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI; + } + + regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_OUTPUT_MODE, output_mode); + mutex_unlock(&sii902x->mutex); } @@ -960,6 +978,13 @@ static int sii902x_init(struct sii902x *sii902x) { struct device *dev = &sii902x->i2c->dev; unsigned int status = 0; + unsigned char data[2] = { SII902X_CEC_SETUP, 0}; + struct i2c_msg msg = { + .addr = SII902X_CEC_I2C_ADDR << 1, + .flags = 0, + .len = 2, + .buf = data, + }; u8 chipid[4]; int ret; @@ -982,13 +1007,22 @@ static int sii902x_init(struct sii902x *sii902x) return -EINVAL; } + /* + * By default, CEC must be disabled to allow other CEC devives + * to bypass the bridge. + */ + ret = i2c_transfer(sii902x->i2c->adapter, &msg, 1); + if (ret < 0) + dev_warn(&sii902x->i2c->dev, "Failed to disable CEC device!\n"); + /* Clear all pending interrupts */ regmap_read(sii902x->regmap, SII902X_INT_STATUS, &status); regmap_write(sii902x->regmap, SII902X_INT_STATUS, status); if (sii902x->i2c->irq > 0) { - regmap_write(sii902x->regmap, SII902X_INT_ENABLE, - SII902X_HOTPLUG_EVENT); + regmap_update_bits(sii902x->regmap, SII902X_INT_ENABLE, + SII902X_HOTPLUG_EVENT, + SII902X_HOTPLUG_EVENT); ret = devm_request_threaded_irq(dev, sii902x->i2c->irq, NULL, sii902x_interrupt, @@ -1087,6 +1121,65 @@ static int sii902x_remove(struct i2c_client *client) return 0; } +static int sii902x_pm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sii902x *sii902x = i2c_get_clientdata(client); + + DRM_DEBUG_DRIVER("\n"); + + if (sii902x->reset_gpio) + gpiod_set_value(sii902x->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + + return 0; +} + +static int sii902x_pm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sii902x *sii902x = i2c_get_clientdata(client); + unsigned char data[2] = { SII902X_CEC_SETUP, 0}; + struct i2c_msg msg = { + .addr = SII902X_CEC_I2C_ADDR << 1, + .flags = 0, + .len = 2, + .buf = data, + }; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + if (ret) { + DRM_ERROR("regulator_bulk_enable failed\n"); + return ret; + } + + if (sii902x->reset_gpio) + gpiod_set_value(sii902x->reset_gpio, 0); + + regmap_write(sii902x->regmap, SII902X_REG_TPI_RQB, 0x00); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + DRM_ERROR("Failed to disable CEC device!\n"); + + if (client->irq > 0) + regmap_update_bits(sii902x->regmap, SII902X_INT_ENABLE, + SII902X_HOTPLUG_EVENT, + SII902X_HOTPLUG_EVENT); + + return 0; +} + +static const struct dev_pm_ops sii902x_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sii902x_pm_suspend, sii902x_pm_resume) +}; + static const struct of_device_id sii902x_dt_ids[] = { { .compatible = "sil,sii9022", }, { } @@ -1105,6 +1198,7 @@ static struct i2c_driver sii902x_driver = { .driver = { .name = "sii902x", .of_match_table = sii902x_dt_ids, + .pm = &sii902x_pm_ops, }, .id_table = sii902x_i2c_ids, }; diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c index 56c3fd08c6a0..2a58b0b7ace5 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c @@ -998,7 +998,10 @@ dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge, enum drm_mode_status mode_status = MODE_OK; if (pdata->mode_valid) - mode_status = pdata->mode_valid(pdata->priv_data, mode); + mode_status = pdata->mode_valid(pdata->priv_data, mode, + dsi->mode_flags, + dw_mipi_dsi_get_lanes(dsi), + dsi->format); return mode_status; } diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index f195c7013137..9301aa72e6cb 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -773,6 +773,8 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector, state->content_type = val; } else if (property == connector->scaling_mode_property) { state->scaling_mode = val; + } else if (property == connector->dithering_property) { + state->dithering = val; } else if (property == config->content_protection_property) { if (val == DRM_MODE_CONTENT_PROTECTION_ENABLED) { DRM_DEBUG_KMS("only drivers can set CP Enabled\n"); @@ -862,6 +864,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector, *val = state->colorspace; } else if (property == connector->scaling_mode_property) { *val = state->scaling_mode; + } else if (property == connector->dithering_property) { + *val = state->dithering; } else if (property == config->hdr_output_metadata_property) { *val = state->hdr_output_metadata ? state->hdr_output_metadata->base.id : 0; diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 7ee29f073857..798d2cff102f 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -227,11 +227,13 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, list_del(&bridge->chain_node); #ifdef CONFIG_OF - DRM_ERROR("failed to attach bridge %pOF to encoder %s: %d\n", - bridge->of_node, encoder->name, ret); + if (ret != -EPROBE_DEFER) + DRM_ERROR("failed to attach bridge %pOF to encoder %s: %d\n", + bridge->of_node, encoder->name, ret); #else - DRM_ERROR("failed to attach bridge to encoder %s: %d\n", - encoder->name, ret); + if (ret != -EPROBE_DEFER) + DRM_ERROR("failed to attach bridge to encoder %s: %d\n", + encoder->name, ret); #endif return ret; diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index e9b7926d9b66..c035729fa995 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -824,6 +824,12 @@ static const struct drm_prop_enum_list drm_scaling_mode_enum_list[] = { { DRM_MODE_SCALE_ASPECT, "Full aspect" }, }; +static const struct drm_prop_enum_list drm_dithering_enum_list[] = { + { DRM_MODE_DITHERING_OFF, "Off" }, + { DRM_MODE_DITHERING_ON, "On" }, + { DRM_MODE_DITHERING_AUTO, "Automatic" }, +}; + static const struct drm_prop_enum_list drm_aspect_ratio_enum_list[] = { { DRM_MODE_PICTURE_ASPECT_NONE, "Automatic" }, { DRM_MODE_PICTURE_ASPECT_4_3, "4:3" }, @@ -1776,6 +1782,62 @@ int drm_connector_attach_scaling_mode_property(struct drm_connector *connector, } EXPORT_SYMBOL(drm_connector_attach_scaling_mode_property); +/** + * drm_connector_attach_dithering_property - attach atomic dithering property + * @connector: connector to attach dithering property on. + * @dithering_mask: or'ed mask of BIT(%DRM_MODE_DITHERING_\*). + * + * This is used to add support for dithering to atomic drivers. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_attach_dithering_property(struct drm_connector *connector, + u32 dithering_mask) +{ + struct drm_device *dev = connector->dev; + struct drm_property *dithering_property; + int i; + const unsigned int valid_dithering_mask = + (1U << ARRAY_SIZE(drm_dithering_enum_list)) - 1; + + if (WARN_ON(hweight32(dithering_mask) < 2 || + dithering_mask & ~valid_dithering_mask)) + return -EINVAL; + + dithering_property = + drm_property_create(dev, DRM_MODE_PROP_ENUM, "dithering", + hweight32(dithering_mask)); + + if (!dithering_property) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(drm_dithering_enum_list); i++) { + int ret; + + if (!(BIT(i) & dithering_mask)) + continue; + + ret = drm_property_add_enum(dithering_property, + drm_dithering_enum_list[i].type, + drm_dithering_enum_list[i].name); + + if (ret) { + drm_property_destroy(dev, dithering_property); + + return ret; + } + } + + drm_object_attach_property(&connector->base, + dithering_property, 0); + + connector->dithering_property = dithering_property; + + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_dithering_property); + /** * drm_mode_create_aspect_ratio_property - create aspect ratio property * @dev: DRM device diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 479ffdb64486..c49fb01b1ce5 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -359,6 +359,15 @@ config DRM_PANEL_RAYDIUM_RM68200 Say Y here if you want to enable support for Raydium RM68200 720x1280 DSI video mode panel. +config DRM_PANEL_ROCKTECH_HX8394 + tristate "Rocktech HX8394 720x1280 DSI video mode panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Rocktech HX8394 + 720x1280 DSI video mode panel. + config DRM_PANEL_RONBO_RB070D30 tristate "Ronbo Electronics RB070D30 panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index c8132050bcec..57d7948975c2 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o obj-$(CONFIG_DRM_PANEL_ELIDA_KD35T133) += panel-elida-kd35t133.o obj-$(CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02) += panel-feixin-k101-im2ba02.o obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += panel-feiyang-fy07024di26a30d.o +obj-$(CONFIG_DRM_PANEL_ROCKTECH_HX8394) += panel-rocktech-hx8394.o obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o diff --git a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c index f80b44a8a700..70b2bb72dbbc 100644 --- a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c +++ b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c @@ -60,6 +60,9 @@ #define MCS_CMD2_ENA1 0xFF00 /* Enable Access Command2 "CMD2" */ #define MCS_CMD2_ENA2 0xFF80 /* Enable Access Orise Command2 */ +#define OTM8009A_HDISPLAY 480 +#define OTM8009A_VDISPLAY 800 + struct otm8009a { struct device *dev; struct drm_panel panel; @@ -70,19 +73,35 @@ struct otm8009a { bool enabled; }; -static const struct drm_display_mode default_mode = { - .clock = 29700, - .hdisplay = 480, - .hsync_start = 480 + 98, - .hsync_end = 480 + 98 + 32, - .htotal = 480 + 98 + 32 + 98, - .vdisplay = 800, - .vsync_start = 800 + 15, - .vsync_end = 800 + 15 + 10, - .vtotal = 800 + 15 + 10 + 14, - .flags = 0, - .width_mm = 52, - .height_mm = 86, +static const struct drm_display_mode modes[] = { + { /* 50 Hz, preferred */ + .clock = 29700, + .hdisplay = 480, + .hsync_start = 480 + 98, + .hsync_end = 480 + 98 + 32, + .htotal = 480 + 98 + 32 + 98, + .vdisplay = 800, + .vsync_start = 800 + 15, + .vsync_end = 800 + 15 + 10, + .vtotal = 800 + 15 + 10 + 14, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 52, + .height_mm = 86, + }, + { /* 60 Hz */ + .clock = 33000, + .hdisplay = 480, + .hsync_start = 480 + 70, + .hsync_end = 480 + 70 + 32, + .htotal = 480 + 70 + 32 + 72, + .vdisplay = 800, + .vsync_start = 800 + 15, + .vsync_end = 800 + 15 + 10, + .vtotal = 800 + 15 + 10 + 16, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + .width_mm = 52, + .height_mm = 86, + }, }; static inline struct otm8009a *panel_to_otm8009a(struct drm_panel *panel) @@ -208,12 +227,11 @@ static int otm8009a_init_sequence(struct otm8009a *ctx) /* Default portrait 480x800 rgb24 */ dcs_write_seq(ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); - ret = mipi_dsi_dcs_set_column_address(dsi, 0, - default_mode.hdisplay - 1); + ret = mipi_dsi_dcs_set_column_address(dsi, 0, OTM8009A_HDISPLAY - 1); if (ret) return ret; - ret = mipi_dsi_dcs_set_page_address(dsi, 0, default_mode.vdisplay - 1); + ret = mipi_dsi_dcs_set_page_address(dsi, 0, OTM8009A_VDISPLAY - 1); if (ret) return ret; @@ -337,24 +355,35 @@ static int otm8009a_get_modes(struct drm_panel *panel, struct drm_connector *connector) { struct drm_display_mode *mode; - - mode = drm_mode_duplicate(connector->dev, &default_mode); - if (!mode) { - dev_err(panel->dev, "failed to add mode %ux%u@%u\n", - default_mode.hdisplay, default_mode.vdisplay, - drm_mode_vrefresh(&default_mode)); - return -ENOMEM; + unsigned int num_modes = ARRAY_SIZE(modes); + unsigned int i; + + for (i = 0; i < num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, &modes[i]); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + modes[i].hdisplay, + modes[i].vdisplay, + drm_mode_vrefresh(&modes[i])); + return -ENOMEM; + } + + mode->type = DRM_MODE_TYPE_DRIVER; + + /* Setting first mode as preferred */ + if (!i) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); } - drm_mode_set_name(mode); - - mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; - drm_mode_probed_add(connector, mode); - connector->display_info.width_mm = mode->width_mm; connector->display_info.height_mm = mode->height_mm; + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; - return 1; + return num_modes; } static const struct drm_panel_funcs otm8009a_drm_funcs = { @@ -419,8 +448,18 @@ static int otm8009a_probe(struct mipi_dsi_device *dsi) ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset_gpio)) { - dev_err(dev, "cannot get reset-gpio\n"); - return PTR_ERR(ctx->reset_gpio); + ret = PTR_ERR(ctx->reset_gpio); + if (ret != -EPROBE_DEFER) + dev_err(dev, "cannot get reset GPIO: %d\n", ret); + return ret; + } + + /* Reset the panel to avoid visual artifacts */ + if (ctx->reset_gpio) { + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(20); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); } ctx->supply = devm_regulator_get(dev, "power"); diff --git a/drivers/gpu/drm/panel/panel-raydium-rm68200.c b/drivers/gpu/drm/panel/panel-raydium-rm68200.c index 412c0dbcb2b6..d5542266e754 100644 --- a/drivers/gpu/drm/panel/panel-raydium-rm68200.c +++ b/drivers/gpu/drm/panel/panel-raydium-rm68200.c @@ -91,7 +91,7 @@ static const struct drm_display_mode default_mode = { .vsync_start = 1280 + 12, .vsync_end = 1280 + 12 + 5, .vtotal = 1280 + 12 + 5 + 12, - .flags = 0, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, .width_mm = 68, .height_mm = 122, }; @@ -347,6 +347,8 @@ static int rm68200_get_modes(struct drm_panel *panel, connector->display_info.width_mm = mode->width_mm; connector->display_info.height_mm = mode->height_mm; + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; return 1; } @@ -372,7 +374,8 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset_gpio)) { ret = PTR_ERR(ctx->reset_gpio); - dev_err(dev, "cannot get reset GPIO: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(dev, "cannot get reset GPIO: %d\n", ret); return ret; } diff --git a/drivers/gpu/drm/panel/panel-rocktech-hx8394.c b/drivers/gpu/drm/panel/panel-rocktech-hx8394.c new file mode 100644 index 000000000000..6a5926cc79ba --- /dev/null +++ b/drivers/gpu/drm/panel/panel-rocktech-hx8394.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2022 + * + * Author: Yannick Fertre + */ + +#include +#include +#include +#include +#include + +#include