Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Jayant Shekhar" <jayant.shekhar@oss.qualcomm.com>
Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH 2/2] drm/panel: Add driver for Novatek NT37802
Date: Wed, 04 Nov 2026 09:35:11 +0000	[thread overview]
Message-ID: <sashiko-outbox-196968@kernel.org> (raw)
In-Reply-To: <20261104-jshekhar-nt37802-v1-2-58e5e7fb2556@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] drm/panel: nt37802: state desync on DSI errors in disable and unprepare
- [Medium] drm/panel: nt37802: asymmetric display-on/off across prepare and disable
- [Medium] drm/panel: bogus voltage ranges in novatek_nt37802_set_supply_voltages()
- [Low] drm/panel: nt37802: use mipi_dsi_dcs_write_seq_multi() for init commands
--

commit a4454941919dc768e5c78cce0316ea1f0954d638
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>

drm/panel: Add driver for Novatek NT37802

This commit adds a DRM panel driver for the Novatek NT37802 AMOLED DSI
command-mode panel running at 1440x3200@120Hz with DSC 1.2 compression
and enables it as a module in the arm64 defconfig.

> diff --git a/drivers/gpu/drm/panel/panel-novatek-nt37802.c b/drivers/gpu/drm/panel/panel-novatek-nt37802.c
> new file mode 100644
> index 0000000000000..632520f04ffad
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-novatek-nt37802.c
> @@ -0,0 +1,544 @@

[ ... ]

> +struct nt37802_cmd {
> +	u8 len;
> +	u8 data[32];
> +};
> +
> +#define NT37802_CMD(...) \
> +	{ .len = sizeof((u8[]){ __VA_ARGS__ }), .data = { __VA_ARGS__ } }

[ ... ]

> +static int novatek_nt37802_set_supply_voltages(struct novatek_nt37802 *ctx)
> +{
> +	int ret;
> +
> +	ret = regulator_set_voltage(ctx->supplies[NT37802_SUPPLY_VDDIO].consumer,
> +				    1800000, 2000000);
> +	if (ret)
> +		return ret;
> +
> +	ret = regulator_set_voltage(ctx->supplies[NT37802_SUPPLY_VDD].consumer,
> +				    1100000, 1950000);
> +	if (ret)
> +		return ret;
> +
> +	return regulator_set_voltage(ctx->supplies[NT37802_SUPPLY_VCI].consumer,
> +				     3000000, 3544000);
> +}

[Severity: Medium]
Should the voltage constraints be defined in the board Device Tree via
regulator-min-microvolt and regulator-max-microvolt rather than calling
regulator_set_voltage() with wide ranges in
novatek_nt37802_set_supply_voltages()?

The upper bounds (2000000 uV, 1950000 uV, and 3544000 uV) appear to be
Qualcomm PMIC5 LDO hardware maximums from downstream
qcom,panel-supply-entries rather than exact panel requirements.

In drivers/regulator/core.c, regulator_set_voltage() selects the lowest
voltage permitted by the intersection of the consumer request and the
regulator's constraints:

regulator_set_voltage_unlocked()
  regulator_balance_voltage()
    regulator_get_optimal_voltage()
    _regulator_do_set_voltage()

If a board's PMIC LDO constraints allow a voltage down to 1.1V while the
panel expects 1.2V on vdd, requesting (1100000, 1950000) will lower the
rail to 1.1V, and novatek_nt37802_probe() will also fail with -EINVAL on
boards using fixed regulators without voltage adjustment constraints.

[ ... ]

> +static int novatek_nt37802_write_init(struct novatek_nt37802 *ctx)
> +{
> +	struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi };
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(nt37802_init_cmds); i++)
> +		mipi_dsi_dcs_write_buffer_multi(&dsi_ctx,
> +						nt37802_init_cmds[i].data,
> +						nt37802_init_cmds[i].len);
> +
> +	return dsi_ctx.accum_err;
> +}

[Severity: Low]
This isn't a bug, but could novatek_nt37802_write_init() use
mipi_dsi_dcs_write_seq_multi() and standard DCS helpers (such as
mipi_dsi_dcs_set_column_address_multi(),
mipi_dsi_dcs_set_page_address_multi(), and mipi_dsi_dcs_set_tear_on_multi())
with the caller's struct mipi_dsi_multi_context instead of defining the
custom struct nt37802_cmd with a fixed 32-byte array for every entry in
nt37802_init_cmds?

> +
> +static int novatek_nt37802_prepare(struct drm_panel *panel)
> +{
> +	struct novatek_nt37802 *ctx = to_novatek_nt37802(panel);
> +	struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi };
> +	struct drm_dsc_picture_parameter_set pps;
> +	int ret;
> +
> +	if (ctx->prepared)
> +		return 0;
> +
> +	ret = novatek_nt37802_enable_supplies(ctx);
> +	if (ret)
> +		return ret;
> +
> +	novatek_nt37802_reset(ctx);
> +	ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM;
> +
> +	ret = novatek_nt37802_write_init(ctx);
> +	if (ret) {
> +		novatek_nt37802_set_reset(ctx, 1);
> +		novatek_nt37802_disable_supplies(ctx);
> +		return ret;
> +	}
> +
> +	mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
> +	mipi_dsi_msleep(&dsi_ctx, 120);
> +	mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
> +	mipi_dsi_msleep(&dsi_ctx, 120);
> +
> +	drm_dsc_pps_payload_pack(&pps, &ctx->dsc);
> +	mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps);
> +	mipi_dsi_compression_mode_multi(&dsi_ctx, true);
> +	if (dsi_ctx.accum_err) {
> +		novatek_nt37802_set_reset(ctx, 1);
> +		novatek_nt37802_disable_supplies(ctx);
> +		return dsi_ctx.accum_err;
> +	}
> +
> +	ctx->prepared = true;
> +	return 0;
> +}
> +
> +static int novatek_nt37802_enable(struct drm_panel *panel)
> +{
> +	struct novatek_nt37802 *ctx = to_novatek_nt37802(panel);
> +
> +	if (!ctx->prepared)
> +		return -EPERM;
> +
> +	if (ctx->enabled)
> +		return 0;
> +
> +	ctx->enabled = true;
> +	return 0;
> +}
> +
> +static int novatek_nt37802_disable(struct drm_panel *panel)
> +{
> +	struct novatek_nt37802 *ctx = to_novatek_nt37802(panel);
> +	struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi };
> +
> +	if (!ctx->enabled)
> +		return 0;
> +
> +	/* The off sequence sends display-off in HS mode, then waits 10 ms. */
> +	ctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
> +	mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
> +	mipi_dsi_msleep(&dsi_ctx, 10);
> +
> +	ctx->enabled = false;
> +	return dsi_ctx.accum_err;
> +}

