* [PATCH 0/2] drm/panel: Add panel driver for Chipone ICNA35XX based panels
@ 2026-05-09 20:18 ` Aaron Kling via B4 Relay
0 siblings, 0 replies; 10+ messages in thread
From: Aaron Kling @ 2026-05-09 20:18 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-kernel, Aaron Kling, Teguh Sobirin
This driver is based on the one by Teguh Sobirin [0].
Due to [1], the AYN vendor description patch has been folded into the
AYN QCS8550 dt series. Which means this series depends on said series
and it must be picked up before this.
[0] https://github.com/AYNTechnologies/linux/commit/4c5e76e974db7cca853619ca138eecd8f004622f
[1] https://lore.kernel.org/linux-arm-msm/c7fb3f89-6574-4761-9ef2-2fdf6d4801b5@kernel.org
Signed-off-by: Aaron Kling <webgeek1234@gmail.com>
---
Aaron Kling (1):
dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings
Teguh Sobirin (1):
drm/panel: Add panel driver for Chipone ICNA35XX based panels
.../bindings/display/panel/chipone,icna35xx.yaml | 77 +++
drivers/gpu/drm/panel/Kconfig | 11 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-chipone-icna35xx.c | 618 +++++++++++++++++++++
4 files changed, 707 insertions(+)
---
base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
change-id: 20260220-icna35xx-ec9afa13e1aa
prerequisite-change-id: 20260217-ayn-qcs8550-16c07b63de26:v8
prerequisite-patch-id: 2b010637c46f5ea0e19a2fa87cc5ccb19bfd9204
prerequisite-patch-id: d0c633dc17f0aba726a8e8d21fee52b944bf67ff
prerequisite-patch-id: 11f4208bd788df984cec7404088c7d071ec49dfc
prerequisite-patch-id: 76b61799dba520fc7fa3ff39b044e24103337af3
prerequisite-patch-id: 3844bef2eda3cf59031b1d131eb6ba9295629bb4
prerequisite-patch-id: 3331648bc6f4ac3bb156f0525aa1ede92bfc57f1
Best regards,
--
Aaron Kling <webgeek1234@gmail.com>
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 0/2] drm/panel: Add panel driver for Chipone ICNA35XX based panels @ 2026-05-09 20:18 ` Aaron Kling via B4 Relay 0 siblings, 0 replies; 10+ messages in thread From: Aaron Kling via B4 Relay @ 2026-05-09 20:18 UTC (permalink / raw) To: Neil Armstrong, Jessica Zhang, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: dri-devel, devicetree, linux-kernel, Aaron Kling, Teguh Sobirin This driver is based on the one by Teguh Sobirin [0]. Due to [1], the AYN vendor description patch has been folded into the AYN QCS8550 dt series. Which means this series depends on said series and it must be picked up before this. [0] https://github.com/AYNTechnologies/linux/commit/4c5e76e974db7cca853619ca138eecd8f004622f [1] https://lore.kernel.org/linux-arm-msm/c7fb3f89-6574-4761-9ef2-2fdf6d4801b5@kernel.org Signed-off-by: Aaron Kling <webgeek1234@gmail.com> --- Aaron Kling (1): dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings Teguh Sobirin (1): drm/panel: Add panel driver for Chipone ICNA35XX based panels .../bindings/display/panel/chipone,icna35xx.yaml | 77 +++ drivers/gpu/drm/panel/Kconfig | 11 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-chipone-icna35xx.c | 618 +++++++++++++++++++++ 4 files changed, 707 insertions(+) --- base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83 change-id: 20260220-icna35xx-ec9afa13e1aa prerequisite-change-id: 20260217-ayn-qcs8550-16c07b63de26:v8 prerequisite-patch-id: 2b010637c46f5ea0e19a2fa87cc5ccb19bfd9204 prerequisite-patch-id: d0c633dc17f0aba726a8e8d21fee52b944bf67ff prerequisite-patch-id: 11f4208bd788df984cec7404088c7d071ec49dfc prerequisite-patch-id: 76b61799dba520fc7fa3ff39b044e24103337af3 prerequisite-patch-id: 3844bef2eda3cf59031b1d131eb6ba9295629bb4 prerequisite-patch-id: 3331648bc6f4ac3bb156f0525aa1ede92bfc57f1 Best regards, -- Aaron Kling <webgeek1234@gmail.com> ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/2] dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings 2026-05-09 20:18 ` Aaron Kling via B4 Relay @ 2026-05-09 20:18 ` Aaron Kling via B4 Relay -1 siblings, 0 replies; 10+ messages in thread From: Aaron Kling @ 2026-05-09 20:18 UTC (permalink / raw) To: Neil Armstrong, Jessica Zhang, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: dri-devel, devicetree, linux-kernel, Aaron Kling The Chipone ICNA35xx series of DDICs are high refresh, low power MIPI-DSI drivers for OLED panels. The icna3512 is used by the Ayn Odin 2 Portal and the icna3520 is used by the Ayn Thor top panel and the Ayn Odin 3. Signed-off-by: Aaron Kling <webgeek1234@gmail.com> --- .../bindings/display/panel/chipone,icna35xx.yaml | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml b/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml new file mode 100644 index 00000000000000..3c497a40101732 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/chipone,icna35xx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Chipone ICNA35xx display drivers + +maintainers: + - Neil Armstrong <neil.armstrong@linaro.org> + +description: + The Chipone ICNA35xx are a series of DDICs connected + using a MIPI-DSI video interface. + +allOf: + - $ref: panel-common.yaml# + +properties: + compatible: + oneOf: + - items: + - const: ayntec,odin2portal-panel + - const: chipone,icna3512 + + - items: + - enum: + - ayntec,odin3-panel + - ayntec,thor-panel-top + - const: chipone,icna3520 + + reg: + maxItems: 1 + description: DSI virtual channel + + vdd-supply: true + vddio-supply: true + vci-supply: true + disp-supply: true + blvdd-supply: true + + port: true + reset-gpios: true + rotation: true + +required: + - compatible + - reg + - vdd-supply + - vddio-supply + - vci-supply + - disp-supply + - blvdd-supply + - reset-gpios + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + dsi { + #address-cells = <1>; + #size-cells = <0>; + panel@0 { + compatible = "ayntec,odin2portal-panel", "chipone,icna3512"; + reg = <0>; + vdd-supply = <&vreg_l11b_1p2>; + vddio-supply = <&vreg_l12b_1p8>; + vci-supply = <&vreg_l13b_3p0>; + disp-supply = <&vdd_disp_2v8>; + blvdd-supply = <&vdd_bl_5v0>; + reset-gpios = <&tlmm 133 GPIO_ACTIVE_LOW>; + }; + }; + +... -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 1/2] dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings @ 2026-05-09 20:18 ` Aaron Kling via B4 Relay 0 siblings, 0 replies; 10+ messages in thread From: Aaron Kling via B4 Relay @ 2026-05-09 20:18 UTC (permalink / raw) To: Neil Armstrong, Jessica Zhang, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: dri-devel, devicetree, linux-kernel, Aaron Kling From: Aaron Kling <webgeek1234@gmail.com> The Chipone ICNA35xx series of DDICs are high refresh, low power MIPI-DSI drivers for OLED panels. The icna3512 is used by the Ayn Odin 2 Portal and the icna3520 is used by the Ayn Thor top panel and the Ayn Odin 3. Signed-off-by: Aaron Kling <webgeek1234@gmail.com> --- .../bindings/display/panel/chipone,icna35xx.yaml | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml b/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml new file mode 100644 index 00000000000000..3c497a40101732 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/chipone,icna35xx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Chipone ICNA35xx display drivers + +maintainers: + - Neil Armstrong <neil.armstrong@linaro.org> + +description: + The Chipone ICNA35xx are a series of DDICs connected + using a MIPI-DSI video interface. + +allOf: + - $ref: panel-common.yaml# + +properties: + compatible: + oneOf: + - items: + - const: ayntec,odin2portal-panel + - const: chipone,icna3512 + + - items: + - enum: + - ayntec,odin3-panel + - ayntec,thor-panel-top + - const: chipone,icna3520 + + reg: + maxItems: 1 + description: DSI virtual channel + + vdd-supply: true + vddio-supply: true + vci-supply: true + disp-supply: true + blvdd-supply: true + + port: true + reset-gpios: true + rotation: true + +required: + - compatible + - reg + - vdd-supply + - vddio-supply + - vci-supply + - disp-supply + - blvdd-supply + - reset-gpios + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + dsi { + #address-cells = <1>; + #size-cells = <0>; + panel@0 { + compatible = "ayntec,odin2portal-panel", "chipone,icna3512"; + reg = <0>; + vdd-supply = <&vreg_l11b_1p2>; + vddio-supply = <&vreg_l12b_1p8>; + vci-supply = <&vreg_l13b_3p0>; + disp-supply = <&vdd_disp_2v8>; + blvdd-supply = <&vdd_bl_5v0>; + reset-gpios = <&tlmm 133 GPIO_ACTIVE_LOW>; + }; + }; + +... -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings 2026-05-09 20:18 ` Aaron Kling via B4 Relay (?) @ 2026-05-09 20:46 ` sashiko-bot -1 siblings, 0 replies; 10+ messages in thread From: sashiko-bot @ 2026-05-09 20:46 UTC (permalink / raw) To: Aaron Kling via B4 Relay; +Cc: robh, conor+dt, krzk+dt, devicetree Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The `ayntec` vendor prefix used in the new compatible strings is missing from the device tree vendor prefix registry. -- commit 2a0dec37c2745490f796112b94d143b7a694cd12 Author: Aaron Kling <webgeek1234@gmail.com> dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings The Chipone ICNA35xx series of DDICs are high refresh, low power MIPI-DSI drivers for OLED panels used by Ayn Odin and Thor devices. > diff --git a/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml b/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml > new file mode 100644 > index 0000000000000..3c497a4010173 > --- /dev/null > +++ b/Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml [ ... ] > +properties: > + compatible: > + oneOf: > + - items: > + - const: ayntec,odin2portal-panel Is the ayntec vendor prefix documented in the device tree vendor prefix registry? It appears this prefix is missing from Documentation/devicetree/bindings/vendor-prefixes.yaml. Although the prefix was previously used in another binding without being documented, adding these new compatible strings introduces new dt_binding_check validation warnings for this newly added schema. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260509-icna35xx-v1-0-688d3d4e10f9@gmail.com?part=1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings 2026-05-09 20:18 ` Aaron Kling via B4 Relay (?) (?) @ 2026-05-09 21:25 ` Rob Herring (Arm) 2026-05-09 21:51 ` Aaron Kling -1 siblings, 1 reply; 10+ messages in thread From: Rob Herring (Arm) @ 2026-05-09 21:25 UTC (permalink / raw) To: Aaron Kling Cc: Conor Dooley, dri-devel, Maxime Ripard, Krzysztof Kozlowski, linux-kernel, Jessica Zhang, devicetree, Neil Armstrong, Maarten Lankhorst, Simona Vetter, David Airlie, Thomas Zimmermann On Sat, 09 May 2026 15:18:57 -0500, Aaron Kling wrote: > The Chipone ICNA35xx series of DDICs are high refresh, low power > MIPI-DSI drivers for OLED panels. The icna3512 is used by the Ayn Odin 2 > Portal and the icna3520 is used by the Ayn Thor top panel and the Ayn > Odin 3. > > Signed-off-by: Aaron Kling <webgeek1234@gmail.com> > --- > .../bindings/display/panel/chipone,icna35xx.yaml | 77 ++++++++++++++++++++++ > 1 file changed, 77 insertions(+) > My bot found errors running 'make dt_binding_check' on your patch: yamllint warnings/errors: ./Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml:28:13: [warning] wrong indentation: expected 14 but found 12 (indentation) dtschema/dtc warnings/errors: doc reference errors (make refcheckdocs): See https://patchwork.kernel.org/project/devicetree/patch/20260509-icna35xx-v1-1-688d3d4e10f9@gmail.com The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch. If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date: pip3 install dtschema --upgrade Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings 2026-05-09 21:25 ` Rob Herring (Arm) @ 2026-05-09 21:51 ` Aaron Kling 0 siblings, 0 replies; 10+ messages in thread From: Aaron Kling @ 2026-05-09 21:51 UTC (permalink / raw) To: Rob Herring (Arm) Cc: Conor Dooley, dri-devel, Maxime Ripard, Krzysztof Kozlowski, linux-kernel, Jessica Zhang, devicetree, Neil Armstrong, Maarten Lankhorst, Simona Vetter, David Airlie, Thomas Zimmermann On Sat, May 9, 2026 at 4:25 PM Rob Herring (Arm) <robh@kernel.org> wrote: > > > On Sat, 09 May 2026 15:18:57 -0500, Aaron Kling wrote: > > The Chipone ICNA35xx series of DDICs are high refresh, low power > > MIPI-DSI drivers for OLED panels. The icna3512 is used by the Ayn Odin 2 > > Portal and the icna3520 is used by the Ayn Thor top panel and the Ayn > > Odin 3. > > > > Signed-off-by: Aaron Kling <webgeek1234@gmail.com> > > --- > > .../bindings/display/panel/chipone,icna35xx.yaml | 77 ++++++++++++++++++++++ > > 1 file changed, 77 insertions(+) > > > > My bot found errors running 'make dt_binding_check' on your patch: > > yamllint warnings/errors: > ./Documentation/devicetree/bindings/display/panel/chipone,icna35xx.yaml:28:13: [warning] wrong indentation: expected 14 but found 12 (indentation) > > dtschema/dtc warnings/errors: > > doc reference errors (make refcheckdocs): > > See https://patchwork.kernel.org/project/devicetree/patch/20260509-icna35xx-v1-1-688d3d4e10f9@gmail.com > > The base for the series is generally the latest rc1. A different dependency > should be noted in *this* patch. > > If you already ran 'make dt_binding_check' and didn't see the above > error(s), then make sure 'yamllint' is installed and dt-schema is up to > date: > > pip3 install dtschema --upgrade > > Please check and re-submit after running the above command yourself. Note > that DT_SCHEMA_FILES can be set to your schema file to speed up checking > your schema. However, it must be unset to test all examples with your schema. > Ack. Will wait a couple days before sending a fixed revision to see if more review comments get made on the series. Aaron ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 2/2] drm/panel: Add panel driver for Chipone ICNA35XX based panels 2026-05-09 20:18 ` Aaron Kling via B4 Relay @ 2026-05-09 20:18 ` Aaron Kling via B4 Relay -1 siblings, 0 replies; 10+ messages in thread From: Aaron Kling @ 2026-05-09 20:18 UTC (permalink / raw) To: Neil Armstrong, Jessica Zhang, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: dri-devel, devicetree, linux-kernel, Aaron Kling, Teguh Sobirin From: Teguh Sobirin <teguh@sobir.in> This adds support for the ICNA3512 and ICNA3520 DDICs used in the AYN Odin 2 Portal and for both the AYN Odin 3 and the AYN Thor top panel respectively. Signed-off-by: Teguh Sobirin <teguh@sobir.in> Co-developed-by: Aaron Kling <webgeek1234@gmail.com> Signed-off-by: Aaron Kling <webgeek1234@gmail.com> --- drivers/gpu/drm/panel/Kconfig | 11 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-chipone-icna35xx.c | 618 +++++++++++++++++++++++++ 3 files changed, 630 insertions(+) diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 7450b27622a233..1368b5a0b6c912 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -105,6 +105,17 @@ config DRM_PANEL_BOE_TV101WUM_LL2 Say Y here if you want to support for BOE TV101WUM-LL2 WUXGA PANEL DSI Video Mode panel +config DRM_PANEL_CHIPONE_ICNA35XX + tristate "Chipone ICNA35XX panel driver" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_HELPER + help + Say Y here if you want to enable support for the panels built + around the Chipone ICNA3512 and ICNA3520 display controllers, + such as some Tianma panels used in AYN Odin2 Portal and Thor. + config DRM_PANEL_CHIPWEALTH_CH13726A tristate "CHIPWEALTH CH13726A-based DSI panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index c2c5cf81711633..d39a8f82fa8c06 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_PANEL_BOE_TD4320) += panel-boe-td4320.o obj-$(CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A) += panel-boe-th101mb31ig002-28a.o obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_LL2) += panel-boe-tv101wum-ll2.o obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o +obj-$(CONFIG_DRM_PANEL_CHIPONE_ICNA35XX) += panel-chipone-icna35xx.o obj-$(CONFIG_DRM_PANEL_CHIPWEALTH_CH13726A) += panel-chipwealth-ch13726a.o obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o diff --git a/drivers/gpu/drm/panel/panel-chipone-icna35xx.c b/drivers/gpu/drm/panel/panel-chipone-icna35xx.c new file mode 100644 index 00000000000000..1be903270db8e2 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-chipone-icna35xx.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Chipone ICNA35XX Driver IC panels driver + * + * Copyright (c) 2025 Teguh Sobirin <teguh@sobir.in> + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct panel_info { + struct drm_panel panel; + struct drm_connector *connector; + struct mipi_dsi_device *dsi; + struct panel_desc *desc; + enum drm_panel_orientation orientation; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +struct panel_desc { + unsigned int width_mm; + unsigned int height_mm; + + unsigned int bpc; + unsigned int lanes; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + + const struct drm_display_mode *modes; + unsigned int num_modes; + int (*init_sequence)(struct panel_info *pinfo); + + struct drm_dsc_config dsc; +}; + +static const struct regulator_bulk_data panel_supplies[] = { + { .supply = "vdd" }, + { .supply = "vddio" }, + { .supply = "vci" }, + { .supply = "disp" }, + { .supply = "blvdd" }, +}; + +static inline struct panel_info *to_panel_info(struct drm_panel *panel) +{ + return container_of(panel, struct panel_info, panel); +} + +static int icna35xx_get_current_mode(struct panel_info *pinfo) +{ + struct drm_connector *connector = pinfo->connector; + struct drm_crtc_state *crtc_state; + int i; + + /* Return the default (first) mode if no info available yet */ + if (!connector->state || !connector->state->crtc) + return 0; + + crtc_state = connector->state->crtc->state; + + for (i = 0; i < pinfo->desc->num_modes; i++) { + if (drm_mode_match(&crtc_state->mode, + &pinfo->desc->modes[i], + DRM_MODE_MATCH_TIMINGS | DRM_MODE_MATCH_CLOCK)) + return i; + } + + return 0; +} + +static int icna3512_init_sequence(struct panel_info *pinfo) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; + struct drm_dsc_picture_parameter_set pps; + + int cur_mode = icna35xx_get_current_mode(pinfo); + int cur_vrefresh = drm_mode_vrefresh(&pinfo->desc->modes[cur_mode]); + + pinfo->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9C, 0xA5, 0xA5); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xFD, 0x5A, 0x5A); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0xE0); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x0F); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCE, 0x22); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x01); + if (cur_vrefresh == 165) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x20); + } else if (cur_vrefresh == 144) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x00, 0x02, 0x83, + 0x00, 0x10, 0x14, 0x00, 0x00, 0xC3, 0x00, 0x10, + 0x14, 0x00, 0x00, 0xE0, 0x00, 0x10, 0x14, 0x00, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x22, 0x18, 0x18, + 0x18, 0x18, 0x18); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x07); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB5, + 0x04, 0x0A, 0x08, 0x0A, 0x04, 0x00, 0xC4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xD9, + 0x66, 0xE4, 0xE4, 0x66, 0xE4, 0xE4, 0x00, 0xC4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCE, + 0x01, 0x01, 0x01, 0x01, 0x04, 0x07, 0xA4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x30); + } else if (cur_vrefresh == 120) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x00, 0x02, 0x83, + 0x00, 0x10, 0x14, 0x00, 0x00, 0xC3, 0x00, 0x10, + 0x14, 0x00, 0x00, 0xE0, 0x10, 0x10, 0x9C, 0x00, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x22, 0x18, 0x18, + 0x18, 0x18, 0x18); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x07); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB5, + 0x04, 0x0C, 0x08, 0x0C, 0x04, 0x00, 0xC4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xD9, + 0x88, 0x40, 0x40, 0x88, 0x40, 0x40, 0x00, 0xEB, + 0x11, 0xFF); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCE, + 0x01, 0x01, 0x01, 0x01, 0x04, 0x09, 0x2C); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x30); + } else if (cur_vrefresh == 90) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xE0, 0x40, 0x10, 0xA8, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x07); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, + 0x04, 0x10, 0x08, 0x0C, 0x04, 0x00, 0xC4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xD3, + 0x55, 0x80, 0x80, 0x55, 0x80, 0xB0, 0x00, 0x9C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCB, + 0x01, 0x01, 0x01, 0x01, 0x04, 0x06, 0x1C); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x00); + } else { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x07); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, + 0x04, 0x18, 0x08, 0x0C, 0x02, 0x00, 0xC4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xD3, + 0x88, 0x4A, 0x4A, 0x88, 0x4A, 0x4A, 0x00, 0xEB, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCB, + 0x01, 0x01, 0x01, 0x01, 0x04, 0x2C); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x00); + } + + drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc); + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int icna3520_init_sequence(struct panel_info *pinfo) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; + struct drm_dsc_picture_parameter_set pps; + + int cur_mode = icna35xx_get_current_mode(pinfo); + int cur_vrefresh = drm_mode_vrefresh(&pinfo->desc->modes[cur_mode]); + + pinfo->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9C, 0xA5, 0xA5); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xFD, 0x5A, 0x5A); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0xE0); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + if (cur_vrefresh == 120) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xD8, 0x00, 0x1C, 0x00, 0x4C); + } else { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x10); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xDB, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x00, + 0xDB, 0x00, 0x1C, 0x07, 0xD6, 0x00); + } + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x00); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x0D); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x27); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB6, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xBB, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x24); + + drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc); + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static const struct drm_display_mode odin2portal_modes[] = { + { + /* 165Hz */ + .clock = (1080 + 98 + 1 + 23) * (1920 + 20 + 1 + 15) * 165 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 98, + .hsync_end = 1080 + 98 + 1, + .htotal = 1080 + 98 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 20, + .vsync_end = 1920 + 20 + 1, + .vtotal = 1920 + 20 + 1 + 15, + }, + { + /* 144Hz */ + .clock = (1080 + 156 + 1 + 23) * (1920 + 20 + 1 + 15) * 144 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 156, + .hsync_end = 1080 + 156 + 1, + .htotal = 1080 + 156 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 20, + .vsync_end = 1920 + 20 + 1, + .vtotal = 1920 + 20 + 1 + 15, + }, + { + /* 120Hz */ + .clock = (1080 + 156 + 1 + 23) * (1920 + 412 + 1 + 15) * 120 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 156, + .hsync_end = 1080 + 156 + 1, + .htotal = 1080 + 156 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 412, + .vsync_end = 1920 + 412 + 1, + .vtotal = 1920 + 412 + 1 + 15, + }, + { + /* 90Hz */ + .clock = (1080 + 156 + 1 + 23) * (1920 + 1192 + 1 + 15) * 90 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 156, + .hsync_end = 1080 + 156 + 1, + .htotal = 1080 + 156 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 1192, + .vsync_end = 1920 + 1192 + 1, + .vtotal = 1920 + 1192 + 1 + 15, + }, + { + /* 60Hz */ + .clock = (1080 + 156 + 1 + 23) * (1920 + 2760 + 1 + 15) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 156, + .hsync_end = 1080 + 156 + 1, + .htotal = 1080 + 156 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 2760, + .vsync_end = 1920 + 2760 + 1, + .vtotal = 1920 + 2760 + 1 + 15, + } +}; + +static const struct drm_display_mode thor_top_modes[] = { + { + /* 120Hz */ + .clock = (1080 + 24 + 1 + 24) * (1920 + 28 + 1 + 28) * 120 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 24, + .hsync_end = 1080 + 24 + 1, + .htotal = 1080 + 24 + 1 + 24, + .vdisplay = 1920, + .vsync_start = 1920 + 28, + .vsync_end = 1920 + 28 + 1, + .vtotal = 1920 + 28 + 1 + 28, + }, + { + /* 60Hz */ + .clock = (1080 + 24 + 1 + 24) * (1920 + 2006 + 1 + 28) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 24, + .hsync_end = 1080 + 24 + 1, + .htotal = 1080 + 24 + 1 + 24, + .vdisplay = 1920, + .vsync_start = 1920 + 2006, + .vsync_end = 1920 + 2006 + 1, + .vtotal = 1920 + 2006 + 1 + 28, + } +}; + +static struct panel_desc odin2portal_desc = { + .modes = odin2portal_modes, + .num_modes = ARRAY_SIZE(odin2portal_modes), + .width_mm = 160, + .height_mm = 89, + .bpc = 8, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_LPM, + .init_sequence = icna3512_init_sequence, + .dsc = { + .dsc_version_major = 0x1, + .dsc_version_minor = 0x1, + .slice_height = 20, + .slice_width = 540, + .slice_count = 2, + .bits_per_component = 8, + .bits_per_pixel = 8 << 4, + .block_pred_enable = true, + }, +}; + +static struct panel_desc thor_top_desc = { + .modes = thor_top_modes, + .num_modes = ARRAY_SIZE(thor_top_modes), + .width_mm = 136, + .height_mm = 68, + .bpc = 8, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_LPM, + .init_sequence = icna3520_init_sequence, + .dsc = { + .dsc_version_major = 0x1, + .dsc_version_minor = 0x1, + .slice_height = 12, + .slice_width = 540, + .slice_count = 2, + .bits_per_component = 8, + .bits_per_pixel = 8 << 4, + .block_pred_enable = true, + }, +}; + +static void icna35xx_reset(struct panel_info *pinfo) +{ + gpiod_set_value_cansleep(pinfo->reset_gpio, 0); + usleep_range(20000, 21000); + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); + usleep_range(20000, 21000); + gpiod_set_value_cansleep(pinfo->reset_gpio, 0); + usleep_range(20000, 21000); +} + +static int icna35xx_prepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(panel_supplies), pinfo->supplies); + if (ret < 0) { + dev_err(panel->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + icna35xx_reset(pinfo); + + ret = pinfo->desc->init_sequence(pinfo); + if (ret < 0) { + regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies); + dev_err(panel->dev, "failed to initialize panel: %d\n", ret); + return ret; + } + + return 0; +} + +static int icna35xx_disable(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; + + pinfo->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int icna35xx_unprepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies); + + return 0; +} + +static void icna35xx_remove(struct mipi_dsi_device *dsi) +{ + struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(pinfo->dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&pinfo->panel); +} + +static int icna35xx_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_info *pinfo = to_panel_info(panel); + int i; + + for (i = 0; i < pinfo->desc->num_modes; i++) { + const struct drm_display_mode *m = &pinfo->desc->modes[i]; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); + return -ENOMEM; + } + + mode->type = DRM_MODE_TYPE_DRIVER; + if (i == 0) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + connector->display_info.width_mm = pinfo->desc->width_mm; + connector->display_info.height_mm = pinfo->desc->height_mm; + connector->display_info.bpc = pinfo->desc->bpc; + pinfo->connector = connector; + + return pinfo->desc->num_modes; +} + +static enum drm_panel_orientation icna35xx_get_orientation(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + + return pinfo->orientation; +} + +static const struct drm_panel_funcs icna35xx_panel_funcs = { + .disable = icna35xx_disable, + .prepare = icna35xx_prepare, + .unprepare = icna35xx_unprepare, + .get_modes = icna35xx_get_modes, + .get_orientation = icna35xx_get_orientation, +}; + +static int icna35xx_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static int icna35xx_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness; +} + +static const struct backlight_ops icna35xx_bl_ops = { + .update_status = icna35xx_bl_update_status, + .get_brightness = icna35xx_bl_get_brightness, +}; + +static struct backlight_device *icna35xx_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 4096, + .max_brightness = 4096, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &icna35xx_bl_ops, &props); +} + +static int icna35xx_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct panel_info *pinfo; + int ret; + + pinfo = devm_drm_panel_alloc(dev, __typeof(*pinfo), panel, + &icna35xx_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(pinfo)) + return PTR_ERR(pinfo); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(panel_supplies), + panel_supplies, &pinfo->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(pinfo->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio), "failed to get reset gpio\n"); + + pinfo->desc = (struct panel_desc *)of_device_get_match_data(dev); + if (!pinfo->desc) + return -ENODEV; + + pinfo->dsi = dsi; + mipi_dsi_set_drvdata(dsi, pinfo); + + ret = of_drm_get_panel_orientation(dev->of_node, &pinfo->orientation); + if (ret < 0) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret); + return ret; + } + + pinfo->panel.prepare_prev_first = true; + + pinfo->panel.backlight = icna35xx_create_backlight(dsi); + if (IS_ERR(pinfo->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(pinfo->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&pinfo->panel); + + pinfo->dsi->lanes = pinfo->desc->lanes; + pinfo->dsi->format = pinfo->desc->format; + pinfo->dsi->mode_flags = pinfo->desc->mode_flags; + pinfo->dsi->dsc = &pinfo->desc->dsc; + + ret = mipi_dsi_attach(pinfo->dsi); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + drm_panel_remove(&pinfo->panel); + return ret; + } + + return 0; +} + +static const struct of_device_id icna35xx_of_match[] = { + { .compatible = "ayntec,odin2portal-panel", .data = &odin2portal_desc }, + { .compatible = "ayntec,odin3-panel", .data = &thor_top_desc }, + { .compatible = "ayntec,thor-panel-top", .data = &thor_top_desc }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, icna35xx_of_match); + +static struct mipi_dsi_driver icna35xx_driver = { + .probe = icna35xx_probe, + .remove = icna35xx_remove, + .driver = { + .name = "panel-chipone-icna35xx", + .of_match_table = icna35xx_of_match, + }, +}; +module_mipi_dsi_driver(icna35xx_driver); + +MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in>"); +MODULE_DESCRIPTION("DRM driver for Chipone ICNA35XX based MIPI DSI panels"); +MODULE_LICENSE("GPL"); -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/2] drm/panel: Add panel driver for Chipone ICNA35XX based panels @ 2026-05-09 20:18 ` Aaron Kling via B4 Relay 0 siblings, 0 replies; 10+ messages in thread From: Aaron Kling via B4 Relay @ 2026-05-09 20:18 UTC (permalink / raw) To: Neil Armstrong, Jessica Zhang, David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: dri-devel, devicetree, linux-kernel, Aaron Kling, Teguh Sobirin From: Teguh Sobirin <teguh@sobir.in> This adds support for the ICNA3512 and ICNA3520 DDICs used in the AYN Odin 2 Portal and for both the AYN Odin 3 and the AYN Thor top panel respectively. Signed-off-by: Teguh Sobirin <teguh@sobir.in> Co-developed-by: Aaron Kling <webgeek1234@gmail.com> Signed-off-by: Aaron Kling <webgeek1234@gmail.com> --- drivers/gpu/drm/panel/Kconfig | 11 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-chipone-icna35xx.c | 618 +++++++++++++++++++++++++ 3 files changed, 630 insertions(+) diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 7450b27622a233..1368b5a0b6c912 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -105,6 +105,17 @@ config DRM_PANEL_BOE_TV101WUM_LL2 Say Y here if you want to support for BOE TV101WUM-LL2 WUXGA PANEL DSI Video Mode panel +config DRM_PANEL_CHIPONE_ICNA35XX + tristate "Chipone ICNA35XX panel driver" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_HELPER + help + Say Y here if you want to enable support for the panels built + around the Chipone ICNA3512 and ICNA3520 display controllers, + such as some Tianma panels used in AYN Odin2 Portal and Thor. + config DRM_PANEL_CHIPWEALTH_CH13726A tristate "CHIPWEALTH CH13726A-based DSI panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index c2c5cf81711633..d39a8f82fa8c06 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_PANEL_BOE_TD4320) += panel-boe-td4320.o obj-$(CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A) += panel-boe-th101mb31ig002-28a.o obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_LL2) += panel-boe-tv101wum-ll2.o obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o +obj-$(CONFIG_DRM_PANEL_CHIPONE_ICNA35XX) += panel-chipone-icna35xx.o obj-$(CONFIG_DRM_PANEL_CHIPWEALTH_CH13726A) += panel-chipwealth-ch13726a.o obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o diff --git a/drivers/gpu/drm/panel/panel-chipone-icna35xx.c b/drivers/gpu/drm/panel/panel-chipone-icna35xx.c new file mode 100644 index 00000000000000..1be903270db8e2 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-chipone-icna35xx.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Chipone ICNA35XX Driver IC panels driver + * + * Copyright (c) 2025 Teguh Sobirin <teguh@sobir.in> + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/display/drm_dsc.h> +#include <drm/display/drm_dsc_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct panel_info { + struct drm_panel panel; + struct drm_connector *connector; + struct mipi_dsi_device *dsi; + struct panel_desc *desc; + enum drm_panel_orientation orientation; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +}; + +struct panel_desc { + unsigned int width_mm; + unsigned int height_mm; + + unsigned int bpc; + unsigned int lanes; + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + + const struct drm_display_mode *modes; + unsigned int num_modes; + int (*init_sequence)(struct panel_info *pinfo); + + struct drm_dsc_config dsc; +}; + +static const struct regulator_bulk_data panel_supplies[] = { + { .supply = "vdd" }, + { .supply = "vddio" }, + { .supply = "vci" }, + { .supply = "disp" }, + { .supply = "blvdd" }, +}; + +static inline struct panel_info *to_panel_info(struct drm_panel *panel) +{ + return container_of(panel, struct panel_info, panel); +} + +static int icna35xx_get_current_mode(struct panel_info *pinfo) +{ + struct drm_connector *connector = pinfo->connector; + struct drm_crtc_state *crtc_state; + int i; + + /* Return the default (first) mode if no info available yet */ + if (!connector->state || !connector->state->crtc) + return 0; + + crtc_state = connector->state->crtc->state; + + for (i = 0; i < pinfo->desc->num_modes; i++) { + if (drm_mode_match(&crtc_state->mode, + &pinfo->desc->modes[i], + DRM_MODE_MATCH_TIMINGS | DRM_MODE_MATCH_CLOCK)) + return i; + } + + return 0; +} + +static int icna3512_init_sequence(struct panel_info *pinfo) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; + struct drm_dsc_picture_parameter_set pps; + + int cur_mode = icna35xx_get_current_mode(pinfo); + int cur_vrefresh = drm_mode_vrefresh(&pinfo->desc->modes[cur_mode]); + + pinfo->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9C, 0xA5, 0xA5); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xFD, 0x5A, 0x5A); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0xE0); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x0F); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCE, 0x22); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x01); + if (cur_vrefresh == 165) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x20); + } else if (cur_vrefresh == 144) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x00, 0x02, 0x83, + 0x00, 0x10, 0x14, 0x00, 0x00, 0xC3, 0x00, 0x10, + 0x14, 0x00, 0x00, 0xE0, 0x00, 0x10, 0x14, 0x00, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x22, 0x18, 0x18, + 0x18, 0x18, 0x18); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x07); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB5, + 0x04, 0x0A, 0x08, 0x0A, 0x04, 0x00, 0xC4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xD9, + 0x66, 0xE4, 0xE4, 0x66, 0xE4, 0xE4, 0x00, 0xC4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCE, + 0x01, 0x01, 0x01, 0x01, 0x04, 0x07, 0xA4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x30); + } else if (cur_vrefresh == 120) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x00, 0x02, 0x83, + 0x00, 0x10, 0x14, 0x00, 0x00, 0xC3, 0x00, 0x10, + 0x14, 0x00, 0x00, 0xE0, 0x10, 0x10, 0x9C, 0x00, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x22, 0x18, 0x18, + 0x18, 0x18, 0x18); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x07); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB5, + 0x04, 0x0C, 0x08, 0x0C, 0x04, 0x00, 0xC4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xD9, + 0x88, 0x40, 0x40, 0x88, 0x40, 0x40, 0x00, 0xEB, + 0x11, 0xFF); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCE, + 0x01, 0x01, 0x01, 0x01, 0x04, 0x09, 0x2C); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x30); + } else if (cur_vrefresh == 90) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xE0, 0x40, 0x10, 0xA8, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x07); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, + 0x04, 0x10, 0x08, 0x0C, 0x04, 0x00, 0xC4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xD3, + 0x55, 0x80, 0x80, 0x55, 0x80, 0xB0, 0x00, 0x9C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCB, + 0x01, 0x01, 0x01, 0x01, 0x04, 0x06, 0x1C); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x00); + } else { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xE0, 0xA0, 0x10, 0xC8, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x07); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, + 0x04, 0x18, 0x08, 0x0C, 0x02, 0x00, 0xC4); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xD3, + 0x88, 0x4A, 0x4A, 0x88, 0x4A, 0x4A, 0x00, 0xEB, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCB, + 0x01, 0x01, 0x01, 0x01, 0x04, 0x2C); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x00); + } + + drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc); + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static int icna3520_init_sequence(struct panel_info *pinfo) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; + struct drm_dsc_picture_parameter_set pps; + + int cur_mode = icna35xx_get_current_mode(pinfo); + int cur_vrefresh = drm_mode_vrefresh(&pinfo->desc->modes[cur_mode]); + + pinfo->dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9C, 0xA5, 0xA5); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xFD, 0x5A, 0x5A); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0xE0); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + if (cur_vrefresh == 120) { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xD8, 0x00, 0x1C, 0x00, 0x4C); + } else { + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x10); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x00); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, + 0x00, 0xDB, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x00, + 0xDB, 0x00, 0x1C, 0x07, 0xD6, 0x00); + } + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x00); + + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x0D); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x27); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB6, 0x03); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xBB, 0x01); + mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x24); + + drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc); + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + return dsi_ctx.accum_err; +} + +static const struct drm_display_mode odin2portal_modes[] = { + { + /* 165Hz */ + .clock = (1080 + 98 + 1 + 23) * (1920 + 20 + 1 + 15) * 165 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 98, + .hsync_end = 1080 + 98 + 1, + .htotal = 1080 + 98 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 20, + .vsync_end = 1920 + 20 + 1, + .vtotal = 1920 + 20 + 1 + 15, + }, + { + /* 144Hz */ + .clock = (1080 + 156 + 1 + 23) * (1920 + 20 + 1 + 15) * 144 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 156, + .hsync_end = 1080 + 156 + 1, + .htotal = 1080 + 156 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 20, + .vsync_end = 1920 + 20 + 1, + .vtotal = 1920 + 20 + 1 + 15, + }, + { + /* 120Hz */ + .clock = (1080 + 156 + 1 + 23) * (1920 + 412 + 1 + 15) * 120 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 156, + .hsync_end = 1080 + 156 + 1, + .htotal = 1080 + 156 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 412, + .vsync_end = 1920 + 412 + 1, + .vtotal = 1920 + 412 + 1 + 15, + }, + { + /* 90Hz */ + .clock = (1080 + 156 + 1 + 23) * (1920 + 1192 + 1 + 15) * 90 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 156, + .hsync_end = 1080 + 156 + 1, + .htotal = 1080 + 156 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 1192, + .vsync_end = 1920 + 1192 + 1, + .vtotal = 1920 + 1192 + 1 + 15, + }, + { + /* 60Hz */ + .clock = (1080 + 156 + 1 + 23) * (1920 + 2760 + 1 + 15) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 156, + .hsync_end = 1080 + 156 + 1, + .htotal = 1080 + 156 + 1 + 23, + .vdisplay = 1920, + .vsync_start = 1920 + 2760, + .vsync_end = 1920 + 2760 + 1, + .vtotal = 1920 + 2760 + 1 + 15, + } +}; + +static const struct drm_display_mode thor_top_modes[] = { + { + /* 120Hz */ + .clock = (1080 + 24 + 1 + 24) * (1920 + 28 + 1 + 28) * 120 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 24, + .hsync_end = 1080 + 24 + 1, + .htotal = 1080 + 24 + 1 + 24, + .vdisplay = 1920, + .vsync_start = 1920 + 28, + .vsync_end = 1920 + 28 + 1, + .vtotal = 1920 + 28 + 1 + 28, + }, + { + /* 60Hz */ + .clock = (1080 + 24 + 1 + 24) * (1920 + 2006 + 1 + 28) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 24, + .hsync_end = 1080 + 24 + 1, + .htotal = 1080 + 24 + 1 + 24, + .vdisplay = 1920, + .vsync_start = 1920 + 2006, + .vsync_end = 1920 + 2006 + 1, + .vtotal = 1920 + 2006 + 1 + 28, + } +}; + +static struct panel_desc odin2portal_desc = { + .modes = odin2portal_modes, + .num_modes = ARRAY_SIZE(odin2portal_modes), + .width_mm = 160, + .height_mm = 89, + .bpc = 8, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_LPM, + .init_sequence = icna3512_init_sequence, + .dsc = { + .dsc_version_major = 0x1, + .dsc_version_minor = 0x1, + .slice_height = 20, + .slice_width = 540, + .slice_count = 2, + .bits_per_component = 8, + .bits_per_pixel = 8 << 4, + .block_pred_enable = true, + }, +}; + +static struct panel_desc thor_top_desc = { + .modes = thor_top_modes, + .num_modes = ARRAY_SIZE(thor_top_modes), + .width_mm = 136, + .height_mm = 68, + .bpc = 8, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_LPM, + .init_sequence = icna3520_init_sequence, + .dsc = { + .dsc_version_major = 0x1, + .dsc_version_minor = 0x1, + .slice_height = 12, + .slice_width = 540, + .slice_count = 2, + .bits_per_component = 8, + .bits_per_pixel = 8 << 4, + .block_pred_enable = true, + }, +}; + +static void icna35xx_reset(struct panel_info *pinfo) +{ + gpiod_set_value_cansleep(pinfo->reset_gpio, 0); + usleep_range(20000, 21000); + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); + usleep_range(20000, 21000); + gpiod_set_value_cansleep(pinfo->reset_gpio, 0); + usleep_range(20000, 21000); +} + +static int icna35xx_prepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(panel_supplies), pinfo->supplies); + if (ret < 0) { + dev_err(panel->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + icna35xx_reset(pinfo); + + ret = pinfo->desc->init_sequence(pinfo); + if (ret < 0) { + regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies); + dev_err(panel->dev, "failed to initialize panel: %d\n", ret); + return ret; + } + + return 0; +} + +static int icna35xx_disable(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; + + pinfo->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 50); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); + + return dsi_ctx.accum_err; +} + +static int icna35xx_unprepare(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + + gpiod_set_value_cansleep(pinfo->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies); + + return 0; +} + +static void icna35xx_remove(struct mipi_dsi_device *dsi) +{ + struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(pinfo->dsi); + if (ret < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&pinfo->panel); +} + +static int icna35xx_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct panel_info *pinfo = to_panel_info(panel); + int i; + + for (i = 0; i < pinfo->desc->num_modes; i++) { + const struct drm_display_mode *m = &pinfo->desc->modes[i]; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, drm_mode_vrefresh(m)); + return -ENOMEM; + } + + mode->type = DRM_MODE_TYPE_DRIVER; + if (i == 0) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + connector->display_info.width_mm = pinfo->desc->width_mm; + connector->display_info.height_mm = pinfo->desc->height_mm; + connector->display_info.bpc = pinfo->desc->bpc; + pinfo->connector = connector; + + return pinfo->desc->num_modes; +} + +static enum drm_panel_orientation icna35xx_get_orientation(struct drm_panel *panel) +{ + struct panel_info *pinfo = to_panel_info(panel); + + return pinfo->orientation; +} + +static const struct drm_panel_funcs icna35xx_panel_funcs = { + .disable = icna35xx_disable, + .prepare = icna35xx_prepare, + .unprepare = icna35xx_unprepare, + .get_modes = icna35xx_get_modes, + .get_orientation = icna35xx_get_orientation, +}; + +static int icna35xx_bl_update_status(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness = backlight_get_brightness(bl); + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return 0; +} + +static int icna35xx_bl_get_brightness(struct backlight_device *bl) +{ + struct mipi_dsi_device *dsi = bl_get_data(bl); + u16 brightness; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); + if (ret < 0) + return ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + return brightness; +} + +static const struct backlight_ops icna35xx_bl_ops = { + .update_status = icna35xx_bl_update_status, + .get_brightness = icna35xx_bl_get_brightness, +}; + +static struct backlight_device *icna35xx_create_backlight(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 4096, + .max_brightness = 4096, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, + &icna35xx_bl_ops, &props); +} + +static int icna35xx_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct panel_info *pinfo; + int ret; + + pinfo = devm_drm_panel_alloc(dev, __typeof(*pinfo), panel, + &icna35xx_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(pinfo)) + return PTR_ERR(pinfo); + + ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(panel_supplies), + panel_supplies, &pinfo->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(pinfo->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio), "failed to get reset gpio\n"); + + pinfo->desc = (struct panel_desc *)of_device_get_match_data(dev); + if (!pinfo->desc) + return -ENODEV; + + pinfo->dsi = dsi; + mipi_dsi_set_drvdata(dsi, pinfo); + + ret = of_drm_get_panel_orientation(dev->of_node, &pinfo->orientation); + if (ret < 0) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret); + return ret; + } + + pinfo->panel.prepare_prev_first = true; + + pinfo->panel.backlight = icna35xx_create_backlight(dsi); + if (IS_ERR(pinfo->panel.backlight)) + return dev_err_probe(dev, PTR_ERR(pinfo->panel.backlight), + "Failed to create backlight\n"); + + drm_panel_add(&pinfo->panel); + + pinfo->dsi->lanes = pinfo->desc->lanes; + pinfo->dsi->format = pinfo->desc->format; + pinfo->dsi->mode_flags = pinfo->desc->mode_flags; + pinfo->dsi->dsc = &pinfo->desc->dsc; + + ret = mipi_dsi_attach(pinfo->dsi); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + drm_panel_remove(&pinfo->panel); + return ret; + } + + return 0; +} + +static const struct of_device_id icna35xx_of_match[] = { + { .compatible = "ayntec,odin2portal-panel", .data = &odin2portal_desc }, + { .compatible = "ayntec,odin3-panel", .data = &thor_top_desc }, + { .compatible = "ayntec,thor-panel-top", .data = &thor_top_desc }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, icna35xx_of_match); + +static struct mipi_dsi_driver icna35xx_driver = { + .probe = icna35xx_probe, + .remove = icna35xx_remove, + .driver = { + .name = "panel-chipone-icna35xx", + .of_match_table = icna35xx_of_match, + }, +}; +module_mipi_dsi_driver(icna35xx_driver); + +MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in>"); +MODULE_DESCRIPTION("DRM driver for Chipone ICNA35XX based MIPI DSI panels"); +MODULE_LICENSE("GPL"); -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] drm/panel: Add panel driver for Chipone ICNA35XX based panels 2026-05-09 20:18 ` Aaron Kling via B4 Relay (?) @ 2026-05-09 21:09 ` sashiko-bot -1 siblings, 0 replies; 10+ messages in thread From: sashiko-bot @ 2026-05-09 21:09 UTC (permalink / raw) To: Aaron Kling via B4 Relay; +Cc: krzk+dt, conor+dt, robh, devicetree Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [High] NULL pointer dereference of `connector` in `icna35xx_get_current_mode()` - [High] Mutation of shared global static data (`dsi->dsc`) - [High] Data races, state leaks, and unprotected hardware access in backlight operations - [Medium] Reset GPIO left deasserted on `icna35xx_prepare` error path - [Low] Missing Kconfig dependency for DRM_DISPLAY_DSC_HELPER -- commit c36140d9bdc899b722496ec403d1552b4c5aaf5a Author: Teguh Sobirin <teguh@sobir.in> drm/panel: Add panel driver for Chipone ICNA35XX based panels This commit adds support for the ICNA3512 and ICNA3520 DDICs used in the AYN Odin 2 Portal, Odin 3, and Thor top panels. > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig > index 7450b27622a23..1368b5a0b6c91 100644 > --- a/drivers/gpu/drm/panel/Kconfig > +++ b/drivers/gpu/drm/panel/Kconfig > @@ -105,6 +105,17 @@ config DRM_PANEL_BOE_TV101WUM_LL2 > Say Y here if you want to support for BOE TV101WUM-LL2 > WUXGA PANEL DSI Video Mode panel > > +config DRM_PANEL_CHIPONE_ICNA35XX > + tristate "Chipone ICNA35XX panel driver" > + depends on OF > + depends on DRM_MIPI_DSI > + depends on BACKLIGHT_CLASS_DEVICE > + select DRM_DISPLAY_HELPER Since the driver calls drm_dsc_pps_payload_pack(), should this also select DRM_DISPLAY_DSC_HELPER? Otherwise, it might cause unresolved symbol linker errors if the DSC helper isn't pulled in by another driver. > + help > + Say Y here if you want to enable support for the panels built > + around the Chipone ICNA3512 and ICNA3520 display controllers, > + such as some Tianma panels used in AYN Odin2 Portal and Thor. [ ... ] > diff --git a/drivers/gpu/drm/panel/panel-chipone-icna35xx.c b/drivers/gpu/drm/panel/panel-chipone-icna35xx.c > new file mode 100644 > index 0000000000000..1be903270db8e > --- /dev/null > +++ b/drivers/gpu/drm/panel/panel-chipone-icna35xx.c [ ... ] > +static int icna35xx_get_current_mode(struct panel_info *pinfo) > +{ > + struct drm_connector *connector = pinfo->connector; > + struct drm_crtc_state *crtc_state; > + int i; > + > + /* Return the default (first) mode if no info available yet */ > + if (!connector->state || !connector->state->crtc) > + return 0; Can connector be NULL here? The pinfo->connector field is only populated when icna35xx_get_modes() is invoked. If drm_panel_prepare() is called before modes are probed, connector will be NULL and this will cause a NULL pointer dereference. [ ... ] > +static int icna35xx_prepare(struct drm_panel *panel) > +{ > + struct panel_info *pinfo = to_panel_info(panel); > + int ret; > + > + ret = regulator_bulk_enable(ARRAY_SIZE(panel_supplies), pinfo->supplies); > + if (ret < 0) { > + dev_err(panel->dev, "failed to enable regulators: %d\n", ret); > + return ret; > + } > + > + icna35xx_reset(pinfo); > + > + ret = pinfo->desc->init_sequence(pinfo); > + if (ret < 0) { > + regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies); Does this error path leave the reset GPIO deasserted? When initialization fails, it might be necessary to assert the reset GPIO (gpiod_set_value_cansleep(pinfo->reset_gpio, 1)) before disabling the regulators, similar to what icna35xx_unprepare() does. > + dev_err(panel->dev, "failed to initialize panel: %d\n", ret); > + return ret; > + } > + > + return 0; > +} [ ... ] > +static int icna35xx_bl_update_status(struct backlight_device *bl) > +{ > + struct mipi_dsi_device *dsi = bl_get_data(bl); > + u16 brightness = backlight_get_brightness(bl); > + int ret; > + > + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; > + > + ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); > + if (ret < 0) > + return ret; Are there potential data races or state leaks here? The mode_flags are modified using non-atomic bitwise operations without any locking, which could race with icna35xx_disable() and icna35xx_prepare(). Also, if mipi_dsi_dcs_set_display_brightness_large() fails, the function returns early without restoring MIPI_DSI_MODE_LPM, which permanently alters the panel's mode state for subsequent operations. Finally, could this send DCS commands to a suspended panel? If userspace alters brightness while the panel is off, there is no check ensuring the hardware is powered on, which might trigger DSI bus timeouts. > + > + dsi->mode_flags |= MIPI_DSI_MODE_LPM; > + > + return 0; > +} > + > +static int icna35xx_bl_get_brightness(struct backlight_device *bl) > +{ > + struct mipi_dsi_device *dsi = bl_get_data(bl); > + u16 brightness; > + int ret; > + > + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; > + > + ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); > + if (ret < 0) > + return ret; Similar to icna35xx_bl_update_status(), does this early return leak the MIPI_DSI_MODE_LPM state? And is it safe to modify mode_flags here without locking or checking if the panel is powered on? > + > + dsi->mode_flags |= MIPI_DSI_MODE_LPM; > + > + return brightness; > +} [ ... ] > +static int icna35xx_probe(struct mipi_dsi_device *dsi) > +{ [ ... ] > + pinfo->dsi->lanes = pinfo->desc->lanes; > + pinfo->dsi->format = pinfo->desc->format; > + pinfo->dsi->mode_flags = pinfo->desc->mode_flags; > + pinfo->dsi->dsc = &pinfo->desc->dsc; Is it safe to point dsi->dsc directly to the static global pinfo->desc->dsc? DSI host drivers like MSM call drm_dsc_compute_rc_parameters(dsi->dsc), which computes and writes Rate Control parameters directly into the struct. This modifies the globally shared descriptor in the .data section, potentially corrupting state if multiple panels exist or if the driver is unbound and rebound. Consider copying the DSC configuration to a dynamically allocated structure. > + > + ret = mipi_dsi_attach(pinfo->dsi); > + if (ret < 0) { > + dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); > + drm_panel_remove(&pinfo->panel); > + return ret; > + } > + > + return 0; > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260509-icna35xx-v1-0-688d3d4e10f9@gmail.com?part=2 ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-05-09 21:51 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-09 20:18 [PATCH 0/2] drm/panel: Add panel driver for Chipone ICNA35XX based panels Aaron Kling 2026-05-09 20:18 ` Aaron Kling via B4 Relay 2026-05-09 20:18 ` [PATCH 1/2] dt-bindings: display: panel: Add Chipone ICNA 35xx OLED driver bindings Aaron Kling 2026-05-09 20:18 ` Aaron Kling via B4 Relay 2026-05-09 20:46 ` sashiko-bot 2026-05-09 21:25 ` Rob Herring (Arm) 2026-05-09 21:51 ` Aaron Kling 2026-05-09 20:18 ` [PATCH 2/2] drm/panel: Add panel driver for Chipone ICNA35XX based panels Aaron Kling 2026-05-09 20:18 ` Aaron Kling via B4 Relay 2026-05-09 21:09 ` sashiko-bot
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.