[Severity: Medium]
What happens if drm_panel_disable() is followed by drm_panel_enable()
without an intervening unprepare and prepare cycle?

novatek_nt37802_prepare() sends both mipi_dsi_dcs_exit_sleep_mode_multi()
and mipi_dsi_dcs_set_display_on_multi(), while novatek_nt37802_enable()
does not send any DSI commands. On the teardown side,
novatek_nt37802_disable() sends mipi_dsi_dcs_set_display_off_multi() and
novatek_nt37802_unprepare() only sends
mipi_dsi_dcs_enter_sleep_mode_multi().

If a disable/enable cycle occurs without unprepare/prepare, the panel will
be left with the display turned off while the backlight is enabled;
conversely, if prepare is followed by unprepare without enable/disable,
the panel enters sleep mode without sending display-off.

Should mipi_dsi_dcs_set_display_off_multi() and
mipi_dsi_dcs_enter_sleep_mode_multi() both be handled in
novatek_nt37802_unprepare() (dropping the enable and disable callbacks,
similar to panel-novatek-nt37801.c), or should display-on be paired in
novatek_nt37802_enable() with display-off in novatek_nt37802_disable()?

> +
> +static int novatek_nt37802_unprepare(struct drm_panel *panel)
> +{
> +	struct novatek_nt37802 *ctx = to_novatek_nt37802(panel);
> +	struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi };
> +
> +	if (!ctx->prepared)
> +		return 0;
> +
> +	ctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
> +	mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
> +	mipi_dsi_msleep(&dsi_ctx, 120);
> +
> +	/* Assert the active-low reset after the downstream sleep delay. */
> +	novatek_nt37802_set_reset(ctx, 1);
> +	ctx->prepared = false;
> +	novatek_nt37802_disable_supplies(ctx);
> +
> +	return dsi_ctx.accum_err;
> +}

[Severity: Medium]
Can a DSI error in novatek_nt37802_unprepare() or novatek_nt37802_disable()
permanently desynchronize ctx->prepared and ctx->enabled from the core
panel->prepared and panel->enabled state?

In drivers/gpu/drm/drm_panel.c, drm_panel_unprepare() and
drm_panel_disable() already track panel->prepared and panel->enabled, and
skip clearing them when the driver callback returns a negative error:

drivers/gpu/drm/drm_panel.c:drm_panel_unprepare() {
	...
	if (panel->funcs && panel->funcs->unprepare) {
		ret = panel->funcs->unprepare(panel);
		if (ret < 0)
			goto exit;
	}
	panel->prepared = false;
	...
}

If mipi_dsi_dcs_enter_sleep_mode_multi() fails in
novatek_nt37802_unprepare(), the driver still asserts reset, sets
ctx->prepared = false, disables all four regulators, and returns the
negative dsi_ctx.accum_err. Because drm_panel_unprepare() then leaves
panel->prepared set to true, the next drm_panel_prepare() call returns
early without calling novatek_nt37802_prepare() to re-enable the
regulators, and novatek_nt37802_enable() then fails with -EPERM because
ctx->prepared is false.

Similarly, in drm_panel_disable(), backlight_disable() runs before
panel->funcs->disable(). If mipi_dsi_dcs_set_display_off_multi() fails in
novatek_nt37802_disable(), ctx->enabled is cleared and the negative error
is returned, leaving panel->enabled set to true so subsequent
drm_panel_enable() calls return early without re-enabling the backlight.

Since struct drm_panel already maintains prepared and enabled state in the
core, can ctx->prepared and ctx->enabled be dropped, and should
novatek_nt37802_unprepare() return 0 after powering down the hardware (with
mipi_dsi_dcs_enter_sleep_mode_multi() already logging the DSI error)?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261104-jshekhar-nt37802-v1-0-58e5e7fb2556@oss.qualcomm.com?part=2

  reply	other threads:[~2026-11-04  9:35 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04  9:11 [PATCH 0/2] drm/panel: Add support for Novatek NT37802 Jayant Shekhar
2026-11-04  9:11 ` [PATCH 1/2] dt-bindings: display: panel: Add " Jayant Shekhar
2026-11-04  9:20   ` sashiko-bot
2026-11-04 15:32   ` Krzysztof Kozlowski
2026-11-04  9:11 ` [PATCH 2/2] drm/panel: Add driver for " Jayant Shekhar
2026-11-04  9:35   ` sashiko-bot [this message]
2026-11-04 15:30   ` Krzysztof Kozlowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-196968@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jayant.shekhar@oss.qualcomm.com \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox