diff options
| author | Dave Airlie <airlied@redhat.com> | 2025-12-26 19:00:34 +1000 |
|---|---|---|
| committer | Dave Airlie <airlied@redhat.com> | 2025-12-26 19:00:41 +1000 |
| commit | 7bc0f871f992f1469229ffcd2b40a45ec5f695b0 (patch) | |
| tree | 01a60f01ce5d0c5a574cc3ed60e3f8defcc41c66 | |
| parent | 6c8e404891e1059564d1a15a71d3d76070304dde (diff) | |
| parent | 332070795bd96193756cb4446eddc3ec9ff6a0e8 (diff) | |
Merge tag 'drm-misc-next-2025-12-19' of https://gitlab.freedesktop.org/drm/misc/kernel into drm-next
drm-misc-next for 6.20:
Core Changes:
- dma-buf: Add tracepoints
- sched: Introduce new helpers
Driver Changes:
- amdxdna: Enable hardware context priority, Remove (obsolete and
never public) NPU2 Support, Race condition fix
- rockchip: Add RK3368 HDMI Support
- rz-du: Add RZ/V2H(P) MIPI-DSI Support
- panels:
- st7571: Introduce SPI support
- New panels: Sitronix ST7920, Samsung LTL106HL02, LG LH546WF1-ED01, HannStar HSD156JUW2
Signed-off-by: Dave Airlie <airlied@redhat.com>
From: Maxime Ripard <mripard@redhat.com>
Link: https://patch.msgid.link/20251219-arcane-quaint-skunk-e383b0@houat
53 files changed, 3541 insertions, 1445 deletions
diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,dsi.yaml b/Documentation/devicetree/bindings/display/bridge/renesas,dsi.yaml index 5a99d9b9635e..c20625b8425e 100644 --- a/Documentation/devicetree/bindings/display/bridge/renesas,dsi.yaml +++ b/Documentation/devicetree/bindings/display/bridge/renesas,dsi.yaml @@ -14,16 +14,21 @@ description: | RZ/G2L alike family of SoC's. The encoder can operate in DSI mode, with up to four data lanes. -allOf: - - $ref: /schemas/display/dsi-controller.yaml# - properties: compatible: - items: + oneOf: + - items: + - enum: + - renesas,r9a07g044-mipi-dsi # RZ/G2{L,LC} + - renesas,r9a07g054-mipi-dsi # RZ/V2L + - const: renesas,rzg2l-mipi-dsi + + - items: + - const: renesas,r9a09g056-mipi-dsi # RZ/V2N + - const: renesas,r9a09g057-mipi-dsi + - enum: - - renesas,r9a07g044-mipi-dsi # RZ/G2{L,LC} - - renesas,r9a07g054-mipi-dsi # RZ/V2L - - const: renesas,rzg2l-mipi-dsi + - renesas,r9a09g057-mipi-dsi # RZ/V2H(P) reg: maxItems: 1 @@ -49,34 +54,56 @@ properties: - const: debug clocks: - items: - - description: DSI D-PHY PLL multiplied clock - - description: DSI D-PHY system clock - - description: DSI AXI bus clock - - description: DSI Register access clock - - description: DSI Video clock - - description: DSI D-PHY Escape mode transmit clock + oneOf: + - items: + - description: DSI D-PHY PLL multiplied clock + - description: DSI D-PHY system clock + - description: DSI AXI bus clock + - description: DSI Register access clock + - description: DSI Video clock + - description: DSI D-PHY Escape mode transmit clock + - items: + - description: DSI D-PHY PLL reference clock + - description: DSI AXI bus clock + - description: DSI Register access clock + - description: DSI Video clock + - description: DSI D-PHY Escape mode transmit clock clock-names: - items: - - const: pllclk - - const: sysclk - - const: aclk - - const: pclk - - const: vclk - - const: lpclk + oneOf: + - items: + - const: pllclk + - const: sysclk + - const: aclk + - const: pclk + - const: vclk + - const: lpclk + - items: + - const: pllrefclk + - const: aclk + - const: pclk + - const: vclk + - const: lpclk resets: - items: - - description: MIPI_DSI_CMN_RSTB - - description: MIPI_DSI_ARESET_N - - description: MIPI_DSI_PRESET_N + oneOf: + - items: + - description: MIPI_DSI_CMN_RSTB + - description: MIPI_DSI_ARESET_N + - description: MIPI_DSI_PRESET_N + - items: + - description: MIPI_DSI_ARESET_N + - description: MIPI_DSI_PRESET_N reset-names: - items: - - const: rst - - const: arst - - const: prst + oneOf: + - items: + - const: rst + - const: arst + - const: prst + - items: + - const: arst + - const: prst power-domains: maxItems: 1 @@ -130,6 +157,41 @@ required: unevaluatedProperties: false +allOf: + - $ref: ../dsi-controller.yaml# + + - if: + properties: + compatible: + contains: + const: renesas,r9a09g057-mipi-dsi + then: + properties: + clocks: + maxItems: 5 + + clock-names: + maxItems: 5 + + resets: + maxItems: 2 + + reset-names: + maxItems: 2 + else: + properties: + clocks: + minItems: 6 + + clock-names: + minItems: 6 + + resets: + minItems: 3 + + reset-names: + minItems: 3 + examples: - | #include <dt-bindings/clock/r9a07g044-cpg.h> diff --git a/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml b/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml index 2219d3d4ac43..f641efaeb8b3 100644 --- a/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml +++ b/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml @@ -4,14 +4,16 @@ $id: http://devicetree.org/schemas/display/panel/lg,sw43408.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: LG SW43408 1080x2160 DSI panel +title: LG SW43408 AMOLED DDIC maintainers: - Casey Connolly <casey.connolly@linaro.org> description: - This panel is used on the Pixel 3, it is a 60hz OLED panel which - required DSC (Display Stream Compression) and has rounded corners. + The SW43408 is display driver IC with connected panel. + + LG LH546WF1-ED01 panel is used on the Pixel 3, it is a 60hz OLED panel + which required DSC (Display Stream Compression) and has rounded corners. allOf: - $ref: panel-common.yaml# @@ -19,6 +21,9 @@ allOf: properties: compatible: items: + - enum: + # LG 5.46 inch, 1080x2160 pixels, 18:9 ratio + - lg,sw43408-lh546wf1-ed01 - const: lg,sw43408 reg: @@ -46,7 +51,7 @@ examples: #size-cells = <0>; panel@0 { - compatible = "lg,sw43408"; + compatible = "lg,sw43408-lh546wf1-ed01", "lg,sw43408"; reg = <0>; vddi-supply = <&vreg_l14a_1p88>; diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml index 8d668979b62d..2f90c887b7b8 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple-dsi.yaml @@ -55,6 +55,8 @@ properties: - panasonic,vvx10f004b00 # Panasonic 10" WUXGA TFT LCD panel - panasonic,vvx10f034n00 + # Samsung ltl106hl02 10.6" Full HD TFT LCD panel + - samsung,ltl106hl02-001 # Samsung s6e3fa7 1080x2220 based AMS559NK06 AMOLED panel - samsung,s6e3fa7-ams559nk06 # Shangai Top Display Optoelectronics 7" TL070WSH30 1024x600 TFT LCD panel diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml index 24e277b19094..fc244fbb5a54 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml @@ -154,6 +154,8 @@ properties: - hannstar,hsd070pww1 # HannStar Display Corp. HSD100PXN1 10.1" XGA LVDS panel - hannstar,hsd100pxn1 + # HannStar Display Corp. HSD156JUW2 15.6" FHD (1920x1080) TFT LCD panel + - hannstar,hsd156juw2 # Hitachi Ltd. Corporation 9" WVGA (800x480) TFT LCD panel - hit,tx23d38vm0caa # Innolux AT043TN24 4.3" WQVGA TFT LCD panel diff --git a/Documentation/devicetree/bindings/display/panel/samsung,s6e3fc2x01.yaml b/Documentation/devicetree/bindings/display/panel/samsung,s6e3fc2x01.yaml index d48354fb52ea..fd4388f5fb11 100644 --- a/Documentation/devicetree/bindings/display/panel/samsung,s6e3fc2x01.yaml +++ b/Documentation/devicetree/bindings/display/panel/samsung,s6e3fc2x01.yaml @@ -6,11 +6,11 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Samsung S6E3FC2X01 AMOLED DDIC -description: The S6E3FC2X01 is display driver IC with connected panel. - maintainers: - David Heidelberg <david@ixit.cz> +description: The S6E3FC2X01 is display driver IC with connected panel. + allOf: - $ref: panel-common.yaml# @@ -25,25 +25,21 @@ properties: reg: maxItems: 1 - reset-gpios: true - - port: true - - vddio-supply: - description: VDD regulator + poc-supply: + description: POC regulator vci-supply: description: VCI regulator - poc-supply: - description: POC regulator + vddio-supply: + description: VDD regulator required: - compatible - reset-gpios - - vddio-supply - - vci-supply - poc-supply + - vci-supply + - vddio-supply unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml index 9d096856a79a..29716764413a 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml @@ -23,6 +23,7 @@ properties: - rockchip,rk3228-dw-hdmi - rockchip,rk3288-dw-hdmi - rockchip,rk3328-dw-hdmi + - rockchip,rk3368-dw-hdmi - rockchip,rk3399-dw-hdmi - rockchip,rk3568-dw-hdmi diff --git a/Documentation/devicetree/bindings/display/sitronix,st7571.yaml b/Documentation/devicetree/bindings/display/sitronix,st7571.yaml index b83721eb4b7f..1931a47c4217 100644 --- a/Documentation/devicetree/bindings/display/sitronix,st7571.yaml +++ b/Documentation/devicetree/bindings/display/sitronix,st7571.yaml @@ -76,3 +76,28 @@ examples: }; }; }; + + spi { + #address-cells = <1>; + #size-cells = <0>; + + display@0 { + compatible = "sitronix,st7571"; + reg = <0>; + reset-gpios = <&gpio0 3 GPIO_ACTIVE_LOW>; + width-mm = <37>; + height-mm = <27>; + + panel-timing { + hactive = <128>; + vactive = <96>; + hback-porch = <0>; + vback-porch = <0>; + clock-frequency = <0>; + hfront-porch = <0>; + hsync-len = <0>; + vfront-porch = <0>; + vsync-len = <0>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/display/sitronix,st7920.yaml b/Documentation/devicetree/bindings/display/sitronix,st7920.yaml new file mode 100644 index 000000000000..c4f006fc41e1 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sitronix,st7920.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/sitronix,st7920.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sitronix ST7920 LCD Display Controllers + +maintainers: + - Iker Pedrosa <ikerpedrosam@gmail.com> + +description: + The Sitronix ST7920 is a controller for monochrome dot-matrix graphical LCDs, + most commonly used for 128x64 pixel displays. + +properties: + compatible: + const: sitronix,st7920 + + reg: + maxItems: 1 + + vdd-supply: + description: Regulator that provides 5V Vdd power supply + + reset-gpios: + maxItems: 1 + + spi-max-frequency: + maximum: 600000 + +required: + - compatible + - reg + - spi-max-frequency + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + display@0 { + compatible = "sitronix,st7920"; + reg = <0>; + vdd-supply = <®_5v>; + reset-gpios = <&gpio 25 GPIO_ACTIVE_LOW>; + spi-max-frequency = <600000>; + spi-cs-high; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index dc731d37c8fe..bdbe32ddcedb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8201,6 +8201,9 @@ S: Maintained F: Documentation/devicetree/bindings/display/sitronix,st7567.yaml F: Documentation/devicetree/bindings/display/sitronix,st7571.yaml F: drivers/gpu/drm/sitronix/st7571-i2c.c +F: drivers/gpu/drm/sitronix/st7571-spi.c +F: drivers/gpu/drm/sitronix/st7571.c +F: drivers/gpu/drm/sitronix/st7571.h DRM DRIVER FOR SITRONIX ST7701 PANELS M: Jagan Teki <jagan@amarulasolutions.com> @@ -8223,6 +8226,13 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: Documentation/devicetree/bindings/display/sitronix,st7735r.yaml F: drivers/gpu/drm/sitronix/st7735r.c +DRM DRIVER FOR SITRONIX ST7920 LCD DISPLAYS +M: Iker Pedrosa <ikerpedrosam@gmail.com> +S: Maintained +T: git https://gitlab.freedesktop.org/drm/misc/kernel.git +F: Documentation/devicetree/bindings/display/sitronix,st7920.yaml +F: drivers/gpu/drm/sitronix/st7920.c + DRM DRIVER FOR SOLOMON SSD130X OLED DISPLAYS M: Javier Martinez Canillas <javierm@redhat.com> S: Maintained diff --git a/drivers/accel/amdxdna/Makefile b/drivers/accel/amdxdna/Makefile index 6344aaf523fa..3fa0e74fd8f5 100644 --- a/drivers/accel/amdxdna/Makefile +++ b/drivers/accel/amdxdna/Makefile @@ -18,7 +18,6 @@ amdxdna-y := \ amdxdna_sysfs.o \ amdxdna_ubuf.o \ npu1_regs.o \ - npu2_regs.o \ npu4_regs.o \ npu5_regs.o \ npu6_regs.o diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index 42d876a427c5..5511ab2ef242 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -468,6 +468,12 @@ static int aie2_alloc_resource(struct amdxdna_hwctx *hwctx) struct alloc_requests *xrs_req; int ret; + if (AIE2_FEATURE_ON(xdna->dev_handle, AIE2_TEMPORAL_ONLY)) { + hwctx->num_unused_col = xdna->dev_handle->total_col - hwctx->num_col; + hwctx->num_col = xdna->dev_handle->total_col; + return aie2_create_context(xdna->dev_handle, hwctx); + } + xrs_req = kzalloc(sizeof(*xrs_req), GFP_KERNEL); if (!xrs_req) return -ENOMEM; @@ -499,9 +505,15 @@ static void aie2_release_resource(struct amdxdna_hwctx *hwctx) struct amdxdna_dev *xdna = hwctx->client->xdna; int ret; - ret = xrs_release_resource(xdna->xrs_hdl, (uintptr_t)hwctx); - if (ret) - XDNA_ERR(xdna, "Release AIE resource failed, ret %d", ret); + if (AIE2_FEATURE_ON(xdna->dev_handle, AIE2_TEMPORAL_ONLY)) { + ret = aie2_destroy_context(xdna->dev_handle, hwctx); + if (ret) + XDNA_ERR(xdna, "Destroy temporal only context failed, ret %d", ret); + } else { + ret = xrs_release_resource(xdna->xrs_hdl, (uintptr_t)hwctx); + if (ret) + XDNA_ERR(xdna, "Release AIE resource failed, ret %d", ret); + } } static int aie2_ctx_syncobj_create(struct amdxdna_hwctx *hwctx) diff --git a/drivers/accel/amdxdna/aie2_message.c b/drivers/accel/amdxdna/aie2_message.c index 03b75757a6e6..051f4ceaabae 100644 --- a/drivers/accel/amdxdna/aie2_message.c +++ b/drivers/accel/amdxdna/aie2_message.c @@ -192,6 +192,40 @@ int aie2_query_firmware_version(struct amdxdna_dev_hdl *ndev, return 0; } +static int aie2_destroy_context_req(struct amdxdna_dev_hdl *ndev, u32 id) +{ + DECLARE_AIE2_MSG(destroy_ctx, MSG_OP_DESTROY_CONTEXT); + struct amdxdna_dev *xdna = ndev->xdna; + int ret; + + req.context_id = id; + ret = aie2_send_mgmt_msg_wait(ndev, &msg); + if (ret) + XDNA_WARN(xdna, "Destroy context failed, ret %d", ret); + + return ret; +} + +static u32 aie2_get_context_priority(struct amdxdna_dev_hdl *ndev, + struct amdxdna_hwctx *hwctx) +{ + if (!AIE2_FEATURE_ON(ndev, AIE2_PREEMPT)) + return PRIORITY_HIGH; + + switch (hwctx->qos.priority) { + case AMDXDNA_QOS_REALTIME_PRIORITY: + return PRIORITY_REALTIME; + case AMDXDNA_QOS_HIGH_PRIORITY: + return PRIORITY_HIGH; + case AMDXDNA_QOS_NORMAL_PRIORITY: + return PRIORITY_NORMAL; + case AMDXDNA_QOS_LOW_PRIORITY: + return PRIORITY_LOW; + default: + return PRIORITY_HIGH; + } +} + int aie2_create_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwctx) { DECLARE_AIE2_MSG(create_ctx, MSG_OP_CREATE_CONTEXT); @@ -205,22 +239,24 @@ int aie2_create_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwct req.aie_type = 1; req.start_col = hwctx->start_col; req.num_col = hwctx->num_col; + req.num_unused_col = hwctx->num_unused_col; req.num_cq_pairs_requested = 1; req.pasid = hwctx->client->pasid; - req.context_priority = 2; + req.context_priority = aie2_get_context_priority(ndev, hwctx); ret = aie2_send_mgmt_msg_wait(ndev, &msg); if (ret) return ret; hwctx->fw_ctx_id = resp.context_id; - WARN_ONCE(hwctx->fw_ctx_id == -1, "Unexpected context id"); + if (WARN_ON_ONCE(hwctx->fw_ctx_id == -1)) + return -EINVAL; if (ndev->force_preempt_enabled) { ret = aie2_runtime_cfg(ndev, AIE2_RT_CFG_FORCE_PREEMPT, &hwctx->fw_ctx_id); if (ret) { XDNA_ERR(xdna, "failed to enable force preempt %d", ret); - return ret; + goto del_ctx_req; } } @@ -237,51 +273,39 @@ int aie2_create_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwct ret = pci_irq_vector(to_pci_dev(xdna->ddev.dev), resp.msix_id); if (ret == -EINVAL) { - XDNA_ERR(xdna, "not able to create channel"); - goto out_destroy_context; + XDNA_ERR(xdna, "Alloc IRQ failed %d", ret); + goto del_ctx_req; } intr_reg = i2x.mb_head_ptr_reg + 4; hwctx->priv->mbox_chann = xdna_mailbox_create_channel(ndev->mbox, &x2i, &i2x, intr_reg, ret); if (!hwctx->priv->mbox_chann) { - XDNA_ERR(xdna, "not able to create channel"); + XDNA_ERR(xdna, "Not able to create channel"); ret = -EINVAL; - goto out_destroy_context; + goto del_ctx_req; } ndev->hwctx_num++; - XDNA_DBG(xdna, "%s mailbox channel irq: %d, msix_id: %d", - hwctx->name, ret, resp.msix_id); - XDNA_DBG(xdna, "%s created fw ctx %d pasid %d", hwctx->name, - hwctx->fw_ctx_id, hwctx->client->pasid); + XDNA_DBG(xdna, "Mailbox channel irq: %d, msix_id: %d", ret, resp.msix_id); + XDNA_DBG(xdna, "Created fw ctx %d pasid %d", hwctx->fw_ctx_id, hwctx->client->pasid); return 0; -out_destroy_context: - aie2_destroy_context(ndev, hwctx); +del_ctx_req: + aie2_destroy_context_req(ndev, hwctx->fw_ctx_id); return ret; } int aie2_destroy_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwctx) { - DECLARE_AIE2_MSG(destroy_ctx, MSG_OP_DESTROY_CONTEXT); struct amdxdna_dev *xdna = ndev->xdna; int ret; - if (hwctx->fw_ctx_id == -1) - return 0; - xdna_mailbox_stop_channel(hwctx->priv->mbox_chann); - - req.context_id = hwctx->fw_ctx_id; - ret = aie2_send_mgmt_msg_wait(ndev, &msg); - if (ret) - XDNA_WARN(xdna, "%s destroy context failed, ret %d", hwctx->name, ret); - + ret = aie2_destroy_context_req(ndev, hwctx->fw_ctx_id); xdna_mailbox_destroy_channel(hwctx->priv->mbox_chann); - XDNA_DBG(xdna, "%s destroyed fw ctx %d", hwctx->name, - hwctx->fw_ctx_id); + XDNA_DBG(xdna, "Destroyed fw ctx %d", hwctx->fw_ctx_id); hwctx->priv->mbox_chann = NULL; hwctx->fw_ctx_id = -1; ndev->hwctx_num--; diff --git a/drivers/accel/amdxdna/aie2_msg_priv.h b/drivers/accel/amdxdna/aie2_msg_priv.h index 1c957a6298d3..728ef56f7f0a 100644 --- a/drivers/accel/amdxdna/aie2_msg_priv.h +++ b/drivers/accel/amdxdna/aie2_msg_priv.h @@ -108,11 +108,17 @@ struct cq_pair { struct cq_info i2x_q; }; +#define PRIORITY_REALTIME 1 +#define PRIORITY_HIGH 2 +#define PRIORITY_NORMAL 3 +#define PRIORITY_LOW 4 + struct create_ctx_req { __u32 aie_type; __u8 start_col; __u8 num_col; - __u16 reserved; + __u8 num_unused_col; + __u8 reserved; __u8 num_cq_pairs_requested; __u8 reserved1; __u16 pasid; diff --git a/drivers/accel/amdxdna/aie2_pci.h b/drivers/accel/amdxdna/aie2_pci.h index c6b5cf4ae5c4..a929fa98a121 100644 --- a/drivers/accel/amdxdna/aie2_pci.h +++ b/drivers/accel/amdxdna/aie2_pci.h @@ -232,6 +232,7 @@ struct aie2_hw_ops { enum aie2_fw_feature { AIE2_NPU_COMMAND, AIE2_PREEMPT, + AIE2_TEMPORAL_ONLY, AIE2_FEATURE_MAX }; diff --git a/drivers/accel/amdxdna/amdxdna_ctx.h b/drivers/accel/amdxdna/amdxdna_ctx.h index b6151244d64f..b29449a92f60 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.h +++ b/drivers/accel/amdxdna/amdxdna_ctx.h @@ -98,6 +98,7 @@ struct amdxdna_hwctx { u32 *col_list; u32 start_col; u32 num_col; + u32 num_unused_col; #define HWCTX_STAT_INIT 0 #define HWCTX_STAT_READY 1 #define HWCTX_STAT_STOP 2 diff --git a/drivers/accel/amdxdna/amdxdna_mailbox.c b/drivers/accel/amdxdna/amdxdna_mailbox.c index a60a85ce564c..469242ed8224 100644 --- a/drivers/accel/amdxdna/amdxdna_mailbox.c +++ b/drivers/accel/amdxdna/amdxdna_mailbox.c @@ -191,26 +191,34 @@ mailbox_send_msg(struct mailbox_channel *mb_chann, struct mailbox_msg *mb_msg) u32 head, tail; u32 start_addr; u32 tmp_tail; + int ret; head = mailbox_get_headptr(mb_chann, CHAN_RES_X2I); tail = mb_chann->x2i_tail; - ringbuf_size = mailbox_get_ringbuf_size(mb_chann, CHAN_RES_X2I); + ringbuf_size = mailbox_get_ringbuf_size(mb_chann, CHAN_RES_X2I) - sizeof(u32); start_addr = mb_chann->res[CHAN_RES_X2I].rb_start_addr; tmp_tail = tail + mb_msg->pkg_size; - if (tail < head && tmp_tail >= head) - goto no_space; - - if (tail >= head && (tmp_tail > ringbuf_size - sizeof(u32) && - mb_msg->pkg_size >= head)) - goto no_space; - if (tail >= head && tmp_tail > ringbuf_size - sizeof(u32)) { +check_again: + if (tail >= head && tmp_tail > ringbuf_size) { write_addr = mb_chann->mb->res.ringbuf_base + start_addr + tail; writel(TOMBSTONE, write_addr); /* tombstone is set. Write from the start of the ringbuf */ tail = 0; + tmp_tail = tail + mb_msg->pkg_size; + } + + if (tail < head && tmp_tail >= head) { + ret = read_poll_timeout(mailbox_get_headptr, head, + tmp_tail < head || tail >= head, + 1, 100, false, mb_chann, CHAN_RES_X2I); + if (ret) + return ret; + + if (tail >= head) + goto check_again; } write_addr = mb_chann->mb->res.ringbuf_base + start_addr + tail; @@ -222,9 +230,6 @@ mailbox_send_msg(struct mailbox_channel *mb_chann, struct mailbox_msg *mb_msg) mb_msg->pkg.header.id); return 0; - -no_space: - return -ENOSPC; } static int diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c index 1973ab67721b..7d59764d7869 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.c +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c @@ -51,7 +51,6 @@ MODULE_DEVICE_TABLE(pci, pci_ids); static const struct amdxdna_device_id amdxdna_ids[] = { { 0x1502, 0x0, &dev_npu1_info }, - { 0x17f0, 0x0, &dev_npu2_info }, { 0x17f0, 0x10, &dev_npu4_info }, { 0x17f0, 0x11, &dev_npu5_info }, { 0x17f0, 0x20, &dev_npu6_info }, @@ -105,43 +104,38 @@ failed: return ret; } -static void amdxdna_drm_close(struct drm_device *ddev, struct drm_file *filp) +static void amdxdna_client_cleanup(struct amdxdna_client *client) { - struct amdxdna_client *client = filp->driver_priv; - struct amdxdna_dev *xdna = to_xdna_dev(ddev); - - XDNA_DBG(xdna, "closing pid %d", client->pid); - + list_del(&client->node); + amdxdna_hwctx_remove_all(client); xa_destroy(&client->hwctx_xa); cleanup_srcu_struct(&client->hwctx_srcu); mutex_destroy(&client->mm_lock); + if (client->dev_heap) drm_gem_object_put(to_gobj(client->dev_heap)); iommu_sva_unbind_device(client->sva); - XDNA_DBG(xdna, "pid %d closed", client->pid); kfree(client); } -static int amdxdna_flush(struct file *f, fl_owner_t id) +static void amdxdna_drm_close(struct drm_device *ddev, struct drm_file *filp) { - struct drm_file *filp = f->private_data; struct amdxdna_client *client = filp->driver_priv; - struct amdxdna_dev *xdna = client->xdna; + struct amdxdna_dev *xdna = to_xdna_dev(ddev); int idx; - XDNA_DBG(xdna, "PID %d flushing...", client->pid); + XDNA_DBG(xdna, "closing pid %d", client->pid); + if (!drm_dev_enter(&xdna->ddev, &idx)) - return 0; + return; mutex_lock(&xdna->dev_lock); - list_del_init(&client->node); - amdxdna_hwctx_remove_all(client); + amdxdna_client_cleanup(client); mutex_unlock(&xdna->dev_lock); drm_dev_exit(idx); - return 0; } static int amdxdna_drm_get_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) @@ -217,7 +211,6 @@ static const struct file_operations amdxdna_fops = { .owner = THIS_MODULE, .open = accel_open, .release = drm_release, - .flush = amdxdna_flush, .unlocked_ioctl = drm_ioctl, .compat_ioctl = drm_compat_ioctl, .poll = drm_poll, @@ -333,8 +326,7 @@ static void amdxdna_remove(struct pci_dev *pdev) client = list_first_entry_or_null(&xdna->client_list, struct amdxdna_client, node); while (client) { - list_del_init(&client->node); - amdxdna_hwctx_remove_all(client); + amdxdna_client_cleanup(client); client = list_first_entry_or_null(&xdna->client_list, struct amdxdna_client, node); diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.h b/drivers/accel/amdxdna/amdxdna_pci_drv.h index 0d50c4c8b353..6580cb5ec7e2 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.h +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.h @@ -137,7 +137,6 @@ struct amdxdna_client { /* Add device info below */ extern const struct amdxdna_dev_info dev_npu1_info; -extern const struct amdxdna_dev_info dev_npu2_info; extern const struct amdxdna_dev_info dev_npu4_info; extern const struct amdxdna_dev_info dev_npu5_info; extern const struct amdxdna_dev_info dev_npu6_info; diff --git a/drivers/accel/amdxdna/npu2_regs.c b/drivers/accel/amdxdna/npu2_regs.c deleted file mode 100644 index ad0743fb06d5..000000000000 --- a/drivers/accel/amdxdna/npu2_regs.c +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2023-2024, Advanced Micro Devices, Inc. - */ - -#include <drm/amdxdna_accel.h> -#include <drm/drm_device.h> -#include <drm/gpu_scheduler.h> -#include <linux/sizes.h> - -#include "aie2_pci.h" -#include "amdxdna_mailbox.h" -#include "amdxdna_pci_drv.h" - -/* NPU Public Registers on MpNPUAxiXbar (refer to Diag npu_registers.h) */ -#define MPNPU_PWAITMODE 0x301003C -#define MPNPU_PUB_SEC_INTR 0x3010060 -#define MPNPU_PUB_PWRMGMT_INTR 0x3010064 -#define MPNPU_PUB_SCRATCH0 0x301006C -#define MPNPU_PUB_SCRATCH1 0x3010070 -#define MPNPU_PUB_SCRATCH2 0x3010074 -#define MPNPU_PUB_SCRATCH3 0x3010078 -#define MPNPU_PUB_SCRATCH4 0x301007C -#define MPNPU_PUB_SCRATCH5 0x3010080 -#define MPNPU_PUB_SCRATCH6 0x3010084 -#define MPNPU_PUB_SCRATCH7 0x3010088 -#define MPNPU_PUB_SCRATCH8 0x301008C -#define MPNPU_PUB_SCRATCH9 0x3010090 -#define MPNPU_PUB_SCRATCH10 0x3010094 -#define MPNPU_PUB_SCRATCH11 0x3010098 -#define MPNPU_PUB_SCRATCH12 0x301009C -#define MPNPU_PUB_SCRATCH13 0x30100A0 -#define MPNPU_PUB_SCRATCH14 0x30100A4 -#define MPNPU_PUB_SCRATCH15 0x30100A8 -#define MP0_C2PMSG_73 0x3810A24 -#define MP0_C2PMSG_123 0x3810AEC - -#define MP1_C2PMSG_0 0x3B10900 -#define MP1_C2PMSG_60 0x3B109F0 -#define MP1_C2PMSG_61 0x3B109F4 - -#define MPNPU_SRAM_X2I_MAILBOX_0 0x3600000 -#define MPNPU_SRAM_X2I_MAILBOX_15 0x361E000 -#define MPNPU_SRAM_X2I_MAILBOX_31 0x363E000 -#define MPNPU_SRAM_I2X_MAILBOX_31 0x363F000 - -#define MMNPU_APERTURE0_BASE 0x3000000 -#define MMNPU_APERTURE1_BASE 0x3600000 -#define MMNPU_APERTURE3_BASE 0x3810000 -#define MMNPU_APERTURE4_BASE 0x3B10000 - -/* PCIe BAR Index for NPU2 */ -#define NPU2_REG_BAR_INDEX 0 -#define NPU2_MBOX_BAR_INDEX 0 -#define NPU2_PSP_BAR_INDEX 4 -#define NPU2_SMU_BAR_INDEX 5 -#define NPU2_SRAM_BAR_INDEX 2 -/* Associated BARs and Apertures */ -#define NPU2_REG_BAR_BASE MMNPU_APERTURE0_BASE -#define NPU2_MBOX_BAR_BASE MMNPU_APERTURE0_BASE -#define NPU2_PSP_BAR_BASE MMNPU_APERTURE3_BASE -#define NPU2_SMU_BAR_BASE MMNPU_APERTURE4_BASE -#define NPU2_SRAM_BAR_BASE MMNPU_APERTURE1_BASE - -static const struct amdxdna_dev_priv npu2_dev_priv = { - .fw_path = "amdnpu/17f0_00/npu.sbin", - .protocol_major = 0x6, - .protocol_minor = 0x6, - .rt_config = npu4_default_rt_cfg, - .dpm_clk_tbl = npu4_dpm_clk_table, - .fw_feature_tbl = npu4_fw_feature_table, - .col_align = COL_ALIGN_NATURE, - .mbox_dev_addr = NPU2_MBOX_BAR_BASE, - .mbox_size = 0, /* Use BAR size */ - .sram_dev_addr = NPU2_SRAM_BAR_BASE, - .hwctx_limit = 16, - .sram_offs = { - DEFINE_BAR_OFFSET(MBOX_CHANN_OFF, NPU2_SRAM, MPNPU_SRAM_X2I_MAILBOX_0), - DEFINE_BAR_OFFSET(FW_ALIVE_OFF, NPU2_SRAM, MPNPU_SRAM_X2I_MAILBOX_15), - }, - .psp_regs_off = { - DEFINE_BAR_OFFSET(PSP_CMD_REG, NPU2_PSP, MP0_C2PMSG_123), - DEFINE_BAR_OFFSET(PSP_ARG0_REG, NPU2_REG, MPNPU_PUB_SCRATCH3), - DEFINE_BAR_OFFSET(PSP_ARG1_REG, NPU2_REG, MPNPU_PUB_SCRATCH4), - DEFINE_BAR_OFFSET(PSP_ARG2_REG, NPU2_REG, MPNPU_PUB_SCRATCH9), - DEFINE_BAR_OFFSET(PSP_INTR_REG, NPU2_PSP, MP0_C2PMSG_73), - DEFINE_BAR_OFFSET(PSP_STATUS_REG, NPU2_PSP, MP0_C2PMSG_123), - DEFINE_BAR_OFFSET(PSP_RESP_REG, NPU2_REG, MPNPU_PUB_SCRATCH3), - DEFINE_BAR_OFFSET(PSP_PWAITMODE_REG, NPU2_REG, MPNPU_PWAITMODE), - }, - .smu_regs_off = { - DEFINE_BAR_OFFSET(SMU_CMD_REG, NPU2_SMU, MP1_C2PMSG_0), - DEFINE_BAR_OFFSET(SMU_ARG_REG, NPU2_SMU, MP1_C2PMSG_60), - DEFINE_BAR_OFFSET(SMU_INTR_REG, NPU2_SMU, MMNPU_APERTURE4_BASE), - DEFINE_BAR_OFFSET(SMU_RESP_REG, NPU2_SMU, MP1_C2PMSG_61), - DEFINE_BAR_OFFSET(SMU_OUT_REG, NPU2_SMU, MP1_C2PMSG_60), - }, - .hw_ops = { - .set_dpm = npu4_set_dpm, - }, -}; - -const struct amdxdna_dev_info dev_npu2_info = { - .reg_bar = NPU2_REG_BAR_INDEX, - .mbox_bar = NPU2_MBOX_BAR_INDEX, - .sram_bar = NPU2_SRAM_BAR_INDEX, - .psp_bar = NPU2_PSP_BAR_INDEX, - .smu_bar = NPU2_SMU_BAR_INDEX, - .first_col = 0, - .dev_mem_buf_shift = 15, /* 32 KiB aligned */ - .dev_mem_base = AIE2_DEVM_BASE, - .dev_mem_size = AIE2_DEVM_SIZE, - .vbnv = "RyzenAI-npu2", - .device_type = AMDXDNA_DEV_TYPE_KMQ, - .dev_priv = &npu2_dev_priv, - .ops = &aie2_ops, /* NPU2 can share NPU1's callback */ -}; diff --git a/drivers/accel/amdxdna/npu4_regs.c b/drivers/accel/amdxdna/npu4_regs.c index 4ca21db70478..a62234fd266d 100644 --- a/drivers/accel/amdxdna/npu4_regs.c +++ b/drivers/accel/amdxdna/npu4_regs.c @@ -90,6 +90,7 @@ const struct dpm_clk_freq npu4_dpm_clk_table[] = { const struct aie2_fw_feature_tbl npu4_fw_feature_table[] = { { .feature = AIE2_NPU_COMMAND, .min_minor = 15 }, { .feature = AIE2_PREEMPT, .min_minor = 12 }, + { .feature = AIE2_TEMPORAL_ONLY, .min_minor = 12 }, { 0 } }; diff --git a/drivers/accel/ivpu/ivpu_gem.c b/drivers/accel/ivpu/ivpu_gem.c index ece68f570b7e..98b9ce26962b 100644 --- a/drivers/accel/ivpu/ivpu_gem.c +++ b/drivers/accel/ivpu/ivpu_gem.c @@ -95,7 +95,7 @@ int __must_check ivpu_bo_bind(struct ivpu_bo *bo) if (!bo->mmu_mapped) { drm_WARN_ON(&vdev->drm, !bo->ctx); - ret = ivpu_mmu_context_map_sgt(vdev, bo->ctx, bo->vpu_addr, sgt, + ret = ivpu_mmu_context_map_sgt(vdev, bo->ctx, bo->vpu_addr, sgt, ivpu_bo_size(bo), ivpu_bo_is_snooped(bo), ivpu_bo_is_read_only(bo)); if (ret) { ivpu_err(vdev, "Failed to map BO in MMU: %d\n", ret); diff --git a/drivers/accel/ivpu/ivpu_mmu_context.c b/drivers/accel/ivpu/ivpu_mmu_context.c index 87ad593ef47d..c4014c83e727 100644 --- a/drivers/accel/ivpu/ivpu_mmu_context.c +++ b/drivers/accel/ivpu/ivpu_mmu_context.c @@ -429,11 +429,12 @@ static void ivpu_mmu_context_unmap_pages(struct ivpu_mmu_context *ctx, u64 vpu_a } int -ivpu_mmu_context_map_sgt(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, - u64 vpu_addr, struct sg_table *sgt, bool llc_coherent, bool read_only) +ivpu_mmu_context_map_sgt(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, u64 vpu_addr, + struct sg_table *sgt, size_t bo_size, bool llc_coherent, bool read_only) { size_t start_vpu_addr = vpu_addr; struct scatterlist *sg; + size_t sgt_size = 0; int ret; u64 prot; u64 i; @@ -462,12 +463,25 @@ ivpu_mmu_context_map_sgt(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, ivpu_dbg(vdev, MMU_MAP, "Map ctx: %u dma_addr: 0x%llx vpu_addr: 0x%llx size: %lu\n", ctx->id, dma_addr, vpu_addr, size); + if (sgt_size + size > bo_size) { + ivpu_err(vdev, "Scatter-gather table size exceeds buffer object size\n"); + ret = -EINVAL; + goto err_unmap_pages; + } + ret = ivpu_mmu_context_map_pages(vdev, ctx, vpu_addr, dma_addr, size, prot); if (ret) { ivpu_err(vdev, "Failed to map context pages\n"); goto err_unmap_pages; } vpu_addr += size; + sgt_size += size; + } + + if (sgt_size < bo_size) { + ivpu_err(vdev, "Scatter-gather table size too small to cover buffer object size\n"); + ret = -EINVAL; + goto err_unmap_pages; } if (!ctx->is_cd_valid) { @@ -493,7 +507,7 @@ ivpu_mmu_context_map_sgt(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, return 0; err_unmap_pages: - ivpu_mmu_context_unmap_pages(ctx, start_vpu_addr, vpu_addr - start_vpu_addr); + ivpu_mmu_context_unmap_pages(ctx, start_vpu_addr, sgt_size); mutex_unlock(&ctx->lock); return ret; } diff --git a/drivers/accel/ivpu/ivpu_mmu_context.h b/drivers/accel/ivpu/ivpu_mmu_context.h index 663a11a9db11..cc02e7bab04e 100644 --- a/drivers/accel/ivpu/ivpu_mmu_context.h +++ b/drivers/accel/ivpu/ivpu_mmu_context.h @@ -41,8 +41,9 @@ int ivpu_mmu_context_insert_node(struct ivpu_mmu_context *ctx, const struct ivpu u64 size, struct drm_mm_node *node); void ivpu_mmu_context_remove_node(struct ivpu_mmu_context *ctx, struct drm_mm_node *node); -int ivpu_mmu_context_map_sgt(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, - u64 vpu_addr, struct sg_table *sgt, bool llc_coherent, bool read_only); +int +ivpu_mmu_context_map_sgt(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, u64 vpu_addr, + struct sg_table *sgt, size_t bo_size, bool llc_coherent, bool read_only); void ivpu_mmu_context_unmap_sgt(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, u64 vpu_addr, struct sg_table *sgt); int ivpu_mmu_context_set_pages_ro(struct ivpu_device *vdev, struct ivpu_mmu_context *ctx, diff --git a/drivers/dma-buf/dma-buf.c b/drivers/dma-buf/dma-buf.c index 4a7716a5b7c6..a4d8f2ff94e4 100644 --- a/drivers/dma-buf/dma-buf.c +++ b/drivers/dma-buf/dma-buf.c @@ -35,6 +35,28 @@ #include "dma-buf-sysfs-stats.h" +#define CREATE_TRACE_POINTS +#include <trace/events/dma_buf.h> + +/* + * dmabuf->name must be accessed with holding dmabuf->name_lock. + * we need to take the lock around the tracepoint call itself where + * it is called in the code. + * + * Note: FUNC##_enabled() is a static branch that will only + * be set when the trace event is enabled. + */ +#define DMA_BUF_TRACE(FUNC, ...) \ + do { \ + if (FUNC##_enabled()) { \ + guard(spinlock)(&dmabuf->name_lock); \ + FUNC(__VA_ARGS__); \ + } else if (IS_ENABLED(CONFIG_LOCKDEP)) { \ + /* Expose this lock when lockdep is enabled */ \ + guard(spinlock)(&dmabuf->name_lock); \ + } \ + } while (0) + /* Wrapper to hide the sg_table page link from the importer */ struct dma_buf_sg_table_wrapper { struct sg_table *original; @@ -226,6 +248,8 @@ static int dma_buf_mmap_internal(struct file *file, struct vm_area_struct *vma) dmabuf->size >> PAGE_SHIFT) return -EINVAL; + DMA_BUF_TRACE(trace_dma_buf_mmap_internal, dmabuf); + return dmabuf->ops->mmap(dmabuf, vma); } @@ -751,6 +775,8 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info) __dma_buf_list_add(dmabuf); + DMA_BUF_TRACE(trace_dma_buf_export, dmabuf); + return dmabuf; err_dmabuf: @@ -774,10 +800,16 @@ EXPORT_SYMBOL_NS_GPL(dma_buf_export, "DMA_BUF"); */ int dma_buf_fd(struct dma_buf *dmabuf, int flags) { + int fd; + if (!dmabuf || !dmabuf->file) return -EINVAL; - return FD_ADD(flags, dmabuf->file); + fd = FD_ADD(flags, dmabuf->file); + if (fd >= 0) + DMA_BUF_TRACE(trace_dma_buf_fd, dmabuf, fd); + + return fd; } EXPORT_SYMBOL_NS_GPL(dma_buf_fd, "DMA_BUF"); @@ -792,6 +824,7 @@ EXPORT_SYMBOL_NS_GPL(dma_buf_fd, "DMA_BUF"); struct dma_buf *dma_buf_get(int fd) { struct file *file; + struct dma_buf *dmabuf; file = fget(fd); @@ -803,7 +836,11 @@ struct dma_buf *dma_buf_get(int fd) return ERR_PTR(-EINVAL); } - return file->private_data; + dmabuf = file->private_data; + + DMA_BUF_TRACE(trace_dma_buf_get, dmabuf, fd); + + return dmabuf; } EXPORT_SYMBOL_NS_GPL(dma_buf_get, "DMA_BUF"); @@ -823,6 +860,8 @@ void dma_buf_put(struct dma_buf *dmabuf) return; fput(dmabuf->file); + + DMA_BUF_TRACE(trace_dma_buf_put, dmabuf); } EXPORT_SYMBOL_NS_GPL(dma_buf_put, "DMA_BUF"); @@ -1015,6 +1054,9 @@ dma_buf_dynamic_attach(struct dma_buf *dmabuf, struct device *dev, list_add(&attach->node, &dmabuf->attachments); dma_resv_unlock(dmabuf->resv); + DMA_BUF_TRACE(trace_dma_buf_dynamic_attach, dmabuf, attach, + dma_buf_attachment_is_dynamic(attach), dev); + return attach; err_attach: @@ -1059,6 +1101,9 @@ void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach) if (dmabuf->ops->detach) dmabuf->ops->detach(dmabuf, attach); + DMA_BUF_TRACE(trace_dma_buf_detach, dmabuf, attach, + dma_buf_attachment_is_dynamic(attach), attach->dev); + kfree(attach); } EXPORT_SYMBOL_NS_GPL(dma_buf_detach, "DMA_BUF"); @@ -1525,6 +1570,8 @@ int dma_buf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma, vma_set_file(vma, dmabuf->file); vma->vm_pgoff = pgoff; + DMA_BUF_TRACE(trace_dma_buf_mmap, dmabuf); + return dmabuf->ops->mmap(dmabuf, vma); } EXPORT_SYMBOL_NS_GPL(dma_buf_mmap, "DMA_BUF"); diff --git a/drivers/dma-buf/st-dma-fence.c b/drivers/dma-buf/st-dma-fence.c index 4dbe39c58bfb..73ed6fd48a13 100644 --- a/drivers/dma-buf/st-dma-fence.c +++ b/drivers/dma-buf/st-dma-fence.c @@ -33,50 +33,9 @@ static void mock_fence_release(struct dma_fence *f) kmem_cache_free(slab_fences, to_mock_fence(f)); } -struct wait_cb { - struct dma_fence_cb cb; - struct task_struct *task; -}; - -static void mock_wakeup(struct dma_fence *f, struct dma_fence_cb *cb) -{ - wake_up_process(container_of(cb, struct wait_cb, cb)->task); -} - -static long mock_wait(struct dma_fence *f, bool intr, long timeout) -{ - const int state = intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE; - struct wait_cb cb = { .task = current }; - - if (dma_fence_add_callback(f, &cb.cb, mock_wakeup)) - return timeout; - - while (timeout) { - set_current_state(state); - - if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &f->flags)) - break; - - if (signal_pending_state(state, current)) - break; - - timeout = schedule_timeout(timeout); - } - __set_current_state(TASK_RUNNING); - - if (!dma_fence_remove_callback(f, &cb.cb)) - return timeout; - - if (signal_pending_state(state, current)) - return -ERESTARTSYS; - - return -ETIME; -} - static const struct dma_fence_ops mock_ops = { .get_driver_name = mock_name, .get_timeline_name = mock_name, - .wait = mock_wait, .release = mock_fence_release, }; @@ -349,14 +308,14 @@ static int test_wait(void *arg) dma_fence_enable_sw_signaling(f); - if (dma_fence_wait_timeout(f, false, 0) != -ETIME) { + if (dma_fence_wait_timeout(f, false, 0) != 0) { pr_err("Wait reported complete before being signaled\n"); goto err_free; } dma_fence_signal(f); - if (dma_fence_wait_timeout(f, false, 0) != 0) { + if (dma_fence_wait_timeout(f, false, 0) != 1) { pr_err("Wait reported incomplete after being signaled\n"); goto err_free; } @@ -393,16 +352,16 @@ static int test_wait_timeout(void *arg) dma_fence_enable_sw_signaling(wt.f); - if (dma_fence_wait_timeout(wt.f, false, 1) != -ETIME) { + if (dma_fence_wait_timeout(wt.f, false, 1) != 0) { pr_err("Wait reported complete before being signaled\n"); goto err_free; } mod_timer(&wt.timer, jiffies + 1); - if (dma_fence_wait_timeout(wt.f, false, 2) == -ETIME) { + if (dma_fence_wait_timeout(wt.f, false, HZ) == 0) { if (timer_pending(&wt.timer)) { - pr_notice("Timer did not fire within the jiffy!\n"); + pr_notice("Timer did not fire within one HZ!\n"); err = 0; /* not our fault! */ } else { pr_err("Wait reported incomplete after timeout\n"); diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index 29174ab58ff3..fbd1164174b0 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -94,9 +94,12 @@ err_release: } /** - * drm_gem_shmem_init - Initialize an allocated object. + * drm_gem_shmem_init - Initialize an allocated object of the given size * @dev: DRM device - * @obj: The allocated shmem GEM object. + * @shmem: shmem GEM object to initialize + * @size: Size of the object to initialize + * + * This function initializes an allocated shmem GEM object. * * Returns: * 0 on success, or a negative error code on failure. diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index e1b0fa4000cd..2d4ab745fdad 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -250,14 +250,14 @@ struct drm_syncobj *drm_syncobj_find(struct drm_file *file_private, { struct drm_syncobj *syncobj; - spin_lock(&file_private->syncobj_table_lock); + xa_lock(&file_private->syncobj_xa); /* Check if we currently have a reference on the object */ - syncobj = idr_find(&file_private->syncobj_idr, handle); + syncobj = xa_load(&file_private->syncobj_xa, handle); if (syncobj) drm_syncobj_get(syncobj); - spin_unlock(&file_private->syncobj_table_lock); + xa_unlock(&file_private->syncobj_xa); return syncobj; } @@ -598,23 +598,15 @@ int drm_syncobj_get_handle(struct drm_file *file_private, { int ret; - /* take a reference to put in the idr */ + /* take a reference to put in the xarray */ drm_syncobj_get(syncobj); - idr_preload(GFP_KERNEL); - spin_lock(&file_private->syncobj_table_lock); - ret = idr_alloc(&file_private->syncobj_idr, syncobj, 1, 0, GFP_NOWAIT); - spin_unlock(&file_private->syncobj_table_lock); - - idr_preload_end(); - - if (ret < 0) { + ret = xa_alloc(&file_private->syncobj_xa, handle, syncobj, xa_limit_32b, + GFP_NOWAIT); + if (ret) drm_syncobj_put(syncobj); - return ret; - } - *handle = ret; - return 0; + return ret; } EXPORT_SYMBOL(drm_syncobj_get_handle); @@ -638,10 +630,7 @@ static int drm_syncobj_destroy(struct drm_file *file_private, { struct drm_syncobj *syncobj; - spin_lock(&file_private->syncobj_table_lock); - syncobj = idr_remove(&file_private->syncobj_idr, handle); - spin_unlock(&file_private->syncobj_table_lock); - + syncobj = xa_erase(&file_private->syncobj_xa, handle); if (!syncobj) return -EINVAL; @@ -722,20 +711,13 @@ static int drm_syncobj_fd_to_handle(struct drm_file *file_private, if (fd_file(f)->f_op != &drm_syncobj_file_fops) return -EINVAL; - /* take a reference to put in the idr */ + /* take a reference to put in the xarray */ syncobj = fd_file(f)->private_data; drm_syncobj_get(syncobj); - idr_preload(GFP_KERNEL); - spin_lock(&file_private->syncobj_table_lock); - ret = idr_alloc(&file_private->syncobj_idr, syncobj, 1, 0, GFP_NOWAIT); - spin_unlock(&file_private->syncobj_table_lock); - idr_preload_end(); - - if (ret > 0) { - *handle = ret; - ret = 0; - } else + ret = xa_alloc(&file_private->syncobj_xa, handle, syncobj, xa_limit_32b, + GFP_NOWAIT); + if (ret) drm_syncobj_put(syncobj); return ret; @@ -814,17 +796,7 @@ err_put_fd: void drm_syncobj_open(struct drm_file *file_private) { - idr_init_base(&file_private->syncobj_idr, 1); - spin_lock_init(&file_private->syncobj_table_lock); -} - -static int -drm_syncobj_release_handle(int id, void *ptr, void *data) -{ - struct drm_syncobj *syncobj = ptr; - - drm_syncobj_put(syncobj); - return 0; + xa_init_flags(&file_private->syncobj_xa, XA_FLAGS_ALLOC1); } /** @@ -838,9 +810,12 @@ drm_syncobj_release_handle(int id, void *ptr, void *data) void drm_syncobj_release(struct drm_file *file_private) { - idr_for_each(&file_private->syncobj_idr, - &drm_syncobj_release_handle, file_private); - idr_destroy(&file_private->syncobj_idr); + struct drm_syncobj *syncobj; + unsigned long handle; + + xa_for_each(&file_private->syncobj_xa, handle, syncobj) + drm_syncobj_put(syncobj); + xa_destroy(&file_private->syncobj_xa); } int diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 7a83804fedca..307152ad7759 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -781,6 +781,19 @@ config DRM_PANEL_SAMSUNG_LD9040 depends on BACKLIGHT_CLASS_DEVICE select VIDEOMODE_HELPERS +config DRM_PANEL_SAMSUNG_LTL106HL02 + tristate "Samsung LTL106HL02 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select VIDEOMODE_HELPERS + help + Say Y here if you want to enable support for the Samsung LTL106HL02 + panel driver which is used in Microsoft Surface 2. + + To compile this driver as a module, choose M here: the module + will be called panel-samsung-ltl106hl02. + config DRM_PANEL_SAMSUNG_S6E3FA7 tristate "Samsung S6E3FA7 panel driver" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index b9562a6fdcb3..aeffaa95666d 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_DRM_PANEL_SAMSUNG_AMS639RQ08) += panel-samsung-ams639rq08.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20) += panel-samsung-atna33xc20.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_DB7430) += panel-samsung-db7430.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_LTL106HL02) += panel-samsung-ltl106hl02.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D16D0) += panel-samsung-s6d16d0.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D27A1) += panel-samsung-s6d27a1.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D7AA0) += panel-samsung-s6d7aa0.o diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c index 46a56ea92ad9..293826c5006b 100644 --- a/drivers/gpu/drm/panel/panel-lg-sw43408.c +++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c @@ -20,13 +20,18 @@ #include <drm/display/drm_dsc.h> #include <drm/display/drm_dsc_helper.h> -#define NUM_SUPPLIES 2 +static const struct regulator_bulk_data sw43408_supplies[] = { + { .supply = "vddi", /* 1.8 V */ + .init_load_uA = 62000 }, + { .supply = "vpnl", /* 3.0 V */ + .init_load_uA = 857000 }, +}; struct sw43408_panel { struct drm_panel base; struct mipi_dsi_device *link; - struct regulator_bulk_data supplies[NUM_SUPPLIES]; + struct regulator_bulk_data *supplies; struct gpio_desc *reset_gpio; @@ -52,7 +57,7 @@ static int sw43408_unprepare(struct drm_panel *panel) gpiod_set_value(sw43408->reset_gpio, 1); - ret = regulator_bulk_disable(ARRAY_SIZE(sw43408->supplies), sw43408->supplies); + ret = regulator_bulk_disable(ARRAY_SIZE(sw43408_supplies), sw43408->supplies); return ret ? : ctx.accum_err; } @@ -119,23 +124,28 @@ static int sw43408_program(struct drm_panel *panel) return ctx.accum_err; } +static void sw43408_reset(struct sw43408_panel *ctx) +{ + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(9000, 10000); + gpiod_set_value(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(9000, 10000); +} + static int sw43408_prepare(struct drm_panel *panel) { struct sw43408_panel *ctx = to_panel_info(panel); int ret; - ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + ret = regulator_bulk_enable(ARRAY_SIZE(sw43408_supplies), ctx->supplies); if (ret < 0) return ret; usleep_range(5000, 6000); - gpiod_set_value(ctx->reset_gpio, 0); - usleep_range(9000, 10000); - gpiod_set_value(ctx->reset_gpio, 1); - usleep_range(1000, 2000); - gpiod_set_value(ctx->reset_gpio, 0); - usleep_range(9000, 10000); + sw43408_reset(ctx); ret = sw43408_program(panel); if (ret) @@ -145,11 +155,11 @@ static int sw43408_prepare(struct drm_panel *panel) poweroff: gpiod_set_value(ctx->reset_gpio, 1); - regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + regulator_bulk_disable(ARRAY_SIZE(sw43408_supplies), ctx->supplies); return ret; } -static const struct drm_display_mode sw43408_mode = { +static const struct drm_display_mode lh546wf1_ed01_mode = { .clock = (1080 + 20 + 32 + 20) * (2160 + 20 + 4 + 20) * 60 / 1000, .hdisplay = 1080, @@ -171,7 +181,7 @@ static const struct drm_display_mode sw43408_mode = { static int sw43408_get_modes(struct drm_panel *panel, struct drm_connector *connector) { - return drm_connector_helper_get_modes_fixed(connector, &sw43408_mode); + return drm_connector_helper_get_modes_fixed(connector, &lh546wf1_ed01_mode); } static int sw43408_backlight_update_status(struct backlight_device *bl) @@ -214,7 +224,8 @@ static const struct drm_panel_funcs sw43408_funcs = { }; static const struct of_device_id sw43408_of_match[] = { - { .compatible = "lg,sw43408", }, + { .compatible = "lg,sw43408", }, /* legacy */ + { .compatible = "lg,sw43408-lh546wf1-ed01", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sw43408_of_match); @@ -224,20 +235,17 @@ static int sw43408_add(struct sw43408_panel *ctx) struct device *dev = &ctx->link->dev; int ret; - ctx->supplies[0].supply = "vddi"; /* 1.88 V */ - ctx->supplies[0].init_load_uA = 62000; - ctx->supplies[1].supply = "vpnl"; /* 3.0 V */ - ctx->supplies[1].init_load_uA = 857000; - - ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), - ctx->supplies); + ret = devm_regulator_bulk_get_const(dev, + ARRAY_SIZE(sw43408_supplies), + sw43408_supplies, + &ctx->supplies); if (ret < 0) return ret; ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset_gpio)) { - ret = PTR_ERR(ctx->reset_gpio); - return dev_err_probe(dev, ret, "cannot get reset gpio\n"); + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); } ret = sw43408_backlight_init(ctx); @@ -294,10 +302,6 @@ static void sw43408_remove(struct mipi_dsi_device *dsi) struct sw43408_panel *ctx = mipi_dsi_get_drvdata(dsi); int ret; - ret = sw43408_unprepare(&ctx->base); - if (ret < 0) - dev_err(&dsi->dev, "failed to unprepare panel: %d\n", ret); - ret = mipi_dsi_detach(dsi); if (ret < 0) dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret); diff --git a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c index a0f58c3b73f6..60701521c3b1 100644 --- a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c +++ b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c @@ -109,177 +109,140 @@ static inline struct otm8009a *panel_to_otm8009a(struct drm_panel *panel) return container_of(panel, struct otm8009a, panel); } -static void otm8009a_dcs_write_buf(struct otm8009a *ctx, const void *data, - size_t len) -{ - struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); - - if (mipi_dsi_dcs_write_buffer(dsi, data, len) < 0) - dev_warn(ctx->dev, "mipi dsi dcs write buffer failed\n"); -} - -#define dcs_write_seq(ctx, seq...) \ -({ \ - static const u8 d[] = { seq }; \ - otm8009a_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \ -}) - #define dcs_write_cmd_at(ctx, cmd, seq...) \ ({ \ - dcs_write_seq(ctx, MCS_ADRSFT, (cmd) & 0xFF); \ - dcs_write_seq(ctx, (cmd) >> 8, seq); \ + mipi_dsi_dcs_write_seq_multi(ctx, MCS_ADRSFT, (cmd) & 0xFF); \ + mipi_dsi_dcs_write_seq_multi(ctx, (cmd) >> 8, seq); \ }) static int otm8009a_init_sequence(struct otm8009a *ctx) { struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; /* Enter CMD2 */ - dcs_write_cmd_at(ctx, MCS_CMD2_ENA1, 0x80, 0x09, 0x01); + dcs_write_cmd_at(&dsi_ctx, MCS_CMD2_ENA1, 0x80, 0x09, 0x01); /* Enter Orise Command2 */ - dcs_write_cmd_at(ctx, MCS_CMD2_ENA2, 0x80, 0x09); - - dcs_write_cmd_at(ctx, MCS_SD_PCH_CTRL, 0x30); - mdelay(10); - - dcs_write_cmd_at(ctx, MCS_NO_DOC1, 0x40); - mdelay(10); - - dcs_write_cmd_at(ctx, MCS_PWR_CTRL4 + 1, 0xA9); - dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 1, 0x34); - dcs_write_cmd_at(ctx, MCS_P_DRV_M, 0x50); - dcs_write_cmd_at(ctx, MCS_VCOMDC, 0x4E); - dcs_write_cmd_at(ctx, MCS_OSC_ADJ, 0x66); /* 65Hz */ - dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 2, 0x01); - dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 5, 0x34); - dcs_write_cmd_at(ctx, MCS_PWR_CTRL2 + 4, 0x33); - dcs_write_cmd_at(ctx, MCS_GVDDSET, 0x79, 0x79); - dcs_write_cmd_at(ctx, MCS_SD_CTRL + 1, 0x1B); - dcs_write_cmd_at(ctx, MCS_PWR_CTRL1 + 2, 0x83); - dcs_write_cmd_at(ctx, MCS_SD_PCH_CTRL + 1, 0x83); - dcs_write_cmd_at(ctx, MCS_RGB_VID_SET, 0x0E); - dcs_write_cmd_at(ctx, MCS_PANSET, 0x00, 0x01); - - dcs_write_cmd_at(ctx, MCS_GOAVST, 0x85, 0x01, 0x00, 0x84, 0x01, 0x00); - dcs_write_cmd_at(ctx, MCS_GOACLKA1, 0x18, 0x04, 0x03, 0x39, 0x00, 0x00, + dcs_write_cmd_at(&dsi_ctx, MCS_CMD2_ENA2, 0x80, 0x09); + + dcs_write_cmd_at(&dsi_ctx, MCS_SD_PCH_CTRL, 0x30); + mipi_dsi_msleep(&dsi_ctx, 10); + + dcs_write_cmd_at(&dsi_ctx, MCS_NO_DOC1, 0x40); + mipi_dsi_msleep(&dsi_ctx, 10); + + dcs_write_cmd_at(&dsi_ctx, MCS_PWR_CTRL4 + 1, 0xA9); + dcs_write_cmd_at(&dsi_ctx, MCS_PWR_CTRL2 + 1, 0x34); + dcs_write_cmd_at(&dsi_ctx, MCS_P_DRV_M, 0x50); + dcs_write_cmd_at(&dsi_ctx, MCS_VCOMDC, 0x4E); + dcs_write_cmd_at(&dsi_ctx, MCS_OSC_ADJ, 0x66); /* 65Hz */ + dcs_write_cmd_at(&dsi_ctx, MCS_PWR_CTRL2 + 2, 0x01); + dcs_write_cmd_at(&dsi_ctx, MCS_PWR_CTRL2 + 5, 0x34); + dcs_write_cmd_at(&dsi_ctx, MCS_PWR_CTRL2 + 4, 0x33); + dcs_write_cmd_at(&dsi_ctx, MCS_GVDDSET, 0x79, 0x79); + dcs_write_cmd_at(&dsi_ctx, MCS_SD_CTRL + 1, 0x1B); + dcs_write_cmd_at(&dsi_ctx, MCS_PWR_CTRL1 + 2, 0x83); + dcs_write_cmd_at(&dsi_ctx, MCS_SD_PCH_CTRL + 1, 0x83); + dcs_write_cmd_at(&dsi_ctx, MCS_RGB_VID_SET, 0x0E); + dcs_write_cmd_at(&dsi_ctx, MCS_PANSET, 0x00, 0x01); + + dcs_write_cmd_at(&dsi_ctx, MCS_GOAVST, 0x85, 0x01, 0x00, 0x84, 0x01, 0x00); + dcs_write_cmd_at(&dsi_ctx, MCS_GOACLKA1, 0x18, 0x04, 0x03, 0x39, 0x00, 0x00, 0x00, 0x18, 0x03, 0x03, 0x3A, 0x00, 0x00, 0x00); - dcs_write_cmd_at(ctx, MCS_GOACLKA3, 0x18, 0x02, 0x03, 0x3B, 0x00, 0x00, + dcs_write_cmd_at(&dsi_ctx, MCS_GOACLKA3, 0x18, 0x02, 0x03, 0x3B, 0x00, 0x00, 0x00, 0x18, 0x01, 0x03, 0x3C, 0x00, 0x00, 0x00); - dcs_write_cmd_at(ctx, MCS_GOAECLK, 0x01, 0x01, 0x20, 0x20, 0x00, 0x00, + dcs_write_cmd_at(&dsi_ctx, MCS_GOAECLK, 0x01, 0x01, 0x20, 0x20, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00); - dcs_write_cmd_at(ctx, MCS_NO_DOC2, 0x00); + dcs_write_cmd_at(&dsi_ctx, MCS_NO_DOC2, 0x00); - dcs_write_cmd_at(ctx, MCS_PANCTRLSET1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - dcs_write_cmd_at(ctx, MCS_PANCTRLSET2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + dcs_write_cmd_at(&dsi_ctx, MCS_PANCTRLSET1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + dcs_write_cmd_at(&dsi_ctx, MCS_PANCTRLSET2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - dcs_write_cmd_at(ctx, MCS_PANCTRLSET3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + dcs_write_cmd_at(&dsi_ctx, MCS_PANCTRLSET3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - dcs_write_cmd_at(ctx, MCS_PANCTRLSET4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - dcs_write_cmd_at(ctx, MCS_PANCTRLSET5, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0, + dcs_write_cmd_at(&dsi_ctx, MCS_PANCTRLSET4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + dcs_write_cmd_at(&dsi_ctx, MCS_PANCTRLSET5, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0); - dcs_write_cmd_at(ctx, MCS_PANCTRLSET6, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, + dcs_write_cmd_at(&dsi_ctx, MCS_PANCTRLSET6, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0); - dcs_write_cmd_at(ctx, MCS_PANCTRLSET7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - dcs_write_cmd_at(ctx, MCS_PANCTRLSET8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + dcs_write_cmd_at(&dsi_ctx, MCS_PANCTRLSET7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + dcs_write_cmd_at(&dsi_ctx, MCS_PANCTRLSET8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - dcs_write_cmd_at(ctx, MCS_PANU2D1, 0x00, 0x26, 0x09, 0x0B, 0x01, 0x25, + dcs_write_cmd_at(&dsi_ctx, MCS_PANU2D1, 0x00, 0x26, 0x09, 0x0B, 0x01, 0x25, 0x00, 0x00, 0x00, 0x00); - dcs_write_cmd_at(ctx, MCS_PANU2D2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + dcs_write_cmd_at(&dsi_ctx, MCS_PANU2D2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x0A, 0x0C, 0x02); - dcs_write_cmd_at(ctx, MCS_PANU2D3, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, + dcs_write_cmd_at(&dsi_ctx, MCS_PANU2D3, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - dcs_write_cmd_at(ctx, MCS_PAND2U1, 0x00, 0x25, 0x0C, 0x0A, 0x02, 0x26, + dcs_write_cmd_at(&dsi_ctx, MCS_PAND2U1, 0x00, 0x25, 0x0C, 0x0A, 0x02, 0x26, 0x00, 0x00, 0x00, 0x00); - dcs_write_cmd_at(ctx, MCS_PAND2U2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + dcs_write_cmd_at(&dsi_ctx, MCS_PAND2U2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x0B, 0x09, 0x01); - dcs_write_cmd_at(ctx, MCS_PAND2U3, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, + dcs_write_cmd_at(&dsi_ctx, MCS_PAND2U3, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - dcs_write_cmd_at(ctx, MCS_PWR_CTRL1 + 1, 0x66); + dcs_write_cmd_at(&dsi_ctx, MCS_PWR_CTRL1 + 1, 0x66); - dcs_write_cmd_at(ctx, MCS_NO_DOC3, 0x06); + dcs_write_cmd_at(&dsi_ctx, MCS_NO_DOC3, 0x06); - dcs_write_cmd_at(ctx, MCS_GMCT2_2P, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10, + dcs_write_cmd_at(&dsi_ctx, MCS_GMCT2_2P, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10, 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A, 0x01); - dcs_write_cmd_at(ctx, MCS_GMCT2_2N, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10, + dcs_write_cmd_at(&dsi_ctx, MCS_GMCT2_2N, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10, 0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A, 0x01); /* Exit CMD2 */ - dcs_write_cmd_at(ctx, MCS_CMD2_ENA1, 0xFF, 0xFF, 0xFF); + dcs_write_cmd_at(&dsi_ctx, MCS_CMD2_ENA1, 0xFF, 0xFF, 0xFF); - ret = mipi_dsi_dcs_nop(dsi); - if (ret) - return ret; - - ret = mipi_dsi_dcs_exit_sleep_mode(dsi); - if (ret) - return ret; + mipi_dsi_dcs_nop_multi(&dsi_ctx); - /* Wait for sleep out exit */ - mdelay(120); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); /* Default portrait 480x800 rgb24 */ - dcs_write_seq(ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); - ret = mipi_dsi_dcs_set_column_address(dsi, 0, OTM8009A_HDISPLAY - 1); - if (ret) - return ret; + mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0, OTM8009A_HDISPLAY - 1); - ret = mipi_dsi_dcs_set_page_address(dsi, 0, OTM8009A_VDISPLAY - 1); - if (ret) - return ret; + mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0, OTM8009A_VDISPLAY - 1); /* See otm8009a driver documentation for pixel format descriptions */ - ret = mipi_dsi_dcs_set_pixel_format(dsi, MIPI_DCS_PIXEL_FMT_24BIT | + mipi_dsi_dcs_set_pixel_format_multi(&dsi_ctx, MIPI_DCS_PIXEL_FMT_24BIT | MIPI_DCS_PIXEL_FMT_24BIT << 4); - if (ret) - return ret; /* Disable CABC feature */ - dcs_write_seq(ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); - ret = mipi_dsi_dcs_set_display_on(dsi); - if (ret) - return ret; + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); - ret = mipi_dsi_dcs_nop(dsi); - if (ret) - return ret; + mipi_dsi_dcs_nop_multi(&dsi_ctx); /* Send Command GRAM memory write (no parameters) */ - dcs_write_seq(ctx, MIPI_DCS_WRITE_MEMORY_START); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_MEMORY_START); /* Wait a short while to let the panel be ready before the 1st frame */ - mdelay(10); + mipi_dsi_msleep(&dsi_ctx, 10); - return 0; + return dsi_ctx.accum_err; } static int otm8009a_disable(struct drm_panel *panel) { struct otm8009a *ctx = panel_to_otm8009a(panel); struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; backlight_disable(ctx->bl_dev); - ret = mipi_dsi_dcs_set_display_off(dsi); - if (ret) - return ret; - - ret = mipi_dsi_dcs_enter_sleep_mode(dsi); - if (ret) - return ret; + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 120); - msleep(120); - - return 0; + return dsi_ctx.accum_err; } static int otm8009a_unprepare(struct drm_panel *panel) @@ -383,6 +346,8 @@ static const struct drm_panel_funcs otm8009a_drm_funcs = { static int otm8009a_backlight_update_status(struct backlight_device *bd) { struct otm8009a *ctx = bl_get_data(bd); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; u8 data[2]; if (!ctx->prepared) { @@ -397,7 +362,7 @@ static int otm8009a_backlight_update_status(struct backlight_device *bd) */ data[0] = MIPI_DCS_SET_DISPLAY_BRIGHTNESS; data[1] = bd->props.brightness; - otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data)); + mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, data, ARRAY_SIZE(data)); /* set Brightness Control & Backlight on */ data[1] = 0x24; @@ -409,9 +374,9 @@ static int otm8009a_backlight_update_status(struct backlight_device *bd) /* Update Brightness Control & Backlight */ data[0] = MIPI_DCS_WRITE_CONTROL_DISPLAY; - otm8009a_dcs_write_buf(ctx, data, ARRAY_SIZE(data)); + mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, data, ARRAY_SIZE(data)); - return 0; + return dsi_ctx.accum_err; } static const struct backlight_ops otm8009a_backlight_ops = { diff --git a/drivers/gpu/drm/panel/panel-samsung-ltl106hl02.c b/drivers/gpu/drm/panel/panel-samsung-ltl106hl02.c new file mode 100644 index 000000000000..1618841b7caa --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-ltl106hl02.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/array_size.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +struct samsung_ltl106hl02 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct regulator *supply; + struct gpio_desc *reset_gpio; +}; + +static inline struct samsung_ltl106hl02 *to_samsung_ltl106hl02(struct drm_panel *panel) +{ + return container_of(panel, struct samsung_ltl106hl02, panel); +} + +static void samsung_ltl106hl02_reset(struct samsung_ltl106hl02 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(2000, 3000); +} + +static int samsung_ltl106hl02_prepare(struct drm_panel *panel) +{ + struct samsung_ltl106hl02 *ctx = to_samsung_ltl106hl02(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_enable(ctx->supply); + if (ret < 0) { + dev_err(dev, "failed to enable power supply %d\n", ret); + return ret; + } + + if (ctx->reset_gpio) + samsung_ltl106hl02_reset(ctx); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 70); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 5); + + return dsi_ctx.accum_err; +} + +static int samsung_ltl106hl02_unprepare(struct drm_panel *panel) +{ + struct samsung_ltl106hl02 *ctx = to_samsung_ltl106hl02(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + + 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, 150); + + if (ctx->reset_gpio) + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + regulator_disable(ctx->supply); + + return 0; +} + +static const struct drm_display_mode samsung_ltl106hl02_mode = { + .clock = (1920 + 32 + 32 + 64) * (1080 + 6 + 3 + 22) * 60 / 1000, + .hdisplay = 1920, + .hsync_start = 1920 + 32, + .hsync_end = 1920 + 32 + 32, + .htotal = 1920 + 32 + 32 + 64, + .vdisplay = 1080, + .vsync_start = 1080 + 6, + .vsync_end = 1080 + 6 + 3, + .vtotal = 1080 + 6 + 3 + 22, + .width_mm = 235, + .height_mm = 132, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, +}; + +static int samsung_ltl106hl02_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + return drm_connector_helper_get_modes_fixed(connector, &samsung_ltl106hl02_mode); +} + +static const struct drm_panel_funcs samsung_ltl106hl02_panel_funcs = { + .prepare = samsung_ltl106hl02_prepare, + .unprepare = samsung_ltl106hl02_unprepare, + .get_modes = samsung_ltl106hl02_get_modes, +}; + +static int samsung_ltl106hl02_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct samsung_ltl106hl02 *ctx; + int ret; + + ctx = devm_drm_panel_alloc(dev, struct samsung_ltl106hl02, panel, + &samsung_ltl106hl02_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + ctx->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(ctx->supply)) + return dev_err_probe(dev, PTR_ERR(ctx->supply), + "Failed to get power regulator\n"); + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM; + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + drm_panel_remove(&ctx->panel); + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); + } + + return 0; +} + +static void samsung_ltl106hl02_remove(struct mipi_dsi_device *dsi) +{ + struct samsung_ltl106hl02 *ctx = mipi_dsi_get_drvdata(dsi); + + drm_panel_remove(&ctx->panel); +} + +static const struct of_device_id samsung_ltl106hl02_of_match[] = { + { .compatible = "samsung,ltl106hl02-001" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, samsung_ltl106hl02_of_match); + +static struct mipi_dsi_driver samsung_ltl106hl02_driver = { + .driver = { + .name = "panel-samsung-ltl106hl02", + .of_match_table = samsung_ltl106hl02_of_match, + }, + .probe = samsung_ltl106hl02_probe, + .remove = samsung_ltl106hl02_remove, +}; +module_mipi_dsi_driver(samsung_ltl106hl02_driver); + +MODULE_AUTHOR("Anton Bambura <jenneron@protonmail.com>"); +MODULE_DESCRIPTION("DRM driver for Samsung LTL106HL02 video mode DSI panel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index b26b682826bc..3acc9f3dac16 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -2509,6 +2509,31 @@ static const struct panel_desc hannstar_hsd101pww2 = { .connector_type = DRM_MODE_CONNECTOR_LVDS, }; +static const struct display_timing hannstar_hsd156juw2_timing = { + .pixelclock = { 66000000, 72800000, 80500000 }, + .hactive = { 1920, 1920, 1920 }, + .hfront_porch = { 20, 30, 30 }, + .hback_porch = { 20, 30, 30 }, + .hsync_len = { 50, 60, 90 }, + .vactive = { 1080, 1080, 1080 }, + .vfront_porch = { 1, 2, 4 }, + .vback_porch = { 1, 2, 4 }, + .vsync_len = { 3, 40, 80 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc hannstar_hsd156juw2 = { + .timings = &hannstar_hsd156juw2_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 344, + .height = 194, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct drm_display_mode hitachi_tx23d38vm0caa_mode = { .clock = 33333, .hdisplay = 800, @@ -5254,6 +5279,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "hannstar,hsd101pww2", .data = &hannstar_hsd101pww2, }, { + .compatible = "hannstar,hsd156juw2", + .data = &hannstar_hsd156juw2, + }, { .compatible = "hit,tx23d38vm0caa", .data = &hitachi_tx23d38vm0caa }, { diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c index 98d4e8d867ed..165dddfde6ca 100644 --- a/drivers/gpu/drm/panthor/panthor_drv.c +++ b/drivers/gpu/drm/panthor/panthor_drv.c @@ -923,8 +923,10 @@ static int panthor_ioctl_bo_create(struct drm_device *ddev, void *data, } if ((args->flags & DRM_PANTHOR_BO_NO_MMAP) && - (args->flags & DRM_PANTHOR_BO_WB_MMAP)) - return -EINVAL; + (args->flags & DRM_PANTHOR_BO_WB_MMAP)) { + ret = -EINVAL; + goto out_dev_exit; + } if (args->exclusive_vm_id) { vm = panthor_vm_pool_get_vm(pfile->vms, args->exclusive_vm_id); diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c index 4beaa589ba66..a64ec8756bed 100644 --- a/drivers/gpu/drm/panthor/panthor_fw.c +++ b/drivers/gpu/drm/panthor/panthor_fw.c @@ -1261,10 +1261,6 @@ void panthor_fw_unplug(struct panthor_device *ptdev) if (ptdev->fw->irq.irq) panthor_job_irq_suspend(&ptdev->fw->irq); - panthor_fw_halt_mcu(ptdev); - if (!panthor_fw_wait_mcu_halted(ptdev)) - drm_warn(&ptdev->base, "Failed to halt MCU on unplug"); - panthor_fw_stop(ptdev); } diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index a17b067a0439..0f83e778d89a 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -23,6 +23,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/rcupdate.h> #include "panthor_devfreq.h" #include "panthor_device.h" @@ -943,6 +944,9 @@ static void group_release_work(struct work_struct *work) release_work); u32 i; + /* dma-fences may still be accessing group->queues under rcu lock. */ + synchronize_rcu(); + for (i = 0; i < group->queue_count; i++) group_free_queue(group, group->queues[i]); diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c index 3b52dfc0ea1e..5edd45424562 100644 --- a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c @@ -7,6 +7,7 @@ #include <linux/bitfield.h> #include <linux/clk.h> +#include <linux/clk/renesas.h> #include <linux/delay.h> #include <linux/dma-mapping.h> #include <linux/io.h> @@ -32,6 +33,8 @@ #include "rzg2l_mipi_dsi_regs.h" +MODULE_IMPORT_NS("RZV2H_CPG"); + #define RZG2L_DCS_BUF_SIZE 128 /* Maximum DCS buffer size in external memory. */ #define RZ_MIPI_DSI_FEATURE_16BPP BIT(0) @@ -46,6 +49,11 @@ struct rzg2l_mipi_dsi_hw_info { u64 *hsfreq_millihz); unsigned int (*dphy_mode_clk_check)(struct rzg2l_mipi_dsi *dsi, unsigned long mode_freq); + struct { + const struct rzv2h_pll_limits **limits; + const u8 *table; + const u8 table_size; + } cpg_plldsi; u32 phy_reg_offset; u32 link_reg_offset; unsigned long min_dclk; @@ -53,6 +61,11 @@ struct rzg2l_mipi_dsi_hw_info { u8 features; }; +struct rzv2h_dsi_mode_calc { + unsigned long mode_freq_khz; + struct rzv2h_pll_pars dsi_parameters; +}; + struct rzg2l_mipi_dsi { struct device *dev; void __iomem *mmio; @@ -68,17 +81,29 @@ struct rzg2l_mipi_dsi { struct drm_bridge *next_bridge; struct clk *vclk; + struct clk *lpclk; enum mipi_dsi_pixel_format format; unsigned int num_data_lanes; unsigned int lanes; unsigned long mode_flags; + struct rzv2h_dsi_mode_calc mode_calc; + /* DCS buffer pointers when using external memory. */ dma_addr_t dcs_buf_phys; u8 *dcs_buf_virt; }; +static const struct rzv2h_pll_limits rzv2h_plldsi_div_limits = { + .fout = { .min = 80 * MEGA, .max = 1500 * MEGA }, + .fvco = { .min = 1050 * MEGA, .max = 2100 * MEGA }, + .m = { .min = 64, .max = 1023 }, + .p = { .min = 1, .max = 4 }, + .s = { .min = 0, .max = 5 }, + .k = { .min = -32768, .max = 32767 }, +}; + static inline struct rzg2l_mipi_dsi * bridge_to_rzg2l_mipi_dsi(struct drm_bridge *bridge) { @@ -193,6 +218,237 @@ static const struct rzg2l_mipi_dsi_timings rzg2l_mipi_dsi_global_timings[] = { }, }; +/** + * struct rzv2h_mipi_dsi_timings - Timing parameter table structure + * + * @hsfreq: Pointer to frequency threshold array + * @len: Number of entries in the hsfreq array + * @base_value: Base register value offset for this timing parameter + * + * Each timing parameter (TCLK*, THS*, etc.) has its own table with + * frequency thresholds and corresponding base register values. + */ +struct rzv2h_mipi_dsi_timings { + const u8 *hsfreq; + u8 len; + u8 base_value; +}; + +/* + * enum rzv2h_dsi_timing_idx - MIPI DSI timing parameter indices + * + * These enums correspond to different MIPI DSI PHY timing parameters. + */ +enum rzv2h_dsi_timing_idx { + TCLKPRPRCTL, + TCLKZEROCTL, + TCLKPOSTCTL, + TCLKTRAILCTL, + THSPRPRCTL, + THSZEROCTL, + THSTRAILCTL, + TLPXCTL, + THSEXITCTL, +}; + +/* + * RZ/V2H(P) Frequency threshold lookup tables for D-PHY timing parameters + * + * - Each array contains frequency thresholds (in units of 10 Mbps), + * taken directly from the table 9.5-4 hardware manual. + * - These thresholds define the frequency ranges for which timing + * register values must be programmed. + * - The actual register value is calculated in + * rzv2h_dphy_find_timings_val(): + * + * register_value = timings->base_value + table_index + * + * Example (TCLKPRPRCTL, from HW manual): + * 0-150 Mbps -> index 0 -> register_value = base + 0 = 0 + 0 = 0 + * 151-260 Mbps -> index 1 -> register_value = base + 1 = 0 + 1 = 1 + * 261-370 Mbps -> index 2 -> register_value = base + 2 = 0 + 2 = 2 + * + * Each of the following arrays corresponds to a specific timing + * parameter (TCLKPRPRCTL, TCLKZEROCTL, TCLKPOSTCTL, etc.). + */ +static const u8 tclkprprctl[] = { + 15, 26, 37, 47, 58, 69, 79, 90, 101, 111, 122, 133, 143, 150, +}; + +static const u8 tclkzeroctl[] = { + 9, 11, 13, 15, 18, 21, 23, 24, 25, 27, 29, 31, 34, 36, 38, + 41, 43, 45, 47, 50, 52, 54, 57, 59, 61, 63, 66, 68, 70, 73, + 75, 77, 79, 82, 84, 86, 89, 91, 93, 95, 98, 100, 102, 105, + 107, 109, 111, 114, 116, 118, 121, 123, 125, 127, 130, 132, + 134, 137, 139, 141, 143, 146, 148, 150, +}; + +static const u8 tclkpostctl[] = { + 8, 21, 34, 48, 61, 74, 88, 101, 114, 128, 141, 150, +}; + +static const u8 tclktrailctl[] = { + 14, 25, 37, 48, 59, 71, 82, 94, 105, 117, 128, 139, 150, +}; + +static const u8 thsprprctl[] = { + 11, 19, 29, 40, 50, 61, 72, 82, 93, 103, 114, 125, 135, 146, 150, +}; + +static const u8 thszeroctl[] = { + 18, 24, 29, 35, 40, 46, 51, 57, 62, 68, 73, 79, 84, 90, + 95, 101, 106, 112, 117, 123, 128, 134, 139, 145, 150, +}; + +static const u8 thstrailctl[] = { + 10, 21, 32, 42, 53, 64, 75, 85, 96, 107, 118, 128, 139, 150, +}; + +static const u8 tlpxctl[] = { + 13, 26, 39, 53, 66, 79, 93, 106, 119, 133, 146, 150, +}; + +static const u8 thsexitctl[] = { + 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, + 95, 103, 111, 119, 127, 135, 143, 150, +}; + +/* + * rzv2h_dsi_timings_tables - main timing parameter lookup table + * Maps timing parameter enum to its frequency table, array length and + * base register offset value. + */ +static const struct rzv2h_mipi_dsi_timings rzv2h_dsi_timings_tables[] = { + [TCLKPRPRCTL] = { + .hsfreq = tclkprprctl, + .len = ARRAY_SIZE(tclkprprctl), + .base_value = 0, + }, + [TCLKZEROCTL] = { + .hsfreq = tclkzeroctl, + .len = ARRAY_SIZE(tclkzeroctl), + .base_value = 2, + }, + [TCLKPOSTCTL] = { + .hsfreq = tclkpostctl, + .len = ARRAY_SIZE(tclkpostctl), + .base_value = 6, + }, + [TCLKTRAILCTL] = { + .hsfreq = tclktrailctl, + .len = ARRAY_SIZE(tclktrailctl), + .base_value = 1, + }, + [THSPRPRCTL] = { + .hsfreq = thsprprctl, + .len = ARRAY_SIZE(thsprprctl), + .base_value = 0, + }, + [THSZEROCTL] = { + .hsfreq = thszeroctl, + .len = ARRAY_SIZE(thszeroctl), + .base_value = 0, + }, + [THSTRAILCTL] = { + .hsfreq = thstrailctl, + .len = ARRAY_SIZE(thstrailctl), + .base_value = 3, + }, + [TLPXCTL] = { + .hsfreq = tlpxctl, + .len = ARRAY_SIZE(tlpxctl), + .base_value = 0, + }, + [THSEXITCTL] = { + .hsfreq = thsexitctl, + .len = ARRAY_SIZE(thsexitctl), + .base_value = 1, + }, +}; + +/** + * rzv2h_dphy_find_ulpsexit - Find ULP Exit timing value based on frequency + * The function maps frequency ranges to ULP exit timing values. + * Thresholds in the local hsfreq[] are expressed in Hz already. + * + * @freq: Input frequency in Hz + * + * Return: ULP exit timing value + */ +static u16 rzv2h_dphy_find_ulpsexit(unsigned long freq) +{ + /* Frequency thresholds in Hz for ULP exit timing selection */ + static const unsigned long hsfreq[] = { + 1953125UL, + 3906250UL, + 7812500UL, + 15625000UL, + }; + /* Corresponding ULP exit timing values for each frequency range */ + static const u16 ulpsexit[] = {49, 98, 195, 391}; + unsigned int i; + + /* Find the appropriate frequency range */ + for (i = 0; i < ARRAY_SIZE(hsfreq); i++) { + if (freq <= hsfreq[i]) + break; + } + + /* If frequency exceeds all thresholds, use the highest range */ + if (i == ARRAY_SIZE(hsfreq)) + i--; + + return ulpsexit[i]; +} + +/** + * rzv2h_dphy_find_timings_val - Find timing parameter value from lookup tables + * @freq: Input frequency in Hz + * @index: Index to select timing parameter table (see enum rzv2h_dsi_timing_idx) + * + * Selects the timing table for the requested parameter, finds the + * frequency range entry and returns the register value to program: + * + * register_value = timings->base_value + table_index + * + * Note: frequency table entries are stored as small integers (units of 10): + * threshold_in_hz = (unsigned long)table_entry * 10 * MEGA + * + * Return: timing register value to be programmed into hardware + */ +static u16 rzv2h_dphy_find_timings_val(unsigned long freq, u8 index) +{ + const struct rzv2h_mipi_dsi_timings *timings; + u16 i; + + /* Get the timing table structure for the requested parameter */ + timings = &rzv2h_dsi_timings_tables[index]; + + /* + * Search through frequency table to find appropriate range + * timings->hsfreq[i] contains frequency values from HW manual + * Convert to Hz by multiplying by 10 * MEGA. + */ + for (i = 0; i < timings->len; i++) { + unsigned long hsfreq = timings->hsfreq[i] * 10 * MEGA; + + if (freq <= hsfreq) + break; + } + + /* If frequency exceeds table range, use the last entry */ + if (i == timings->len) + i--; + + /* + * Calculate final register value: + * - timings->base_value: base value for this timing parameter + * - i: index into frequency table (0-based) + * Combined they give the exact register value to program + */ + return timings->base_value + i; +}; + static void rzg2l_mipi_dsi_phy_write(struct rzg2l_mipi_dsi *dsi, u32 reg, u32 data) { iowrite32(data, dsi->mmio + dsi->info->phy_reg_offset + reg); @@ -317,6 +573,169 @@ static int rzg2l_dphy_conf_clks(struct rzg2l_mipi_dsi *dsi, unsigned long mode_f return 0; } +static unsigned int rzv2h_dphy_mode_clk_check(struct rzg2l_mipi_dsi *dsi, + unsigned long mode_freq) +{ + u64 hsfreq_millihz, mode_freq_hz, mode_freq_millihz; + struct rzv2h_pll_div_pars cpg_dsi_parameters; + struct rzv2h_pll_pars dsi_parameters; + bool parameters_found; + unsigned int bpp; + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + mode_freq_hz = mul_u32_u32(mode_freq, KILO); + mode_freq_millihz = mode_freq_hz * MILLI; + parameters_found = + rzv2h_get_pll_divs_pars(dsi->info->cpg_plldsi.limits[0], + &cpg_dsi_parameters, + dsi->info->cpg_plldsi.table, + dsi->info->cpg_plldsi.table_size, + mode_freq_millihz); + if (!parameters_found) + return MODE_CLOCK_RANGE; + + hsfreq_millihz = DIV_ROUND_CLOSEST_ULL(cpg_dsi_parameters.div.freq_millihz * bpp, + dsi->lanes); + parameters_found = rzv2h_get_pll_pars(&rzv2h_plldsi_div_limits, + &dsi_parameters, hsfreq_millihz); + if (!parameters_found) + return MODE_CLOCK_RANGE; + + if (abs(dsi_parameters.error_millihz) >= 500) + return MODE_CLOCK_RANGE; + + memcpy(&dsi->mode_calc.dsi_parameters, &dsi_parameters, sizeof(dsi_parameters)); + dsi->mode_calc.mode_freq_khz = mode_freq; + + return MODE_OK; +} + +static int rzv2h_dphy_conf_clks(struct rzg2l_mipi_dsi *dsi, unsigned long mode_freq, + u64 *hsfreq_millihz) +{ + struct rzv2h_pll_pars *dsi_parameters = &dsi->mode_calc.dsi_parameters; + unsigned long status; + + if (dsi->mode_calc.mode_freq_khz != mode_freq) { + status = rzv2h_dphy_mode_clk_check(dsi, mode_freq); + if (status != MODE_OK) { + dev_err(dsi->dev, "No PLL parameters found for mode clk %lu\n", + mode_freq); + return -EINVAL; + } + } + + *hsfreq_millihz = dsi_parameters->freq_millihz; + + return 0; +} + +static int rzv2h_mipi_dsi_dphy_init(struct rzg2l_mipi_dsi *dsi, + u64 hsfreq_millihz) +{ + struct rzv2h_pll_pars *dsi_parameters = &dsi->mode_calc.dsi_parameters; + unsigned long lpclk_rate = clk_get_rate(dsi->lpclk); + u32 phytclksetr, phythssetr, phytlpxsetr, phycr; + struct rzg2l_mipi_dsi_timings dphy_timings; + u16 ulpsexit; + u64 hsfreq; + + hsfreq = DIV_ROUND_CLOSEST_ULL(hsfreq_millihz, MILLI); + + if (dsi_parameters->freq_millihz != hsfreq_millihz && + !rzv2h_get_pll_pars(&rzv2h_plldsi_div_limits, dsi_parameters, + hsfreq_millihz)) { + dev_err(dsi->dev, "No PLL parameters found for HSFREQ %lluHz\n", hsfreq); + return -EINVAL; + } + + dphy_timings.tclk_trail = + rzv2h_dphy_find_timings_val(hsfreq, TCLKTRAILCTL); + dphy_timings.tclk_post = + rzv2h_dphy_find_timings_val(hsfreq, TCLKPOSTCTL); + dphy_timings.tclk_zero = + rzv2h_dphy_find_timings_val(hsfreq, TCLKZEROCTL); + dphy_timings.tclk_prepare = + rzv2h_dphy_find_timings_val(hsfreq, TCLKPRPRCTL); + dphy_timings.ths_exit = + rzv2h_dphy_find_timings_val(hsfreq, THSEXITCTL); + dphy_timings.ths_trail = + rzv2h_dphy_find_timings_val(hsfreq, THSTRAILCTL); + dphy_timings.ths_zero = + rzv2h_dphy_find_timings_val(hsfreq, THSZEROCTL); + dphy_timings.ths_prepare = + rzv2h_dphy_find_timings_val(hsfreq, THSPRPRCTL); + dphy_timings.tlpx = + rzv2h_dphy_find_timings_val(hsfreq, TLPXCTL); + ulpsexit = rzv2h_dphy_find_ulpsexit(lpclk_rate); + + phytclksetr = FIELD_PREP(PHYTCLKSETR_TCLKTRAILCTL, dphy_timings.tclk_trail) | + FIELD_PREP(PHYTCLKSETR_TCLKPOSTCTL, dphy_timings.tclk_post) | + FIELD_PREP(PHYTCLKSETR_TCLKZEROCTL, dphy_timings.tclk_zero) | + FIELD_PREP(PHYTCLKSETR_TCLKPRPRCTL, dphy_timings.tclk_prepare); + phythssetr = FIELD_PREP(PHYTHSSETR_THSEXITCTL, dphy_timings.ths_exit) | + FIELD_PREP(PHYTHSSETR_THSTRAILCTL, dphy_timings.ths_trail) | + FIELD_PREP(PHYTHSSETR_THSZEROCTL, dphy_timings.ths_zero) | + FIELD_PREP(PHYTHSSETR_THSPRPRCTL, dphy_timings.ths_prepare); + phytlpxsetr = rzg2l_mipi_dsi_phy_read(dsi, PHYTLPXSETR) & ~PHYTLPXSETR_TLPXCTL; + phytlpxsetr |= FIELD_PREP(PHYTLPXSETR_TLPXCTL, dphy_timings.tlpx); + phycr = rzg2l_mipi_dsi_phy_read(dsi, PHYCR) & ~GENMASK(9, 0); + phycr |= FIELD_PREP(PHYCR_ULPSEXIT, ulpsexit); + + /* Setting all D-PHY Timings Registers */ + rzg2l_mipi_dsi_phy_write(dsi, PHYTCLKSETR, phytclksetr); + rzg2l_mipi_dsi_phy_write(dsi, PHYTHSSETR, phythssetr); + rzg2l_mipi_dsi_phy_write(dsi, PHYTLPXSETR, phytlpxsetr); + rzg2l_mipi_dsi_phy_write(dsi, PHYCR, phycr); + + rzg2l_mipi_dsi_phy_write(dsi, PLLCLKSET0R, + FIELD_PREP(PLLCLKSET0R_PLL_S, dsi_parameters->s) | + FIELD_PREP(PLLCLKSET0R_PLL_P, dsi_parameters->p) | + FIELD_PREP(PLLCLKSET0R_PLL_M, dsi_parameters->m)); + rzg2l_mipi_dsi_phy_write(dsi, PLLCLKSET1R, + FIELD_PREP(PLLCLKSET1R_PLL_K, dsi_parameters->k)); + + /* + * From RZ/V2H HW manual (Rev.1.20) section 9.5.3 Operation, + * (C) After write to D-PHY registers we need to wait for more than 1 x tp + * + * tp = 1 / (PLLREFCLK / PLLCLKSET0R.PLL_P) + * PLLREFCLK = 24MHz + * PLLCLKSET0R.PLL_P = {1, 2, 3, 4} + * + * To handle all the cases lets use PLLCLKSET0R.PLL_P = 4 + * tp = 1 / (24MHz / 4) = 1 / 6MHz = 166.67ns + */ + ndelay(200); + + rzg2l_mipi_dsi_phy_write(dsi, PLLENR, PLLENR_PLLEN); + /* + * From RZ/V2H HW manual (Rev.1.20) section 9.5.3 Operation, + * (D) After write to PLLENR.PLLEN we need to wait for more than 3000 x tp + * + * 3000 x tp = 3000 x 0.16667 ns = 500.01 microseconds + */ + usleep_range(510, 520); + + return 0; +} + +static void rzv2h_mipi_dsi_dphy_startup_late_init(struct rzg2l_mipi_dsi *dsi) +{ + /* + * From RZ/V2H HW manual (Rev.1.20) section 9.5.3 Operation, + * (E) After write to TXSETR we need to wait for more than 200 microseconds + * and then write to PHYRSTR + */ + usleep_range(210, 220); + rzg2l_mipi_dsi_phy_write(dsi, PHYRSTR, PHYRSTR_PHYMRSTN); +} + +static void rzv2h_mipi_dsi_dphy_exit(struct rzg2l_mipi_dsi *dsi) +{ + rzg2l_mipi_dsi_phy_write(dsi, PLLENR, 0); +} + static int rzg2l_mipi_dsi_startup(struct rzg2l_mipi_dsi *dsi, const struct drm_display_mode *mode) { @@ -429,6 +848,9 @@ static void rzg2l_mipi_dsi_set_display_timing(struct rzg2l_mipi_dsi *dsi, case 18: vich1ppsetr = VICH1PPSETR_DT_RGB18; break; + case 16: + vich1ppsetr = VICH1PPSETR_DT_RGB16; + break; } if ((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) && @@ -979,6 +1401,10 @@ static int rzg2l_mipi_dsi_probe(struct platform_device *pdev) if (IS_ERR(dsi->vclk)) return PTR_ERR(dsi->vclk); + dsi->lpclk = devm_clk_get(dsi->dev, "lpclk"); + if (IS_ERR(dsi->lpclk)) + return PTR_ERR(dsi->lpclk); + dsi->rstc = devm_reset_control_get_optional_exclusive(dsi->dev, "rst"); if (IS_ERR(dsi->rstc)) return dev_err_probe(dsi->dev, PTR_ERR(dsi->rstc), @@ -1051,6 +1477,32 @@ static void rzg2l_mipi_dsi_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } +RZV2H_CPG_PLL_DSI_LIMITS(rzv2h_cpg_pll_dsi_limits); + +static const struct rzv2h_pll_limits *rzv2h_plldsi_limits[] = { + &rzv2h_cpg_pll_dsi_limits, +}; + +static const u8 rzv2h_cpg_div_table[] = { + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, +}; + +static const struct rzg2l_mipi_dsi_hw_info rzv2h_mipi_dsi_info = { + .dphy_init = rzv2h_mipi_dsi_dphy_init, + .dphy_startup_late_init = rzv2h_mipi_dsi_dphy_startup_late_init, + .dphy_exit = rzv2h_mipi_dsi_dphy_exit, + .dphy_mode_clk_check = rzv2h_dphy_mode_clk_check, + .dphy_conf_clks = rzv2h_dphy_conf_clks, + .cpg_plldsi.limits = rzv2h_plldsi_limits, + .cpg_plldsi.table = rzv2h_cpg_div_table, + .cpg_plldsi.table_size = ARRAY_SIZE(rzv2h_cpg_div_table), + .phy_reg_offset = 0x10000, + .link_reg_offset = 0, + .min_dclk = 5440, + .max_dclk = 187500, + .features = RZ_MIPI_DSI_FEATURE_16BPP, +}; + static const struct rzg2l_mipi_dsi_hw_info rzg2l_mipi_dsi_info = { .dphy_init = rzg2l_mipi_dsi_dphy_init, .dphy_exit = rzg2l_mipi_dsi_dphy_exit, @@ -1061,6 +1513,7 @@ static const struct rzg2l_mipi_dsi_hw_info rzg2l_mipi_dsi_info = { }; static const struct of_device_id rzg2l_mipi_dsi_of_table[] = { + { .compatible = "renesas,r9a09g057-mipi-dsi", .data = &rzv2h_mipi_dsi_info, }, { .compatible = "renesas,rzg2l-mipi-dsi", .data = &rzg2l_mipi_dsi_info, }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h index d8082a87d874..2bef20566648 100644 --- a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h @@ -40,6 +40,39 @@ #define DSIDPHYTIM3_THS_TRAIL(x) ((x) << 8) #define DSIDPHYTIM3_THS_ZERO(x) ((x) << 0) +/* RZ/V2H DPHY Registers */ +#define PLLENR 0x000 +#define PLLENR_PLLEN BIT(0) + +#define PHYRSTR 0x004 +#define PHYRSTR_PHYMRSTN BIT(0) + +#define PLLCLKSET0R 0x010 +#define PLLCLKSET0R_PLL_S GENMASK(2, 0) +#define PLLCLKSET0R_PLL_P GENMASK(13, 8) +#define PLLCLKSET0R_PLL_M GENMASK(25, 16) + +#define PLLCLKSET1R 0x014 +#define PLLCLKSET1R_PLL_K GENMASK(15, 0) + +#define PHYTCLKSETR 0x020 +#define PHYTCLKSETR_TCLKTRAILCTL GENMASK(7, 0) +#define PHYTCLKSETR_TCLKPOSTCTL GENMASK(15, 8) +#define PHYTCLKSETR_TCLKZEROCTL GENMASK(23, 16) +#define PHYTCLKSETR_TCLKPRPRCTL GENMASK(31, 24) + +#define PHYTHSSETR 0x024 +#define PHYTHSSETR_THSEXITCTL GENMASK(7, 0) +#define PHYTHSSETR_THSTRAILCTL GENMASK(15, 8) +#define PHYTHSSETR_THSZEROCTL GENMASK(23, 16) +#define PHYTHSSETR_THSPRPRCTL GENMASK(31, 24) + +#define PHYTLPXSETR 0x028 +#define PHYTLPXSETR_TLPXCTL GENMASK(7, 0) + +#define PHYCR 0x030 +#define PHYCR_ULPSEXIT GENMASK(9, 0) + /* --------------------------------------------------------*/ /* Link Status Register */ @@ -130,6 +163,7 @@ /* Video-Input Channel 1 Pixel Packet Set Register */ #define VICH1PPSETR 0x420 +#define VICH1PPSETR_DT_RGB16 (0x0e << 16) #define VICH1PPSETR_DT_RGB18 (0x1e << 16) #define VICH1PPSETR_DT_RGB18_LS (0x2e << 16) #define VICH1PPSETR_DT_RGB24 (0x3e << 16) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 727cdf768161..0dc1eb5d2ae3 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -467,6 +467,19 @@ static const struct dw_hdmi_plat_data rk3328_hdmi_drv_data = { .use_drm_infoframe = true, }; +static struct rockchip_hdmi_chip_data rk3368_chip_data = { + .lcdsel_grf_reg = -1, +}; + +static const struct dw_hdmi_plat_data rk3368_hdmi_drv_data = { + .mode_valid = dw_hdmi_rockchip_mode_valid, + .mpll_cfg = rockchip_mpll_cfg, + .cur_ctr = rockchip_cur_ctr, + .phy_config = rockchip_phy_config, + .phy_data = &rk3368_chip_data, + .use_drm_infoframe = true, +}; + static struct rockchip_hdmi_chip_data rk3399_chip_data = { .lcdsel_grf_reg = RK3399_GRF_SOC_CON20, .lcdsel_big = FIELD_PREP_WM16_CONST(RK3399_HDMI_LCDC_SEL, 0), @@ -507,6 +520,9 @@ static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = { { .compatible = "rockchip,rk3328-dw-hdmi", .data = &rk3328_hdmi_drv_data }, + { .compatible = "rockchip,rk3368-dw-hdmi", + .data = &rk3368_hdmi_drv_data + }, { .compatible = "rockchip,rk3399-dw-hdmi", .data = &rk3399_hdmi_drv_data }, diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c index 1d4f1b822e7b..bd7936c03da2 100644 --- a/drivers/gpu/drm/scheduler/sched_main.c +++ b/drivers/gpu/drm/scheduler/sched_main.c @@ -344,7 +344,7 @@ drm_sched_rq_select_entity_fifo(struct drm_gpu_scheduler *sched, */ static void drm_sched_run_job_queue(struct drm_gpu_scheduler *sched) { - if (!READ_ONCE(sched->pause_submit)) + if (!drm_sched_is_stopped(sched)) queue_work(sched->submit_wq, &sched->work_run_job); } @@ -354,7 +354,7 @@ static void drm_sched_run_job_queue(struct drm_gpu_scheduler *sched) */ static void drm_sched_run_free_queue(struct drm_gpu_scheduler *sched) { - if (!READ_ONCE(sched->pause_submit)) + if (!drm_sched_is_stopped(sched)) queue_work(sched->submit_wq, &sched->work_free_job); } @@ -729,7 +729,9 @@ EXPORT_SYMBOL(drm_sched_start); * * Drivers can still save and restore their state for recovery operations, but * we shouldn't make this a general scheduler feature around the dma_fence - * interface. + * interface. The suggested driver-side replacement is to use + * drm_sched_for_each_pending_job() after stopping the scheduler and implement + * their own recovery operations. */ void drm_sched_resubmit_jobs(struct drm_gpu_scheduler *sched) { @@ -1567,3 +1569,35 @@ void drm_sched_wqueue_start(struct drm_gpu_scheduler *sched) queue_work(sched->submit_wq, &sched->work_free_job); } EXPORT_SYMBOL(drm_sched_wqueue_start); + +/** + * drm_sched_is_stopped() - Checks whether drm_sched is stopped + * @sched: DRM scheduler + * + * Return: true if sched is stopped, false otherwise + */ +bool drm_sched_is_stopped(struct drm_gpu_scheduler *sched) +{ + return READ_ONCE(sched->pause_submit); +} +EXPORT_SYMBOL(drm_sched_is_stopped); + +/** + * drm_sched_job_is_signaled() - DRM scheduler job is signaled + * @job: DRM scheduler job + * + * Determine if DRM scheduler job is signaled. DRM scheduler should be stopped + * to obtain a stable snapshot of state. Both parent fence (hardware fence) and + * finished fence (software fence) are checked to determine signaling state. + * + * Return: true if job is signaled, false otherwise + */ +bool drm_sched_job_is_signaled(struct drm_sched_job *job) +{ + struct drm_sched_fence *s_fence = job->s_fence; + + WARN_ON(!drm_sched_is_stopped(job->sched)); + return (s_fence->parent && dma_fence_is_signaled(s_fence->parent)) || + dma_fence_is_signaled(&s_fence->finished); +} +EXPORT_SYMBOL(drm_sched_job_is_signaled); diff --git a/drivers/gpu/drm/sitronix/Kconfig b/drivers/gpu/drm/sitronix/Kconfig index 6de7d92d9b74..41a428ef8295 100644 --- a/drivers/gpu/drm/sitronix/Kconfig +++ b/drivers/gpu/drm/sitronix/Kconfig @@ -1,16 +1,44 @@ -config DRM_ST7571_I2C - tristate "DRM support for Sitronix ST7571 display panels (I2C)" - depends on DRM && I2C && MMU +config DRM_ST7571 + tristate "DRM support for Sitronix ST7567/ST7571 display panels" + depends on DRM && MMU select DRM_CLIENT_SELECTION select DRM_GEM_SHMEM_HELPER select DRM_KMS_HELPER - select REGMAP_I2C select VIDEOMODE_HELPERS help - DRM driver for Sitronix ST7571 panels controlled over I2C. + Sitronix ST7571 is a driver and controller for 4-level gray + scale and monochrome dot matrix LCD panels. + + DRM driver for Sitronix ST7567/ST7571 panels. + This is only the core driver, a driver for the appropriate bus + transport in your chip also must be selected. + + if M is selected the module will be called st7571. + +config DRM_ST7571_I2C + tristate "DRM support for Sitronix ST7567/ST7571 display panels (I2C)" + depends on DRM_ST7571 && I2C + select REGMAP + help + Sitronix ST7571 is a driver and controller for 4-level gray + scale and monochrome dot matrix LCD panels. + + DRM driver for Sitronix ST7565/ST7571 panels connected via I2C bus. if M is selected the module will be called st7571-i2c. +config DRM_ST7571_SPI + tristate "DRM support for Sitronix ST7567/ST7571 display panels (SPI)" + depends on DRM_ST7571 && SPI + select REGMAP_SPI + help + Sitronix ST7571 is a driver and controller for 4-level gray + scale and monochrome dot matrix LCD panels. + + DRM driver for Sitronix ST7565/ST7571 panels connected via SPI bus. + + if M is selected the module will be called st7571-spi. + config DRM_ST7586 tristate "DRM support for Sitronix ST7586 display panels" depends on DRM && SPI @@ -40,3 +68,13 @@ config DRM_ST7735R If M is selected the module will be called st7735r. +config DRM_ST7920 + tristate "DRM support for Sitronix ST7920 LCD displays" + depends on DRM && SPI && MMU + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + select REGMAP_SPI + help + DRM driver for the ST7920 Sitronix LCD controllers. + + If M is selected the module will be called st7920. diff --git a/drivers/gpu/drm/sitronix/Makefile b/drivers/gpu/drm/sitronix/Makefile index bd139e5a6995..d03beff37628 100644 --- a/drivers/gpu/drm/sitronix/Makefile +++ b/drivers/gpu/drm/sitronix/Makefile @@ -1,3 +1,6 @@ +obj-$(CONFIG_DRM_ST7571) += st7571.o obj-$(CONFIG_DRM_ST7571_I2C) += st7571-i2c.o +obj-$(CONFIG_DRM_ST7571_SPI) += st7571-spi.o obj-$(CONFIG_DRM_ST7586) += st7586.o obj-$(CONFIG_DRM_ST7735R) += st7735r.o +obj-$(CONFIG_DRM_ST7920) += st7920.o diff --git a/drivers/gpu/drm/sitronix/st7571-i2c.c b/drivers/gpu/drm/sitronix/st7571-i2c.c index 4e73c8b415d6..44bc94be33d6 100644 --- a/drivers/gpu/drm/sitronix/st7571-i2c.c +++ b/drivers/gpu/drm/sitronix/st7571-i2c.c @@ -1,131 +1,18 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Driver for Sitronix ST7571, a 4 level gray scale dot matrix LCD controller + * Driver for Sitronix ST7571 connected via I2C bus. * * Copyright (C) 2025 Marcus Folkesson <marcus.folkesson@gmail.com> */ -#include <linux/bitfield.h> -#include <linux/delay.h> -#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/module.h> #include <linux/regmap.h> -#include <drm/clients/drm_client_setup.h> -#include <drm/drm_atomic.h> -#include <drm/drm_atomic_helper.h> -#include <drm/drm_connector.h> -#include <drm/drm_crtc_helper.h> -#include <drm/drm_damage_helper.h> -#include <drm/drm_drv.h> -#include <drm/drm_encoder.h> -#include <drm/drm_fb_helper.h> -#include <drm/drm_fbdev_shmem.h> -#include <drm/drm_fourcc.h> -#include <drm/drm_framebuffer.h> -#include <drm/drm_gem_atomic_helper.h> -#include <drm/drm_gem_framebuffer_helper.h> -#include <drm/drm_gem_shmem_helper.h> -#include <drm/drm_modeset_helper_vtables.h> -#include <drm/drm_module.h> -#include <drm/drm_plane.h> -#include <drm/drm_probe_helper.h> +#include "st7571.h" -#include <video/display_timing.h> -#include <video/of_display_timing.h> - -#define ST7571_COMMAND_MODE (0x00) -#define ST7571_DATA_MODE (0x40) - -/* Normal mode command set */ -#define ST7571_DISPLAY_OFF (0xae) -#define ST7571_DISPLAY_ON (0xaf) -#define ST7571_OSC_ON (0xab) -#define ST7571_SET_COLUMN_LSB(c) (0x00 | FIELD_PREP(GENMASK(3, 0), (c))) -#define ST7571_SET_COLUMN_MSB(c) (0x10 | FIELD_PREP(GENMASK(2, 0), (c) >> 4)) -#define ST7571_SET_COM0_LSB(x) (FIELD_PREP(GENMASK(6, 0), (x))) -#define ST7571_SET_COM0_MSB (0x44) -#define ST7571_SET_COM_SCAN_DIR(d) (0xc0 | FIELD_PREP(GENMASK(3, 3), (d))) -#define ST7571_SET_CONTRAST_LSB(c) (FIELD_PREP(GENMASK(5, 0), (c))) -#define ST7571_SET_CONTRAST_MSB (0x81) -#define ST7571_SET_DISPLAY_DUTY_LSB(d) (FIELD_PREP(GENMASK(7, 0), (d))) -#define ST7571_SET_DISPLAY_DUTY_MSB (0x48) -#define ST7571_SET_ENTIRE_DISPLAY_ON(p) (0xa4 | FIELD_PREP(GENMASK(0, 0), (p))) -#define ST7571_SET_LCD_BIAS(b) (0x50 | FIELD_PREP(GENMASK(2, 0), (b))) -#define ST7571_SET_MODE_LSB(m) (FIELD_PREP(GENMASK(7, 2), (m))) -#define ST7571_SET_MODE_MSB (0x38) -#define ST7571_SET_PAGE(p) (0xb0 | FIELD_PREP(GENMASK(3, 0), (p))) -#define ST7571_SET_POWER(p) (0x28 | FIELD_PREP(GENMASK(2, 0), (p))) -#define ST7571_SET_REGULATOR_REG(r) (0x20 | FIELD_PREP(GENMASK(2, 0), (r))) -#define ST7571_SET_REVERSE(r) (0xa6 | FIELD_PREP(GENMASK(0, 0), (r))) -#define ST7571_SET_SEG_SCAN_DIR(d) (0xa0 | FIELD_PREP(GENMASK(0, 0), (d))) -#define ST7571_SET_START_LINE_LSB(l) (FIELD_PREP(GENMASK(6, 0), (l))) -#define ST7571_SET_START_LINE_MSB (0x40) - -/* Extension command set 3 */ -#define ST7571_COMMAND_SET_3 (0x7b) -#define ST7571_SET_COLOR_MODE(c) (0x10 | FIELD_PREP(GENMASK(0, 0), (c))) -#define ST7571_COMMAND_SET_NORMAL (0x00) - -/* ST7567 commands */ -#define ST7567_SET_LCD_BIAS(m) (0xa2 | FIELD_PREP(GENMASK(0, 0), (m))) - -#define ST7571_PAGE_HEIGHT 8 - -#define DRIVER_NAME "st7571" -#define DRIVER_DESC "ST7571 DRM driver" -#define DRIVER_MAJOR 1 -#define DRIVER_MINOR 0 - -enum st7571_color_mode { - ST7571_COLOR_MODE_GRAY = 0, - ST7571_COLOR_MODE_BLACKWHITE = 1, -}; - -struct st7571_device; - -struct st7571_panel_constraints { - u32 min_nlines; - u32 max_nlines; - u32 min_ncols; - u32 max_ncols; - bool support_grayscale; -}; - -struct st7571_panel_data { - int (*init)(struct st7571_device *st7571); - int (*parse_dt)(struct st7571_device *st7571); - struct st7571_panel_constraints constraints; -}; - -struct st7571_panel_format { - void (*prepare_buffer)(struct st7571_device *st7571, - const struct iosys_map *vmap, - struct drm_framebuffer *fb, - struct drm_rect *rect, - struct drm_format_conv_state *fmtcnv_state); - int (*update_rect)(struct drm_framebuffer *fb, struct drm_rect *rect); - enum st7571_color_mode mode; - const u8 nformats; - const u32 formats[]; -}; - -struct st7571_device { - struct drm_device dev; - - struct drm_plane primary_plane; - struct drm_crtc crtc; - struct drm_encoder encoder; - struct drm_connector connector; - - struct drm_display_mode mode; - - const struct st7571_panel_format *pformat; - const struct st7571_panel_data *pdata; +struct st7571_i2c_transport { struct i2c_client *client; - struct gpio_desc *reset; - struct regmap *regmap; /* * Depending on the hardware design, the acknowledge signal may be hard to @@ -149,42 +36,21 @@ struct st7571_device { * */ bool ignore_nak; - - bool grayscale; - bool inverted; - u32 height_mm; - u32 width_mm; - u32 startline; - u32 nlines; - u32 ncols; - u32 bpp; - - /* Intermediate buffer in LCD friendly format */ - u8 *hwbuf; - - /* Row of (transformed) pixels ready to be written to the display */ - u8 *row; }; -static inline struct st7571_device *drm_to_st7571(struct drm_device *dev) -{ - return container_of(dev, struct st7571_device, dev); -} - -static int st7571_regmap_write(void *context, const void *data, size_t count) +static int st7571_i2c_regmap_write(void *context, const void *data, size_t count) { - struct i2c_client *client = context; - struct st7571_device *st7571 = i2c_get_clientdata(client); + struct st7571_i2c_transport *t = context; int ret; struct i2c_msg msg = { - .addr = st7571->client->addr, - .flags = st7571->ignore_nak ? I2C_M_IGNORE_NAK : 0, + .addr = t->client->addr, + .flags = t->ignore_nak ? I2C_M_IGNORE_NAK : 0, .len = count, .buf = (u8 *)data }; - ret = i2c_transfer(st7571->client->adapter, &msg, 1); + ret = i2c_transfer(t->client->adapter, &msg, 1); /* * Unfortunately, there is no way to check if the transfer failed because of @@ -192,770 +58,41 @@ static int st7571_regmap_write(void *context, const void *data, size_t count) * * However, if the transfer fails and ignore_nak is set, we know it is an error. */ - if (ret < 0 && st7571->ignore_nak) + if (ret < 0 && t->ignore_nak) return ret; return 0; } /* The st7571 driver does not read registers but regmap expects a .read */ -static int st7571_regmap_read(void *context, const void *reg_buf, - size_t reg_size, void *val_buf, size_t val_size) +static int st7571_i2c_regmap_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, size_t val_size) { return -EOPNOTSUPP; } -static int st7571_send_command_list(struct st7571_device *st7571, - const u8 *cmd_list, size_t len) -{ - int ret; - - for (int i = 0; i < len; i++) { - ret = regmap_write(st7571->regmap, ST7571_COMMAND_MODE, cmd_list[i]); - if (ret < 0) - return ret; - } - - return ret; -} - -static inline u8 st7571_transform_xy(const char *p, int x, int y, u8 bpp) -{ - int xrest = x % 8; - u8 result = 0; - u8 row_len = 16 * bpp; - - /* - * Transforms an (x, y) pixel coordinate into a vertical 8-bit - * column from the framebuffer. It calculates the corresponding byte in the - * framebuffer, extracts the bit at the given x position across 8 consecutive - * rows, and packs those bits into a single byte. - * - * Return an 8-bit value representing a vertical column of pixels. - */ - x = x / 8; - y = (y / 8) * 8; - - for (int i = 0; i < 8; i++) { - int row_idx = y + i; - u8 byte = p[row_idx * row_len + x]; - u8 bit = (byte >> xrest) & 1; - - result |= (bit << i); - } - - return result; -} - -static int st7571_set_position(struct st7571_device *st7571, int x, int y) -{ - u8 cmd_list[] = { - ST7571_SET_COLUMN_LSB(x), - ST7571_SET_COLUMN_MSB(x), - ST7571_SET_PAGE(y / ST7571_PAGE_HEIGHT), - }; - - return st7571_send_command_list(st7571, cmd_list, ARRAY_SIZE(cmd_list)); -} - -static int st7571_fb_clear_screen(struct st7571_device *st7571) -{ - u32 npixels = st7571->ncols * round_up(st7571->nlines, ST7571_PAGE_HEIGHT) * st7571->bpp; - char pixelvalue = 0x00; - - st7571_set_position(st7571, 0, 0); - for (int i = 0; i < npixels; i++) - regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, &pixelvalue, 1); - - return 0; -} - -static void st7571_prepare_buffer_monochrome(struct st7571_device *st7571, - const struct iosys_map *vmap, - struct drm_framebuffer *fb, - struct drm_rect *rect, - struct drm_format_conv_state *fmtcnv_state) -{ - unsigned int dst_pitch; - struct iosys_map dst; - u32 size; - - switch (fb->format->format) { - case DRM_FORMAT_XRGB8888: - dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 8); - iosys_map_set_vaddr(&dst, st7571->hwbuf); - - drm_fb_xrgb8888_to_mono(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); - break; - - case DRM_FORMAT_R1: - size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8; - memcpy(st7571->hwbuf, vmap->vaddr, size); - break; - } -} - -static void st7571_prepare_buffer_grayscale(struct st7571_device *st7571, - const struct iosys_map *vmap, - struct drm_framebuffer *fb, - struct drm_rect *rect, - struct drm_format_conv_state *fmtcnv_state) -{ - u32 size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8; - unsigned int dst_pitch; - struct iosys_map dst; - - switch (fb->format->format) { - case DRM_FORMAT_XRGB8888: - dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 4); - iosys_map_set_vaddr(&dst, st7571->hwbuf); - - drm_fb_xrgb8888_to_gray2(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); - break; - - case DRM_FORMAT_R1: - size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8; - memcpy(st7571->hwbuf, vmap->vaddr, size); - break; - - case DRM_FORMAT_R2: - size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 4; - memcpy(st7571->hwbuf, vmap->vaddr, size); - break; - } -} - -static int st7571_fb_update_rect_monochrome(struct drm_framebuffer *fb, struct drm_rect *rect) -{ - struct st7571_device *st7571 = drm_to_st7571(fb->dev); - char *row = st7571->row; - - /* Align y to display page boundaries */ - rect->y1 = round_down(rect->y1, ST7571_PAGE_HEIGHT); - rect->y2 = min_t(unsigned int, round_up(rect->y2, ST7571_PAGE_HEIGHT), st7571->nlines); - - for (int y = rect->y1; y < rect->y2; y += ST7571_PAGE_HEIGHT) { - for (int x = rect->x1; x < rect->x2; x++) - row[x] = st7571_transform_xy(st7571->hwbuf, x, y, 1); - - st7571_set_position(st7571, rect->x1, y); - - /* TODO: Investige why we can't write multiple bytes at once */ - for (int x = rect->x1; x < rect->x2; x++) - regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1); - } - - return 0; -} - -static int st7571_fb_update_rect_grayscale(struct drm_framebuffer *fb, struct drm_rect *rect) -{ - struct st7571_device *st7571 = drm_to_st7571(fb->dev); - u32 format = fb->format->format; - char *row = st7571->row; - int x1; - int x2; - - /* Align y to display page boundaries */ - rect->y1 = round_down(rect->y1, ST7571_PAGE_HEIGHT); - rect->y2 = min_t(unsigned int, round_up(rect->y2, ST7571_PAGE_HEIGHT), st7571->nlines); - - switch (format) { - case DRM_FORMAT_R1: - x1 = rect->x1 * 1; - x2 = rect->x2 * 1; - break; - case DRM_FORMAT_R2: - fallthrough; - case DRM_FORMAT_XRGB8888: - x1 = rect->x1 * 2; - x2 = rect->x2 * 2; - break; - } - - for (int y = rect->y1; y < rect->y2; y += ST7571_PAGE_HEIGHT) { - for (int x = x1; x < x2; x++) - row[x] = st7571_transform_xy(st7571->hwbuf, x, y, 2); - - st7571_set_position(st7571, rect->x1, y); - - /* TODO: Investige why we can't write multiple bytes at once */ - for (int x = x1; x < x2; x++) { - regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1); - - /* - * As the display supports grayscale, all pixels must be written as two bits - * even if the format is monochrome. - * - * The bit values maps to the following grayscale: - * 0 0 = Black - * 0 1 = Dark gray - * 1 0 = Light gray - * 1 1 = White - * - * For monochrome formats, write the same value twice to get - * either a black or white pixel. - */ - if (format == DRM_FORMAT_R1) - regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1); - } - } - - return 0; -} - -static int st7571_connector_get_modes(struct drm_connector *conn) -{ - struct st7571_device *st7571 = drm_to_st7571(conn->dev); - - return drm_connector_helper_get_modes_fixed(conn, &st7571->mode); -} - -static const struct drm_connector_helper_funcs st7571_connector_helper_funcs = { - .get_modes = st7571_connector_get_modes, -}; - -static const struct st7571_panel_format st7571_monochrome = { - .prepare_buffer = st7571_prepare_buffer_monochrome, - .update_rect = st7571_fb_update_rect_monochrome, - .mode = ST7571_COLOR_MODE_BLACKWHITE, - .formats = { - DRM_FORMAT_XRGB8888, - DRM_FORMAT_R1, - }, - .nformats = 2, -}; - -static const struct st7571_panel_format st7571_grayscale = { - .prepare_buffer = st7571_prepare_buffer_grayscale, - .update_rect = st7571_fb_update_rect_grayscale, - .mode = ST7571_COLOR_MODE_GRAY, - .formats = { - DRM_FORMAT_XRGB8888, - DRM_FORMAT_R1, - DRM_FORMAT_R2, - }, - .nformats = 3, -}; - -static const u64 st7571_primary_plane_fmtmods[] = { - DRM_FORMAT_MOD_LINEAR, - DRM_FORMAT_MOD_INVALID -}; - -static int st7571_primary_plane_helper_atomic_check(struct drm_plane *plane, - struct drm_atomic_state *state) -{ - struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); - struct drm_crtc *new_crtc = new_plane_state->crtc; - struct drm_crtc_state *new_crtc_state = NULL; - - if (new_crtc) - new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc); - - return drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state, - DRM_PLANE_NO_SCALING, - DRM_PLANE_NO_SCALING, - false, false); -} - -static void st7571_primary_plane_helper_atomic_update(struct drm_plane *plane, - struct drm_atomic_state *state) -{ - struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); - struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); - struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); - struct drm_framebuffer *fb = plane_state->fb; - struct drm_atomic_helper_damage_iter iter; - struct drm_device *dev = plane->dev; - struct drm_rect damage; - struct st7571_device *st7571 = drm_to_st7571(plane->dev); - int ret, idx; - - if (!fb) - return; /* no framebuffer; plane is disabled */ - - ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); - if (ret) - return; - - if (!drm_dev_enter(dev, &idx)) - goto out_drm_gem_fb_end_cpu_access; - - drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); - drm_atomic_for_each_plane_damage(&iter, &damage) { - st7571->pformat->prepare_buffer(st7571, - &shadow_plane_state->data[0], - fb, &damage, - &shadow_plane_state->fmtcnv_state); - - st7571->pformat->update_rect(fb, &damage); - } - - drm_dev_exit(idx); - -out_drm_gem_fb_end_cpu_access: - drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); -} - -static void st7571_primary_plane_helper_atomic_disable(struct drm_plane *plane, - struct drm_atomic_state *state) -{ - struct drm_device *dev = plane->dev; - struct st7571_device *st7571 = drm_to_st7571(plane->dev); - int idx; - - if (!drm_dev_enter(dev, &idx)) - return; - - st7571_fb_clear_screen(st7571); - drm_dev_exit(idx); -} - -static const struct drm_plane_helper_funcs st7571_primary_plane_helper_funcs = { - DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, - .atomic_check = st7571_primary_plane_helper_atomic_check, - .atomic_update = st7571_primary_plane_helper_atomic_update, - .atomic_disable = st7571_primary_plane_helper_atomic_disable, -}; - -static const struct drm_plane_funcs st7571_primary_plane_funcs = { - .update_plane = drm_atomic_helper_update_plane, - .disable_plane = drm_atomic_helper_disable_plane, - .destroy = drm_plane_cleanup, - DRM_GEM_SHADOW_PLANE_FUNCS, -}; - -/* - * CRTC - */ - -static enum drm_mode_status st7571_crtc_mode_valid(struct drm_crtc *crtc, - const struct drm_display_mode *mode) -{ - struct st7571_device *st7571 = drm_to_st7571(crtc->dev); - - return drm_crtc_helper_mode_valid_fixed(crtc, mode, &st7571->mode); -} - -static const struct drm_crtc_helper_funcs st7571_crtc_helper_funcs = { - .atomic_check = drm_crtc_helper_atomic_check, - .mode_valid = st7571_crtc_mode_valid, -}; - -static const struct drm_crtc_funcs st7571_crtc_funcs = { - .reset = drm_atomic_helper_crtc_reset, - .destroy = drm_crtc_cleanup, - .set_config = drm_atomic_helper_set_config, - .page_flip = drm_atomic_helper_page_flip, - .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, -}; - -/* - * Encoder - */ - -static void st7571_encoder_atomic_enable(struct drm_encoder *encoder, - struct drm_atomic_state *state) -{ - struct drm_device *drm = encoder->dev; - struct st7571_device *st7571 = drm_to_st7571(drm); - u8 command = ST7571_DISPLAY_ON; - int ret; - - ret = st7571->pdata->init(st7571); - if (ret) - return; - - st7571_send_command_list(st7571, &command, 1); -} - -static void st7571_encoder_atomic_disable(struct drm_encoder *encoder, - struct drm_atomic_state *state) -{ - struct drm_device *drm = encoder->dev; - struct st7571_device *st7571 = drm_to_st7571(drm); - u8 command = ST7571_DISPLAY_OFF; - - st7571_send_command_list(st7571, &command, 1); -} - -static const struct drm_encoder_funcs st7571_encoder_funcs = { - .destroy = drm_encoder_cleanup, - -}; - -static const struct drm_encoder_helper_funcs st7571_encoder_helper_funcs = { - .atomic_enable = st7571_encoder_atomic_enable, - .atomic_disable = st7571_encoder_atomic_disable, -}; - -/* - * Connector - */ - -static const struct drm_connector_funcs st7571_connector_funcs = { - .reset = drm_atomic_helper_connector_reset, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = drm_connector_cleanup, - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +static const struct regmap_bus st7571_i2c_regmap_bus = { + .read = st7571_i2c_regmap_read, + .write = st7571_i2c_regmap_write, }; -static const struct drm_mode_config_funcs st7571_mode_config_funcs = { - .fb_create = drm_gem_fb_create_with_dirty, - .atomic_check = drm_atomic_helper_check, - .atomic_commit = drm_atomic_helper_commit, -}; - -static struct drm_display_mode st7571_mode(struct st7571_device *st7571) -{ - struct drm_display_mode mode = { - DRM_SIMPLE_MODE(st7571->ncols, st7571->nlines, - st7571->width_mm, st7571->height_mm), - }; - - return mode; -} - -static int st7571_mode_config_init(struct st7571_device *st7571) -{ - struct drm_device *dev = &st7571->dev; - const struct st7571_panel_constraints *constraints = &st7571->pdata->constraints; - int ret; - - ret = drmm_mode_config_init(dev); - if (ret) - return ret; - - dev->mode_config.min_width = constraints->min_ncols; - dev->mode_config.min_height = constraints->min_nlines; - dev->mode_config.max_width = constraints->max_ncols; - dev->mode_config.max_height = constraints->max_nlines; - dev->mode_config.preferred_depth = 24; - dev->mode_config.funcs = &st7571_mode_config_funcs; - - return 0; -} - -static int st7571_plane_init(struct st7571_device *st7571, - const struct st7571_panel_format *pformat) -{ - struct drm_plane *primary_plane = &st7571->primary_plane; - struct drm_device *dev = &st7571->dev; - int ret; - - ret = drm_universal_plane_init(dev, primary_plane, 0, - &st7571_primary_plane_funcs, - pformat->formats, - pformat->nformats, - st7571_primary_plane_fmtmods, - DRM_PLANE_TYPE_PRIMARY, NULL); - if (ret) - return ret; - - drm_plane_helper_add(primary_plane, &st7571_primary_plane_helper_funcs); - drm_plane_enable_fb_damage_clips(primary_plane); - - return 0; -} - -static int st7571_crtc_init(struct st7571_device *st7571) -{ - struct drm_plane *primary_plane = &st7571->primary_plane; - struct drm_crtc *crtc = &st7571->crtc; - struct drm_device *dev = &st7571->dev; - int ret; - - ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL, - &st7571_crtc_funcs, NULL); - if (ret) - return ret; - - drm_crtc_helper_add(crtc, &st7571_crtc_helper_funcs); - - return 0; -} - -static int st7571_encoder_init(struct st7571_device *st7571) -{ - struct drm_encoder *encoder = &st7571->encoder; - struct drm_crtc *crtc = &st7571->crtc; - struct drm_device *dev = &st7571->dev; - int ret; - - ret = drm_encoder_init(dev, encoder, &st7571_encoder_funcs, DRM_MODE_ENCODER_NONE, NULL); - if (ret) - return ret; - - drm_encoder_helper_add(encoder, &st7571_encoder_helper_funcs); - - encoder->possible_crtcs = drm_crtc_mask(crtc); - - return 0; -} - -static int st7571_connector_init(struct st7571_device *st7571) -{ - struct drm_connector *connector = &st7571->connector; - struct drm_encoder *encoder = &st7571->encoder; - struct drm_device *dev = &st7571->dev; - int ret; - - ret = drm_connector_init(dev, connector, &st7571_connector_funcs, - DRM_MODE_CONNECTOR_Unknown); - if (ret) - return ret; - - drm_connector_helper_add(connector, &st7571_connector_helper_funcs); - - return drm_connector_attach_encoder(connector, encoder); -} - -DEFINE_DRM_GEM_FOPS(st7571_fops); - -static const struct drm_driver st7571_driver = { - .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, - - .name = DRIVER_NAME, - .desc = DRIVER_DESC, - .major = DRIVER_MAJOR, - .minor = DRIVER_MINOR, - - .fops = &st7571_fops, - DRM_GEM_SHMEM_DRIVER_OPS, - DRM_FBDEV_SHMEM_DRIVER_OPS, -}; - -static const struct regmap_bus st7571_regmap_bus = { - .read = st7571_regmap_read, - .write = st7571_regmap_write, -}; - -static const struct regmap_config st7571_regmap_config = { +static const struct regmap_config st7571_i2c_regmap_config = { .reg_bits = 8, .val_bits = 8, .use_single_write = true, }; -static int st7571_validate_parameters(struct st7571_device *st7571) -{ - struct device *dev = st7571->dev.dev; - const struct st7571_panel_constraints *constraints = &st7571->pdata->constraints; - - if (st7571->width_mm == 0) { - dev_err(dev, "Invalid panel width\n"); - return -EINVAL; - } - - if (st7571->height_mm == 0) { - dev_err(dev, "Invalid panel height\n"); - return -EINVAL; - } - - if (st7571->nlines < constraints->min_nlines || - st7571->nlines > constraints->max_nlines) { - dev_err(dev, "Invalid timing configuration.\n"); - return -EINVAL; - } - - if (st7571->startline + st7571->nlines > constraints->max_nlines) { - dev_err(dev, "Invalid timing configuration.\n"); - return -EINVAL; - } - - if (st7571->ncols < constraints->min_ncols || - st7571->ncols > constraints->max_ncols) { - dev_err(dev, "Invalid timing configuration.\n"); - return -EINVAL; - } - - if (st7571->grayscale && !constraints->support_grayscale) { - dev_err(dev, "Grayscale not supported\n"); - return -EINVAL; - } - - return 0; -} - -static int st7567_parse_dt(struct st7571_device *st7567) -{ - struct device *dev = &st7567->client->dev; - struct device_node *np = dev->of_node; - struct display_timing dt; - int ret; - - ret = of_get_display_timing(np, "panel-timing", &dt); - if (ret) { - dev_err(dev, "Failed to get display timing from DT\n"); - return ret; - } - - of_property_read_u32(np, "width-mm", &st7567->width_mm); - of_property_read_u32(np, "height-mm", &st7567->height_mm); - st7567->inverted = of_property_read_bool(np, "sitronix,inverted"); - - st7567->pformat = &st7571_monochrome; - st7567->bpp = 1; - - st7567->startline = dt.vfront_porch.typ; - st7567->nlines = dt.vactive.typ; - st7567->ncols = dt.hactive.typ; - - return 0; -} - -static int st7571_parse_dt(struct st7571_device *st7571) -{ - struct device *dev = &st7571->client->dev; - struct device_node *np = dev->of_node; - struct display_timing dt; - int ret; - - ret = of_get_display_timing(np, "panel-timing", &dt); - if (ret) { - dev_err(dev, "Failed to get display timing from DT\n"); - return ret; - } - - of_property_read_u32(np, "width-mm", &st7571->width_mm); - of_property_read_u32(np, "height-mm", &st7571->height_mm); - st7571->grayscale = of_property_read_bool(np, "sitronix,grayscale"); - st7571->inverted = of_property_read_bool(np, "sitronix,inverted"); - - if (st7571->grayscale) { - st7571->pformat = &st7571_grayscale; - st7571->bpp = 2; - } else { - st7571->pformat = &st7571_monochrome; - st7571->bpp = 1; - } - - st7571->startline = dt.vfront_porch.typ; - st7571->nlines = dt.vactive.typ; - st7571->ncols = dt.hactive.typ; - - st7571->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); - if (IS_ERR(st7571->reset)) - return dev_err_probe(dev, PTR_ERR(st7571->reset), - "Failed to get reset gpio\n"); - - - return 0; -} - -static void st7571_reset(struct st7571_device *st7571) -{ - gpiod_set_value_cansleep(st7571->reset, 1); - fsleep(20); - gpiod_set_value_cansleep(st7571->reset, 0); -} - -static int st7567_lcd_init(struct st7571_device *st7567) -{ - /* - * Most of the initialization sequence is taken directly from the - * referential initial code in the ST7567 datasheet. - */ - u8 commands[] = { - ST7571_DISPLAY_OFF, - - ST7567_SET_LCD_BIAS(1), - - ST7571_SET_SEG_SCAN_DIR(0), - ST7571_SET_COM_SCAN_DIR(1), - - ST7571_SET_REGULATOR_REG(4), - ST7571_SET_CONTRAST_MSB, - ST7571_SET_CONTRAST_LSB(0x20), - - ST7571_SET_START_LINE_MSB, - ST7571_SET_START_LINE_LSB(st7567->startline), - - ST7571_SET_POWER(0x4), /* Power Control, VC: ON, VR: OFF, VF: OFF */ - ST7571_SET_POWER(0x6), /* Power Control, VC: ON, VR: ON, VF: OFF */ - ST7571_SET_POWER(0x7), /* Power Control, VC: ON, VR: ON, VF: ON */ - - ST7571_SET_REVERSE(st7567->inverted ? 1 : 0), - ST7571_SET_ENTIRE_DISPLAY_ON(0), - }; - - return st7571_send_command_list(st7567, commands, ARRAY_SIZE(commands)); -} - -static int st7571_lcd_init(struct st7571_device *st7571) -{ - /* - * Most of the initialization sequence is taken directly from the - * referential initial code in the ST7571 datasheet. - */ - u8 commands[] = { - ST7571_DISPLAY_OFF, - - ST7571_SET_MODE_MSB, - ST7571_SET_MODE_LSB(0x2e), - - ST7571_SET_SEG_SCAN_DIR(0), - ST7571_SET_COM_SCAN_DIR(1), - - ST7571_SET_COM0_MSB, - ST7571_SET_COM0_LSB(0x00), - - ST7571_SET_START_LINE_MSB, - ST7571_SET_START_LINE_LSB(st7571->startline), - - ST7571_OSC_ON, - ST7571_SET_REGULATOR_REG(5), - ST7571_SET_CONTRAST_MSB, - ST7571_SET_CONTRAST_LSB(0x33), - ST7571_SET_LCD_BIAS(0x04), - ST7571_SET_DISPLAY_DUTY_MSB, - ST7571_SET_DISPLAY_DUTY_LSB(st7571->nlines), - - ST7571_SET_POWER(0x4), /* Power Control, VC: ON, VR: OFF, VF: OFF */ - ST7571_SET_POWER(0x6), /* Power Control, VC: ON, VR: ON, VF: OFF */ - ST7571_SET_POWER(0x7), /* Power Control, VC: ON, VR: ON, VF: ON */ - - ST7571_COMMAND_SET_3, - ST7571_SET_COLOR_MODE(st7571->pformat->mode), - ST7571_COMMAND_SET_NORMAL, - - ST7571_SET_REVERSE(st7571->inverted ? 1 : 0), - ST7571_SET_ENTIRE_DISPLAY_ON(0), - }; - - /* Perform a reset before initializing the controller */ - st7571_reset(st7571); - - return st7571_send_command_list(st7571, commands, ARRAY_SIZE(commands)); -} - -static int st7571_probe(struct i2c_client *client) +static int st7571_i2c_probe(struct i2c_client *client) { struct st7571_device *st7571; - struct drm_device *dev; - int ret; - - st7571 = devm_drm_dev_alloc(&client->dev, &st7571_driver, - struct st7571_device, dev); - if (IS_ERR(st7571)) - return PTR_ERR(st7571); - - dev = &st7571->dev; - st7571->client = client; - i2c_set_clientdata(client, st7571); - st7571->pdata = device_get_match_data(&client->dev); - - ret = st7571->pdata->parse_dt(st7571); - if (ret) - return ret; + struct st7571_i2c_transport *t; + struct regmap *regmap; - ret = st7571_validate_parameters(st7571); - if (ret) - return ret; + t = devm_kzalloc(&client->dev, sizeof(*t), GFP_KERNEL); + if (!t) + return -ENOMEM; - st7571->mode = st7571_mode(st7571); + t->client = client; /* * The hardware design could make it hard to detect a NAK on the I2C bus. @@ -964,94 +101,31 @@ static int st7571_probe(struct i2c_client *client) * cruft in the logs. */ if (i2c_check_functionality(client->adapter, I2C_FUNC_PROTOCOL_MANGLING)) - st7571->ignore_nak = true; + t->ignore_nak = true; - st7571->regmap = devm_regmap_init(&client->dev, &st7571_regmap_bus, - client, &st7571_regmap_config); - if (IS_ERR(st7571->regmap)) { - return dev_err_probe(&client->dev, PTR_ERR(st7571->regmap), + regmap = devm_regmap_init(&client->dev, &st7571_i2c_regmap_bus, + t, &st7571_i2c_regmap_config); + if (IS_ERR(regmap)) { + return dev_err_probe(&client->dev, PTR_ERR(regmap), "Failed to initialize regmap\n"); } - st7571->hwbuf = devm_kzalloc(&client->dev, - (st7571->nlines * st7571->ncols * st7571->bpp) / 8, - GFP_KERNEL); - if (!st7571->hwbuf) - return -ENOMEM; - - st7571->row = devm_kzalloc(&client->dev, - (st7571->ncols * st7571->bpp), - GFP_KERNEL); - if (!st7571->row) - return -ENOMEM; - - ret = st7571_mode_config_init(st7571); - if (ret) - return dev_err_probe(&client->dev, ret, - "Failed to initialize mode config\n"); - - ret = st7571_plane_init(st7571, st7571->pformat); - if (ret) - return dev_err_probe(&client->dev, ret, - "Failed to initialize primary plane\n"); - - ret = st7571_crtc_init(st7571); - if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to initialize CRTC\n"); - - ret = st7571_encoder_init(st7571); - if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to initialize encoder\n"); - - ret = st7571_connector_init(st7571); - if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to initialize connector\n"); - - drm_mode_config_reset(dev); - - ret = drm_dev_register(dev, 0); - if (ret) - return dev_err_probe(&client->dev, ret, - "Failed to register DRM device\n"); + st7571 = st7571_probe(&client->dev, regmap); + if (IS_ERR(st7571)) + return dev_err_probe(&client->dev, PTR_ERR(st7571), + "Failed to initialize regmap\n"); - drm_client_setup(dev, NULL); + i2c_set_clientdata(client, st7571); return 0; } -static void st7571_remove(struct i2c_client *client) +static void st7571_i2c_remove(struct i2c_client *client) { struct st7571_device *st7571 = i2c_get_clientdata(client); - drm_dev_unplug(&st7571->dev); + st7571_remove(st7571); } -static const struct st7571_panel_data st7567_config = { - .init = st7567_lcd_init, - .parse_dt = st7567_parse_dt, - .constraints = { - .min_nlines = 1, - .max_nlines = 64, - .min_ncols = 128, - .max_ncols = 128, - .support_grayscale = false, - }, -}; - -static const struct st7571_panel_data st7571_config = { - .init = st7571_lcd_init, - .parse_dt = st7571_parse_dt, - .constraints = { - .min_nlines = 1, - .max_nlines = 128, - .min_ncols = 128, - .max_ncols = 128, - .support_grayscale = true, - }, -}; - static const struct of_device_id st7571_of_match[] = { { .compatible = "sitronix,st7567", .data = &st7567_config }, { .compatible = "sitronix,st7571", .data = &st7571_config }, @@ -1068,16 +142,17 @@ MODULE_DEVICE_TABLE(i2c, st7571_id); static struct i2c_driver st7571_i2c_driver = { .driver = { - .name = "st7571", + .name = "st7571-i2c", .of_match_table = st7571_of_match, }, - .probe = st7571_probe, - .remove = st7571_remove, + .probe = st7571_i2c_probe, + .remove = st7571_i2c_remove, .id_table = st7571_id, }; module_i2c_driver(st7571_i2c_driver); MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>"); -MODULE_DESCRIPTION("DRM Driver for Sitronix ST7571 LCD controller"); +MODULE_DESCRIPTION("DRM Driver for Sitronix ST7571 LCD controller (I2C)"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("DRM_ST7571"); diff --git a/drivers/gpu/drm/sitronix/st7571-spi.c b/drivers/gpu/drm/sitronix/st7571-spi.c new file mode 100644 index 000000000000..0206e9162f1c --- /dev/null +++ b/drivers/gpu/drm/sitronix/st7571-spi.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Sitronix ST7571 connected via SPI bus. + * + * Copyright (C) 2025 Marcus Folkesson <marcus.folkesson@gmail.com> + */ + +#include <linux/spi/spi.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "st7571.h" + +static const struct regmap_config st7571_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .can_multi_write = true, +}; + +static int st7571_spi_probe(struct spi_device *spi) +{ + struct st7571_device *st7571; + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &st7571_spi_regmap_config); + if (IS_ERR(regmap)) { + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "Failed to initialize regmap\n"); + } + + st7571 = st7571_probe(&spi->dev, regmap); + if (IS_ERR(st7571)) + return dev_err_probe(&spi->dev, PTR_ERR(st7571), + "Failed to initialize regmap\n"); + + spi_set_drvdata(spi, st7571); + return 0; +} + +static void st7571_spi_remove(struct spi_device *spi) +{ + struct st7571_device *st7571 = spi_get_drvdata(spi); + + st7571_remove(st7571); +} + +static const struct of_device_id st7571_of_match[] = { + { .compatible = "sitronix,st7567", .data = &st7567_config }, + { .compatible = "sitronix,st7571", .data = &st7571_config }, + {}, +}; +MODULE_DEVICE_TABLE(of, st7571_of_match); + +static const struct spi_device_id st7571_spi_id[] = { + { "st7567", 0 }, + { "st7571", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, st7571_spi_id); + +static struct spi_driver st7571_spi_driver = { + .driver = { + .name = "st7571-spi", + .of_match_table = st7571_of_match, + }, + .probe = st7571_spi_probe, + .remove = st7571_spi_remove, + .id_table = st7571_spi_id, +}; + +module_spi_driver(st7571_spi_driver); + +MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>"); +MODULE_DESCRIPTION("DRM Driver for Sitronix ST7571 LCD controller (SPI)"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("DRM_ST7571"); diff --git a/drivers/gpu/drm/sitronix/st7571.c b/drivers/gpu/drm/sitronix/st7571.c new file mode 100644 index 000000000000..5fd575d972a2 --- /dev/null +++ b/drivers/gpu/drm/sitronix/st7571.c @@ -0,0 +1,918 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Sitronix ST7571, a 4 level gray scale dot matrix LCD controller + * + * Copyright (C) 2025 Marcus Folkesson <marcus.folkesson@gmail.com> + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_encoder.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fbdev_shmem.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_module.h> +#include <drm/drm_plane.h> +#include <drm/drm_probe_helper.h> + +#include <video/display_timing.h> +#include <video/of_display_timing.h> + +#include "st7571.h" + +#define ST7571_COMMAND_MODE (0x00) +#define ST7571_DATA_MODE (0x40) + +/* Normal mode command set */ +#define ST7571_DISPLAY_OFF (0xae) +#define ST7571_DISPLAY_ON (0xaf) +#define ST7571_OSC_ON (0xab) +#define ST7571_SET_COLUMN_LSB(c) (0x00 | FIELD_PREP(GENMASK(3, 0), (c))) +#define ST7571_SET_COLUMN_MSB(c) (0x10 | FIELD_PREP(GENMASK(2, 0), (c) >> 4)) +#define ST7571_SET_COM0_LSB(x) (FIELD_PREP(GENMASK(6, 0), (x))) +#define ST7571_SET_COM0_MSB (0x44) +#define ST7571_SET_COM_SCAN_DIR(d) (0xc0 | FIELD_PREP(GENMASK(3, 3), (d))) +#define ST7571_SET_CONTRAST_LSB(c) (FIELD_PREP(GENMASK(5, 0), (c))) +#define ST7571_SET_CONTRAST_MSB (0x81) +#define ST7571_SET_DISPLAY_DUTY_LSB(d) (FIELD_PREP(GENMASK(7, 0), (d))) +#define ST7571_SET_DISPLAY_DUTY_MSB (0x48) +#define ST7571_SET_ENTIRE_DISPLAY_ON(p) (0xa4 | FIELD_PREP(GENMASK(0, 0), (p))) +#define ST7571_SET_LCD_BIAS(b) (0x50 | FIELD_PREP(GENMASK(2, 0), (b))) +#define ST7571_SET_MODE_LSB(m) (FIELD_PREP(GENMASK(7, 2), (m))) +#define ST7571_SET_MODE_MSB (0x38) +#define ST7571_SET_PAGE(p) (0xb0 | FIELD_PREP(GENMASK(3, 0), (p))) +#define ST7571_SET_POWER(p) (0x28 | FIELD_PREP(GENMASK(2, 0), (p))) +#define ST7571_SET_REGULATOR_REG(r) (0x20 | FIELD_PREP(GENMASK(2, 0), (r))) +#define ST7571_SET_REVERSE(r) (0xa6 | FIELD_PREP(GENMASK(0, 0), (r))) +#define ST7571_SET_SEG_SCAN_DIR(d) (0xa0 | FIELD_PREP(GENMASK(0, 0), (d))) +#define ST7571_SET_START_LINE_LSB(l) (FIELD_PREP(GENMASK(6, 0), (l))) +#define ST7571_SET_START_LINE_MSB (0x40) + +/* Extension command set 3 */ +#define ST7571_COMMAND_SET_3 (0x7b) +#define ST7571_SET_COLOR_MODE(c) (0x10 | FIELD_PREP(GENMASK(0, 0), (c))) +#define ST7571_COMMAND_SET_NORMAL (0x00) + +/* ST7567 commands */ +#define ST7567_SET_LCD_BIAS(m) (0xa2 | FIELD_PREP(GENMASK(0, 0), (m))) + +#define ST7571_PAGE_HEIGHT 8 + +#define DRIVER_NAME "st7571" +#define DRIVER_DESC "ST7571 DRM driver" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static inline struct st7571_device *drm_to_st7571(struct drm_device *drm) +{ + return container_of(drm, struct st7571_device, drm); +} + +static int st7571_send_command_list(struct st7571_device *st7571, + const u8 *cmd_list, size_t len) +{ + int ret; + + for (int i = 0; i < len; i++) { + ret = regmap_write(st7571->regmap, ST7571_COMMAND_MODE, cmd_list[i]); + if (ret < 0) + return ret; + } + + return ret; +} + +static inline u8 st7571_transform_xy(const char *p, int x, int y, u8 bpp) +{ + int xrest = x % 8; + u8 result = 0; + u8 row_len = 16 * bpp; + + /* + * Transforms an (x, y) pixel coordinate into a vertical 8-bit + * column from the framebuffer. It calculates the corresponding byte in the + * framebuffer, extracts the bit at the given x position across 8 consecutive + * rows, and packs those bits into a single byte. + * + * Return an 8-bit value representing a vertical column of pixels. + */ + x = x / 8; + y = (y / 8) * 8; + + for (int i = 0; i < 8; i++) { + int row_idx = y + i; + u8 byte = p[row_idx * row_len + x]; + u8 bit = (byte >> xrest) & 1; + + result |= (bit << i); + } + + return result; +} + +static int st7571_set_position(struct st7571_device *st7571, int x, int y) +{ + u8 cmd_list[] = { + ST7571_SET_COLUMN_LSB(x), + ST7571_SET_COLUMN_MSB(x), + ST7571_SET_PAGE(y / ST7571_PAGE_HEIGHT), + }; + + return st7571_send_command_list(st7571, cmd_list, ARRAY_SIZE(cmd_list)); +} + +static int st7571_fb_clear_screen(struct st7571_device *st7571) +{ + u32 npixels = st7571->ncols * round_up(st7571->nlines, ST7571_PAGE_HEIGHT) * st7571->bpp; + char pixelvalue = 0x00; + + st7571_set_position(st7571, 0, 0); + for (int i = 0; i < npixels; i++) + regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, &pixelvalue, 1); + + return 0; +} + +static void st7571_prepare_buffer_monochrome(struct st7571_device *st7571, + const struct iosys_map *vmap, + struct drm_framebuffer *fb, + struct drm_rect *rect, + struct drm_format_conv_state *fmtcnv_state) +{ + unsigned int dst_pitch; + struct iosys_map dst; + u32 size; + + switch (fb->format->format) { + case DRM_FORMAT_XRGB8888: + dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 8); + iosys_map_set_vaddr(&dst, st7571->hwbuf); + + drm_fb_xrgb8888_to_mono(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); + break; + + case DRM_FORMAT_R1: + size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8; + memcpy(st7571->hwbuf, vmap->vaddr, size); + break; + } +} + +static void st7571_prepare_buffer_grayscale(struct st7571_device *st7571, + const struct iosys_map *vmap, + struct drm_framebuffer *fb, + struct drm_rect *rect, + struct drm_format_conv_state *fmtcnv_state) +{ + u32 size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8; + unsigned int dst_pitch; + struct iosys_map dst; + + switch (fb->format->format) { + case DRM_FORMAT_XRGB8888: + dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 4); + iosys_map_set_vaddr(&dst, st7571->hwbuf); + + drm_fb_xrgb8888_to_gray2(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); + break; + + case DRM_FORMAT_R1: + size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8; + memcpy(st7571->hwbuf, vmap->vaddr, size); + break; + + case DRM_FORMAT_R2: + size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 4; + memcpy(st7571->hwbuf, vmap->vaddr, size); + break; + } +} + +static int st7571_fb_update_rect_monochrome(struct drm_framebuffer *fb, struct drm_rect *rect) +{ + struct st7571_device *st7571 = drm_to_st7571(fb->dev); + char *row = st7571->row; + + /* Align y to display page boundaries */ + rect->y1 = round_down(rect->y1, ST7571_PAGE_HEIGHT); + rect->y2 = min_t(unsigned int, round_up(rect->y2, ST7571_PAGE_HEIGHT), st7571->nlines); + + for (int y = rect->y1; y < rect->y2; y += ST7571_PAGE_HEIGHT) { + for (int x = rect->x1; x < rect->x2; x++) + row[x] = st7571_transform_xy(st7571->hwbuf, x, y, 1); + + st7571_set_position(st7571, rect->x1, y); + + /* TODO: Investige why we can't write multiple bytes at once */ + for (int x = rect->x1; x < rect->x2; x++) + regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1); + } + + return 0; +} + +static int st7571_fb_update_rect_grayscale(struct drm_framebuffer *fb, struct drm_rect *rect) +{ + struct st7571_device *st7571 = drm_to_st7571(fb->dev); + u32 format = fb->format->format; + char *row = st7571->row; + int x1; + int x2; + + /* Align y to display page boundaries */ + rect->y1 = round_down(rect->y1, ST7571_PAGE_HEIGHT); + rect->y2 = min_t(unsigned int, round_up(rect->y2, ST7571_PAGE_HEIGHT), st7571->nlines); + + switch (format) { + case DRM_FORMAT_R1: + x1 = rect->x1 * 1; + x2 = rect->x2 * 1; + break; + case DRM_FORMAT_R2: + fallthrough; + case DRM_FORMAT_XRGB8888: + x1 = rect->x1 * 2; + x2 = rect->x2 * 2; + break; + } + + for (int y = rect->y1; y < rect->y2; y += ST7571_PAGE_HEIGHT) { + for (int x = x1; x < x2; x++) + row[x] = st7571_transform_xy(st7571->hwbuf, x, y, 2); + + st7571_set_position(st7571, rect->x1, y); + + /* TODO: Investige why we can't write multiple bytes at once */ + for (int x = x1; x < x2; x++) { + regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1); + + /* + * As the display supports grayscale, all pixels must be written as two bits + * even if the format is monochrome. + * + * The bit values maps to the following grayscale: + * 0 0 = Black + * 0 1 = Dark gray + * 1 0 = Light gray + * 1 1 = White + * + * For monochrome formats, write the same value twice to get + * either a black or white pixel. + */ + if (format == DRM_FORMAT_R1) + regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1); + } + } + + return 0; +} + +static int st7571_connector_get_modes(struct drm_connector *conn) +{ + struct st7571_device *st7571 = drm_to_st7571(conn->dev); + + return drm_connector_helper_get_modes_fixed(conn, &st7571->mode); +} + +static const struct drm_connector_helper_funcs st7571_connector_helper_funcs = { + .get_modes = st7571_connector_get_modes, +}; + +static const struct st7571_panel_format st7571_monochrome = { + .prepare_buffer = st7571_prepare_buffer_monochrome, + .update_rect = st7571_fb_update_rect_monochrome, + .mode = ST7571_COLOR_MODE_BLACKWHITE, + .formats = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_R1, + }, + .nformats = 2, +}; + +static const struct st7571_panel_format st7571_grayscale = { + .prepare_buffer = st7571_prepare_buffer_grayscale, + .update_rect = st7571_fb_update_rect_grayscale, + .mode = ST7571_COLOR_MODE_GRAY, + .formats = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_R1, + DRM_FORMAT_R2, + }, + .nformats = 3, +}; + +static const u64 st7571_primary_plane_fmtmods[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static int st7571_primary_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_crtc *new_crtc = new_plane_state->crtc; + struct drm_crtc_state *new_crtc_state = NULL; + + if (new_crtc) + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc); + + return drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} + +static void st7571_primary_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_atomic_helper_damage_iter iter; + struct drm_device *drm = plane->dev; + struct drm_rect damage; + struct st7571_device *st7571 = drm_to_st7571(plane->dev); + int ret, idx; + + if (!fb) + return; /* no framebuffer; plane is disabled */ + + ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); + if (ret) + return; + + if (!drm_dev_enter(drm, &idx)) + goto out_drm_gem_fb_end_cpu_access; + + drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &damage) { + st7571->pformat->prepare_buffer(st7571, + &shadow_plane_state->data[0], + fb, &damage, + &shadow_plane_state->fmtcnv_state); + + st7571->pformat->update_rect(fb, &damage); + } + + drm_dev_exit(idx); + +out_drm_gem_fb_end_cpu_access: + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); +} + +static void st7571_primary_plane_helper_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *drm = plane->dev; + struct st7571_device *st7571 = drm_to_st7571(plane->dev); + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + + st7571_fb_clear_screen(st7571); + drm_dev_exit(idx); +} + +static const struct drm_plane_helper_funcs st7571_primary_plane_helper_funcs = { + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .atomic_check = st7571_primary_plane_helper_atomic_check, + .atomic_update = st7571_primary_plane_helper_atomic_update, + .atomic_disable = st7571_primary_plane_helper_atomic_disable, +}; + +static const struct drm_plane_funcs st7571_primary_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + DRM_GEM_SHADOW_PLANE_FUNCS, +}; + +/* + * CRTC + */ + +static enum drm_mode_status st7571_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct st7571_device *st7571 = drm_to_st7571(crtc->dev); + + return drm_crtc_helper_mode_valid_fixed(crtc, mode, &st7571->mode); +} + +static const struct drm_crtc_helper_funcs st7571_crtc_helper_funcs = { + .atomic_check = drm_crtc_helper_atomic_check, + .mode_valid = st7571_crtc_mode_valid, +}; + +static const struct drm_crtc_funcs st7571_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +/* + * Encoder + */ + +static void st7571_encoder_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct drm_device *drm = encoder->dev; + struct st7571_device *st7571 = drm_to_st7571(drm); + u8 command = ST7571_DISPLAY_ON; + int ret; + + ret = st7571->pdata->init(st7571); + if (ret) + return; + + st7571_send_command_list(st7571, &command, 1); +} + +static void st7571_encoder_atomic_disable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct drm_device *drm = encoder->dev; + struct st7571_device *st7571 = drm_to_st7571(drm); + u8 command = ST7571_DISPLAY_OFF; + + st7571_send_command_list(st7571, &command, 1); +} + +static const struct drm_encoder_funcs st7571_encoder_funcs = { + .destroy = drm_encoder_cleanup, + +}; + +static const struct drm_encoder_helper_funcs st7571_encoder_helper_funcs = { + .atomic_enable = st7571_encoder_atomic_enable, + .atomic_disable = st7571_encoder_atomic_disable, +}; + +/* + * Connector + */ + +static const struct drm_connector_funcs st7571_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_mode_config_funcs st7571_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static struct drm_display_mode st7571_mode(struct st7571_device *st7571) +{ + struct drm_display_mode mode = { + DRM_SIMPLE_MODE(st7571->ncols, st7571->nlines, + st7571->width_mm, st7571->height_mm), + }; + + return mode; +} + +static int st7571_mode_config_init(struct st7571_device *st7571) +{ + struct drm_device *drm = &st7571->drm; + const struct st7571_panel_constraints *constraints = &st7571->pdata->constraints; + int ret; + + ret = drmm_mode_config_init(drm); + if (ret) + return ret; + + drm->mode_config.min_width = constraints->min_ncols; + drm->mode_config.min_height = constraints->min_nlines; + drm->mode_config.max_width = constraints->max_ncols; + drm->mode_config.max_height = constraints->max_nlines; + drm->mode_config.preferred_depth = 24; + drm->mode_config.funcs = &st7571_mode_config_funcs; + + return 0; +} + +static int st7571_plane_init(struct st7571_device *st7571, + const struct st7571_panel_format *pformat) +{ + struct drm_plane *primary_plane = &st7571->primary_plane; + struct drm_device *drm = &st7571->drm; + int ret; + + ret = drm_universal_plane_init(drm, primary_plane, 0, + &st7571_primary_plane_funcs, + pformat->formats, + pformat->nformats, + st7571_primary_plane_fmtmods, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + + drm_plane_helper_add(primary_plane, &st7571_primary_plane_helper_funcs); + drm_plane_enable_fb_damage_clips(primary_plane); + + return 0; +} + +static int st7571_crtc_init(struct st7571_device *st7571) +{ + struct drm_plane *primary_plane = &st7571->primary_plane; + struct drm_crtc *crtc = &st7571->crtc; + struct drm_device *drm = &st7571->drm; + int ret; + + ret = drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL, + &st7571_crtc_funcs, NULL); + if (ret) + return ret; + + drm_crtc_helper_add(crtc, &st7571_crtc_helper_funcs); + + return 0; +} + +static int st7571_encoder_init(struct st7571_device *st7571) +{ + struct drm_encoder *encoder = &st7571->encoder; + struct drm_crtc *crtc = &st7571->crtc; + struct drm_device *drm = &st7571->drm; + int ret; + + ret = drm_encoder_init(drm, encoder, &st7571_encoder_funcs, DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ret; + + drm_encoder_helper_add(encoder, &st7571_encoder_helper_funcs); + + encoder->possible_crtcs = drm_crtc_mask(crtc); + + return 0; +} + +static int st7571_connector_init(struct st7571_device *st7571) +{ + struct drm_connector *connector = &st7571->connector; + struct drm_encoder *encoder = &st7571->encoder; + struct drm_device *drm = &st7571->drm; + int ret; + + ret = drm_connector_init(drm, connector, &st7571_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + return ret; + + drm_connector_helper_add(connector, &st7571_connector_helper_funcs); + + return drm_connector_attach_encoder(connector, encoder); +} + +DEFINE_DRM_GEM_FOPS(st7571_fops); + +static const struct drm_driver st7571_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + + .fops = &st7571_fops, + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, +}; + +static int st7571_validate_parameters(struct st7571_device *st7571) +{ + struct device *dev = st7571->dev; + const struct st7571_panel_constraints *constraints = &st7571->pdata->constraints; + + if (st7571->width_mm == 0) { + dev_err(dev, "Invalid panel width\n"); + return -EINVAL; + } + + if (st7571->height_mm == 0) { + dev_err(dev, "Invalid panel height\n"); + return -EINVAL; + } + + if (st7571->nlines < constraints->min_nlines || + st7571->nlines > constraints->max_nlines) { + dev_err(dev, "Invalid timing configuration.\n"); + return -EINVAL; + } + + if (st7571->startline + st7571->nlines > constraints->max_nlines) { + dev_err(dev, "Invalid timing configuration.\n"); + return -EINVAL; + } + + if (st7571->ncols < constraints->min_ncols || + st7571->ncols > constraints->max_ncols) { + dev_err(dev, "Invalid timing configuration.\n"); + return -EINVAL; + } + + if (st7571->grayscale && !constraints->support_grayscale) { + dev_err(dev, "Grayscale not supported\n"); + return -EINVAL; + } + + return 0; +} + +static int st7567_parse_dt(struct st7571_device *st7567) +{ + struct device *dev = st7567->dev; + struct device_node *np = dev->of_node; + struct display_timing dt; + int ret; + + ret = of_get_display_timing(np, "panel-timing", &dt); + if (ret) { + dev_err(dev, "Failed to get display timing from DT\n"); + return ret; + } + + of_property_read_u32(np, "width-mm", &st7567->width_mm); + of_property_read_u32(np, "height-mm", &st7567->height_mm); + st7567->inverted = of_property_read_bool(np, "sitronix,inverted"); + + st7567->pformat = &st7571_monochrome; + st7567->bpp = 1; + + st7567->startline = dt.vfront_porch.typ; + st7567->nlines = dt.vactive.typ; + st7567->ncols = dt.hactive.typ; + + return 0; +} + +static int st7571_parse_dt(struct st7571_device *st7571) +{ + struct device *dev = st7571->dev; + struct device_node *np = dev->of_node; + struct display_timing dt; + int ret; + + ret = of_get_display_timing(np, "panel-timing", &dt); + if (ret) { + dev_err(dev, "Failed to get display timing from DT\n"); + return ret; + } + + of_property_read_u32(np, "width-mm", &st7571->width_mm); + of_property_read_u32(np, "height-mm", &st7571->height_mm); + st7571->grayscale = of_property_read_bool(np, "sitronix,grayscale"); + st7571->inverted = of_property_read_bool(np, "sitronix,inverted"); + + if (st7571->grayscale) { + st7571->pformat = &st7571_grayscale; + st7571->bpp = 2; + } else { + st7571->pformat = &st7571_monochrome; + st7571->bpp = 1; + } + + st7571->startline = dt.vfront_porch.typ; + st7571->nlines = dt.vactive.typ; + st7571->ncols = dt.hactive.typ; + + st7571->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(st7571->reset)) + return dev_err_probe(dev, PTR_ERR(st7571->reset), + "Failed to get reset gpio\n"); + + return 0; +} + +static void st7571_reset(struct st7571_device *st7571) +{ + gpiod_set_value_cansleep(st7571->reset, 1); + fsleep(20); + gpiod_set_value_cansleep(st7571->reset, 0); +} + +static int st7567_lcd_init(struct st7571_device *st7567) +{ + /* + * Most of the initialization sequence is taken directly from the + * referential initial code in the ST7567 datasheet. + */ + u8 commands[] = { + ST7571_DISPLAY_OFF, + + ST7567_SET_LCD_BIAS(1), + + ST7571_SET_SEG_SCAN_DIR(0), + ST7571_SET_COM_SCAN_DIR(1), + + ST7571_SET_REGULATOR_REG(4), + ST7571_SET_CONTRAST_MSB, + ST7571_SET_CONTRAST_LSB(0x20), + + ST7571_SET_START_LINE_MSB, + ST7571_SET_START_LINE_LSB(st7567->startline), + + ST7571_SET_POWER(0x4), /* Power Control, VC: ON, VR: OFF, VF: OFF */ + ST7571_SET_POWER(0x6), /* Power Control, VC: ON, VR: ON, VF: OFF */ + ST7571_SET_POWER(0x7), /* Power Control, VC: ON, VR: ON, VF: ON */ + + ST7571_SET_REVERSE(st7567->inverted ? 1 : 0), + ST7571_SET_ENTIRE_DISPLAY_ON(0), + }; + + return st7571_send_command_list(st7567, commands, ARRAY_SIZE(commands)); +} + +static int st7571_lcd_init(struct st7571_device *st7571) +{ + /* + * Most of the initialization sequence is taken directly from the + * referential initial code in the ST7571 datasheet. + */ + u8 commands[] = { + ST7571_DISPLAY_OFF, + + ST7571_SET_MODE_MSB, + ST7571_SET_MODE_LSB(0x2e), + + ST7571_SET_SEG_SCAN_DIR(0), + ST7571_SET_COM_SCAN_DIR(1), + + ST7571_SET_COM0_MSB, + ST7571_SET_COM0_LSB(0x00), + + ST7571_SET_START_LINE_MSB, + ST7571_SET_START_LINE_LSB(st7571->startline), + + ST7571_OSC_ON, + ST7571_SET_REGULATOR_REG(5), + ST7571_SET_CONTRAST_MSB, + ST7571_SET_CONTRAST_LSB(0x33), + ST7571_SET_LCD_BIAS(0x04), + ST7571_SET_DISPLAY_DUTY_MSB, + ST7571_SET_DISPLAY_DUTY_LSB(st7571->nlines), + + ST7571_SET_POWER(0x4), /* Power Control, VC: ON, VR: OFF, VF: OFF */ + ST7571_SET_POWER(0x6), /* Power Control, VC: ON, VR: ON, VF: OFF */ + ST7571_SET_POWER(0x7), /* Power Control, VC: ON, VR: ON, VF: ON */ + + ST7571_COMMAND_SET_3, + ST7571_SET_COLOR_MODE(st7571->pformat->mode), + ST7571_COMMAND_SET_NORMAL, + + ST7571_SET_REVERSE(st7571->inverted ? 1 : 0), + ST7571_SET_ENTIRE_DISPLAY_ON(0), + }; + + /* Perform a reset before initializing the controller */ + st7571_reset(st7571); + + return st7571_send_command_list(st7571, commands, ARRAY_SIZE(commands)); +} + +struct st7571_device *st7571_probe(struct device *dev, + struct regmap *regmap) +{ + struct st7571_device *st7571; + struct drm_device *drm; + int ret; + + st7571 = devm_drm_dev_alloc(dev, &st7571_driver, + struct st7571_device, drm); + if (IS_ERR(st7571)) + return st7571; + + drm = &st7571->drm; + st7571->dev = dev; + st7571->pdata = device_get_match_data(st7571->dev); + + ret = st7571->pdata->parse_dt(st7571); + if (ret) + return ERR_PTR(ret); + + ret = st7571_validate_parameters(st7571); + if (ret) + return ERR_PTR(ret); + + st7571->mode = st7571_mode(st7571); + st7571->regmap = regmap; + + st7571->hwbuf = devm_kzalloc(st7571->dev, + (st7571->nlines * st7571->ncols * st7571->bpp) / 8, + GFP_KERNEL); + if (!st7571->hwbuf) + return ERR_PTR(-ENOMEM); + + st7571->row = devm_kzalloc(st7571->dev, + (st7571->ncols * st7571->bpp), + GFP_KERNEL); + if (!st7571->row) + return ERR_PTR(-ENOMEM); + + ret = st7571_mode_config_init(st7571); + if (ret) { + dev_err(st7571->dev, "Failed to initialize mode config\n"); + return ERR_PTR(ret); + } + + ret = st7571_plane_init(st7571, st7571->pformat); + if (ret) { + dev_err(st7571->dev, "Failed to initialize primary plane\n"); + return ERR_PTR(ret); + } + + ret = st7571_crtc_init(st7571); + if (ret < 0) { + dev_err(st7571->dev, "Failed to initialize CRTC\n"); + return ERR_PTR(ret); + } + + ret = st7571_encoder_init(st7571); + if (ret < 0) { + dev_err(st7571->dev, "Failed to initialize encoder\n"); + return ERR_PTR(ret); + } + + ret = st7571_connector_init(st7571); + if (ret < 0) { + dev_err(st7571->dev, "Failed to initialize connector\n"); + return ERR_PTR(ret); + } + + drm_mode_config_reset(drm); + + ret = drm_dev_register(drm, 0); + if (ret) { + dev_err(st7571->dev, "Failed to register DRM device\n"); + return ERR_PTR(ret); + } + + drm_client_setup(drm, NULL); + return st7571; +} +EXPORT_SYMBOL_GPL(st7571_probe); + +void st7571_remove(struct st7571_device *st7571) +{ + drm_dev_unplug(&st7571->drm); +} +EXPORT_SYMBOL_GPL(st7571_remove); + +const struct st7571_panel_data st7567_config = { + .init = st7567_lcd_init, + .parse_dt = st7567_parse_dt, + .constraints = { + .min_nlines = 1, + .max_nlines = 64, + .min_ncols = 128, + .max_ncols = 128, + .support_grayscale = false, + }, +}; +EXPORT_SYMBOL_NS_GPL(st7567_config, "DRM_ST7571"); + +const struct st7571_panel_data st7571_config = { + .init = st7571_lcd_init, + .parse_dt = st7571_parse_dt, + .constraints = { + .min_nlines = 1, + .max_nlines = 128, + .min_ncols = 128, + .max_ncols = 128, + .support_grayscale = true, + }, +}; +EXPORT_SYMBOL_NS_GPL(st7571_config, "DRM_ST7571"); + +MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>"); +MODULE_DESCRIPTION("DRM Driver for Sitronix ST7571 LCD controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sitronix/st7571.h b/drivers/gpu/drm/sitronix/st7571.h new file mode 100644 index 000000000000..af264f2e2ea4 --- /dev/null +++ b/drivers/gpu/drm/sitronix/st7571.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Header file for: + * Driver for Sitronix ST7571, a 4 level gray scale dot matrix LCD controller + * + * Copyright (C) 2025 Marcus Folkesson <marcus.folkesson@gmail.com> + */ + +#ifndef __ST7571_H__ +#define __ST7571_H__ + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_drv.h> +#include <drm/drm_encoder.h> +#include <drm/drm_format_helper.h> + +#include <linux/regmap.h> + +enum st7571_color_mode { + ST7571_COLOR_MODE_GRAY = 0, + ST7571_COLOR_MODE_BLACKWHITE = 1, +}; + +struct st7571_device; + +struct st7571_panel_constraints { + u32 min_nlines; + u32 max_nlines; + u32 min_ncols; + u32 max_ncols; + bool support_grayscale; +}; + +struct st7571_panel_data { + int (*init)(struct st7571_device *st7571); + int (*parse_dt)(struct st7571_device *st7571); + struct st7571_panel_constraints constraints; +}; + +struct st7571_panel_format { + void (*prepare_buffer)(struct st7571_device *st7571, + const struct iosys_map *vmap, + struct drm_framebuffer *fb, + struct drm_rect *rect, + struct drm_format_conv_state *fmtcnv_state); + int (*update_rect)(struct drm_framebuffer *fb, struct drm_rect *rect); + enum st7571_color_mode mode; + const u8 nformats; + const u32 formats[]; +}; + +struct st7571_device { + struct drm_device drm; + struct device *dev; + + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector connector; + + struct drm_display_mode mode; + + const struct st7571_panel_format *pformat; + const struct st7571_panel_data *pdata; + struct gpio_desc *reset; + struct regmap *regmap; + + bool grayscale; + bool inverted; + u32 height_mm; + u32 width_mm; + u32 startline; + u32 nlines; + u32 ncols; + u32 bpp; + + /* Intermediate buffer in LCD friendly format */ + u8 *hwbuf; + + /* Row of (transformed) pixels ready to be written to the display */ + u8 *row; +}; + +extern const struct st7571_panel_data st7567_config; +extern const struct st7571_panel_data st7571_config; + +struct st7571_device *st7571_probe(struct device *dev, struct regmap *regmap); +void st7571_remove(struct st7571_device *st7571); + +#endif /* __ST7571_H__ */ diff --git a/drivers/gpu/drm/sitronix/st7920.c b/drivers/gpu/drm/sitronix/st7920.c new file mode 100644 index 000000000000..f35a157fdad8 --- /dev/null +++ b/drivers/gpu/drm/sitronix/st7920.c @@ -0,0 +1,867 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * DRM driver for Sitronix ST7920 LCD displays + * + * Copyright 2025 Iker Pedrosa <ikerpedrosam@gmail.com> + * + */ + +#include <linux/bitrev.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_shmem.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_plane.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#define DRIVER_NAME "sitronix_st7920" +#define DRIVER_DESC "DRM driver for Sitronix ST7920 LCD displays" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +/* Display organization */ +#define ST7920_PITCH 16 +#define ST7920_SCANLINES 64 +#define BYTES_IN_DISPLAY (ST7920_PITCH * ST7920_SCANLINES) +#define BYTES_IN_SEGMENT 2 +#define PIXELS_PER_SEGMENT (BYTES_IN_SEGMENT * 8) +#define ST7920_DEFAULT_WIDTH 128 +#define ST7920_DEFAULT_HEIGHT 64 + +/* Sync sequence */ +#define SYNC_BITS 0xF8 +#define RW_HIGH 0x04 +#define RS_HIGH 0x02 + +/* Commands */ +#define SET_DISPLAY_ON 0x0C +#define SET_DISPLAY_OFF 0x08 +#define SET_DISPLAY_CLEAR 0x01 +#define SET_BASIC_INSTRUCTION_SET 0x30 +#define SET_EXT_INSTRUCTION_SET 0x34 +#define SET_GRAPHICS_DISPLAY 0x36 +#define SET_GDRAM_ADDRESS 0x80 +#define SET_GDRAM_DATA 0xFF /* Driver internal command */ + +/* Masks */ +#define HIGH_DATA_MASK 0xF0 +#define LOW_DATA_MASK 0x0F +#define TOP_VERTICAL_ADDRESS 0x80 +#define BOTTOM_VERTICAL_ADDRESS 0x60 +#define TOP_HORIZONTAL_ADDRESS 0x00 +#define BOTTOM_HORIZONTAL_ADDRESS 0x80 + +#define CMD_SIZE 35 + +struct spi7920_error { + int errno; +}; + +struct st7920_device { + struct drm_device drm; + struct drm_display_mode mode; + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector connector; + struct spi_device *spi; + + struct regmap *regmap; + + struct gpio_desc *reset_gpio; + + u32 height; + u32 width; +}; + +struct st7920_plane_state { + struct drm_shadow_plane_state base; + /* Intermediate buffer to convert pixels from XRGB8888 to HW format */ + u8 *buffer; +}; + +struct st7920_crtc_state { + struct drm_crtc_state base; + /* Buffer to store pixels in HW format and written to the panel */ + u8 *data_array; +}; + +static inline struct st7920_plane_state *to_st7920_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct st7920_plane_state, base.base); +} + +static inline struct st7920_crtc_state *to_st7920_crtc_state(struct drm_crtc_state *state) +{ + return container_of(state, struct st7920_crtc_state, base); +} + +static inline struct st7920_device *drm_to_st7920(struct drm_device *drm) +{ + return container_of(drm, struct st7920_device, drm); +} + +static int st7920_store_gdram_address(const void *data, u8 *reg) +{ + const u8 y_addr = *(const u8 *)data; + bool bottom_screen = (y_addr >= 32); + int i = 0; + + reg[i++] = SYNC_BITS; + /* Set vertical address */ + if (!bottom_screen) + reg[i++] = TOP_VERTICAL_ADDRESS + (*(uint8_t *)data & HIGH_DATA_MASK); + else + reg[i++] = BOTTOM_VERTICAL_ADDRESS + (*(uint8_t *)data & HIGH_DATA_MASK); + + reg[i++] = *(uint8_t *)data << 4; + /* Set horizontal address */ + reg[i++] = SET_GDRAM_ADDRESS; + if (!bottom_screen) + reg[i++] = TOP_HORIZONTAL_ADDRESS; + else + reg[i++] = BOTTOM_HORIZONTAL_ADDRESS; + + return i; +} + +static int st7920_store_gdram_data(const void *data, u8 *reg) +{ + const u8 *line_data = data; + int i = 0, j = 0; + + reg[i++] = SYNC_BITS | RS_HIGH; + + for (j = 0; j < 16; j++) { + reg[i++] = line_data[j] & 0xF0; + reg[i++] = (line_data[j] << 4) & 0xF0; + } + + return i; +} + +static int st7920_store_others(int cmd, const void *data, u8 *reg) +{ + int i = 0; + + reg[i++] = SYNC_BITS; + reg[i++] = cmd & HIGH_DATA_MASK; + reg[i++] = (cmd & LOW_DATA_MASK) << 4; + + return i; +} + +static void st7920_spi_write(struct spi_device *spi, int cmd, const void *data, + int delay_us, struct spi7920_error *err) +{ + u8 reg[CMD_SIZE] = {0}; + int size = 0; + int ret; + + if (err->errno) + return; + + /* + * First the sync bits are sent: 11111WS0. + * Where W is the read/write (RW) bit and S is the register/data (RS) bit. + * Then, every 8 bits instruction/data will be separated into 2 groups. + * Higher 4 bits (DB7~DB4) will be placed in the first section followed by + * 4 '0's. And lower 4 bits (DB3~DB0) will be placed in the second section + * followed by 4 '0's. + */ + if (cmd == SET_GDRAM_ADDRESS) + size = st7920_store_gdram_address(data, reg); + else if (cmd == SET_GDRAM_DATA) + size = st7920_store_gdram_data(data, reg); + else + size = st7920_store_others(cmd, data, reg); + + ret = spi_write(spi, reg, size); + if (ret) { + err->errno = ret; + return; + } + + if (delay_us) + udelay(delay_us); +} + +static const struct regmap_config st7920_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct of_device_id st7920_of_match[] = { + /* st7920 family */ + { + .compatible = "sitronix,st7920", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, st7920_of_match); + +/* + * The SPI core always reports a MODALIAS uevent of the form "spi:<dev>", even + * if the device was registered via OF. This means that the module will not be + * auto loaded, unless it contains an alias that matches the MODALIAS reported. + * + * To workaround this issue, add a SPI device ID table. Even when this should + * not be needed for this driver to match the registered SPI devices. + */ +static const struct spi_device_id st7920_spi_id[] = { + /* st7920 family */ + { "st7920", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, st7920_spi_id); + +static void st7920_power_on(struct st7920_device *st7920, + struct spi7920_error *err) +{ + st7920_spi_write(st7920->spi, SET_DISPLAY_ON, NULL, 72, err); +} + +static void st7920_power_off(struct st7920_device *st7920, + struct spi7920_error *err) +{ + st7920_spi_write(st7920->spi, SET_DISPLAY_CLEAR, NULL, 1600, err); + st7920_spi_write(st7920->spi, SET_DISPLAY_OFF, NULL, 72, err); +} + +static void st7920_hw_reset(struct st7920_device *st7920) +{ + if (!st7920->reset_gpio) + return; + + gpiod_set_value_cansleep(st7920->reset_gpio, 1); + usleep_range(15, 20); + gpiod_set_value_cansleep(st7920->reset_gpio, 0); + msleep(40); +} + +static int st7920_init(struct st7920_device *st7920) +{ + struct spi7920_error err = {0}; + + st7920_spi_write(st7920->spi, SET_BASIC_INSTRUCTION_SET, NULL, 72, &err); + st7920_power_on(st7920, &err); + st7920_spi_write(st7920->spi, SET_GRAPHICS_DISPLAY, NULL, 72, &err); + st7920_spi_write(st7920->spi, SET_DISPLAY_CLEAR, NULL, 1600, &err); + + return err.errno; +} + +static int st7920_update_rect(struct st7920_device *st7920, + struct drm_rect *rect, u8 *buf, + u8 *data_array) +{ + struct spi7920_error err = {0}; + u32 array_idx = 0; + int i, j; + + /* + * The screen is divided in 64(Y)x8(X) segments and each segment is + * further divided in 2 bytes (D15~D0). + * + * Segment 0x0 is in the top-right corner, while segment 63x15 is in the + * bottom-left. They would be displayed in the screen in the following way: + * 0x0 0x1 0x2 ... 0x15 + * 1x0 1x1 1x2 ... 1x15 + * ... + * 63x0 63x1 63x2 ... 63x15 + * + * The data in each byte is big endian. + */ + + for (i = 0; i < ST7920_SCANLINES; i++) { + u8 *line_start = buf + (i * ST7920_PITCH); + u8 line_buffer[ST7920_PITCH]; + + for (j = 0; j < ST7920_PITCH; j++) { + line_buffer[j] = bitrev8(line_start[j]); + data_array[array_idx++] = line_buffer[j]; + } + + st7920_spi_write(st7920->spi, SET_GDRAM_ADDRESS, &i, 72, &err); + st7920_spi_write(st7920->spi, SET_GDRAM_DATA, line_buffer, 72, &err); + } + + return err.errno; +} + +static void st7920_clear_screen(struct st7920_device *st7920, u8 *data_array) +{ + struct spi7920_error err = {0}; + + memset(data_array, 0, BYTES_IN_DISPLAY); + + st7920_spi_write(st7920->spi, SET_DISPLAY_CLEAR, NULL, 1600, &err); +} + +static int st7920_fb_blit_rect(struct drm_framebuffer *fb, + const struct iosys_map *vmap, + struct drm_rect *rect, + u8 *buf, u8 *data_array, + struct drm_format_conv_state *fmtcnv_state) +{ + struct st7920_device *st7920 = drm_to_st7920(fb->dev); + struct iosys_map dst; + unsigned int dst_pitch; + int ret; + + /* Align y to display page boundaries */ + rect->y1 = round_down(rect->y1, PIXELS_PER_SEGMENT); + rect->y2 = min_t(unsigned int, round_up(rect->y2, PIXELS_PER_SEGMENT), st7920->height); + + dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 8); + + iosys_map_set_vaddr(&dst, buf); + drm_fb_xrgb8888_to_mono(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); + + ret = st7920_update_rect(st7920, rect, buf, data_array); + + return ret; +} + +static int st7920_primary_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *drm = plane->dev; + struct st7920_device *st7920 = drm_to_st7920(drm); + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct st7920_plane_state *st7920_state = to_st7920_plane_state(plane_state); + struct drm_shadow_plane_state *shadow_plane_state = &st7920_state->base; + struct drm_crtc *crtc = plane_state->crtc; + struct drm_crtc_state *crtc_state = NULL; + const struct drm_format_info *fi; + unsigned int pitch; + int ret; + + if (crtc) + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); + if (ret) + return ret; + else if (!plane_state->visible) + return 0; + + fi = drm_format_info(DRM_FORMAT_R1); + if (!fi) + return -EINVAL; + + pitch = drm_format_info_min_pitch(fi, 0, st7920->width); + + if (plane_state->fb->format != fi) { + void *buf; + + /* format conversion necessary; reserve buffer */ + buf = drm_format_conv_state_reserve(&shadow_plane_state->fmtcnv_state, + pitch, GFP_KERNEL); + if (!buf) + return -ENOMEM; + } + + st7920_state->buffer = kcalloc(pitch, st7920->height, GFP_KERNEL); + if (!st7920_state->buffer) + return -ENOMEM; + + return 0; +} + +static void st7920_primary_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); + struct st7920_crtc_state *st7920_crtc_state = to_st7920_crtc_state(crtc_state); + struct st7920_plane_state *st7920_plane_state = to_st7920_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_atomic_helper_damage_iter iter; + struct drm_device *drm = plane->dev; + struct drm_rect dst_clip; + struct drm_rect damage; + int idx; + int ret; + + if (!drm_dev_enter(drm, &idx)) + return; + + if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE) == 0) { + drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &damage) { + dst_clip = plane_state->dst; + + if (!drm_rect_intersect(&dst_clip, &damage)) + continue; + + ret = st7920_fb_blit_rect(fb, &shadow_plane_state->data[0], &dst_clip, + st7920_plane_state->buffer, + st7920_crtc_state->data_array, + &shadow_plane_state->fmtcnv_state); + if (ret) + drm_err_once(plane->dev, "Failed to write to device: %d.\n", ret); + } + + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); + } + + drm_dev_exit(idx); +} + +static void st7920_primary_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *drm = plane->dev; + struct st7920_device *st7920 = drm_to_st7920(drm); + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_crtc_state *crtc_state; + struct st7920_crtc_state *st7920_crtc_state; + int idx; + + if (!plane_state->crtc) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); + st7920_crtc_state = to_st7920_crtc_state(crtc_state); + + if (!drm_dev_enter(drm, &idx)) + return; + + st7920_clear_screen(st7920, st7920_crtc_state->data_array); + + drm_dev_exit(idx); +} + +/* Called during init to allocate the plane's atomic state. */ +static void st7920_primary_plane_reset(struct drm_plane *plane) +{ + struct st7920_plane_state *st7920_state; + + drm_WARN_ON_ONCE(plane->dev, plane->state); + + st7920_state = kzalloc(sizeof(*st7920_state), GFP_KERNEL); + if (!st7920_state) + return; + + __drm_gem_reset_shadow_plane(plane, &st7920_state->base); +} + +static struct drm_plane_state *st7920_primary_plane_duplicate_state(struct drm_plane *plane) +{ + struct drm_shadow_plane_state *new_shadow_plane_state; + struct st7920_plane_state *st7920_state; + + if (drm_WARN_ON_ONCE(plane->dev, !plane->state)) + return NULL; + + st7920_state = kzalloc(sizeof(*st7920_state), GFP_KERNEL); + if (!st7920_state) + return NULL; + + new_shadow_plane_state = &st7920_state->base; + + __drm_gem_duplicate_shadow_plane_state(plane, new_shadow_plane_state); + + return &new_shadow_plane_state->base; +} + +static void st7920_primary_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct st7920_plane_state *st7920_state = to_st7920_plane_state(state); + + kfree(st7920_state->buffer); + + __drm_gem_destroy_shadow_plane_state(&st7920_state->base); + + kfree(st7920_state); +} + +static const struct drm_plane_helper_funcs st7920_primary_plane_helper_funcs = { + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .atomic_check = st7920_primary_plane_atomic_check, + .atomic_update = st7920_primary_plane_atomic_update, + .atomic_disable = st7920_primary_plane_atomic_disable, +}; + +static const struct drm_plane_funcs st7920_primary_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = st7920_primary_plane_reset, + .atomic_duplicate_state = st7920_primary_plane_duplicate_state, + .atomic_destroy_state = st7920_primary_plane_destroy_state, + .destroy = drm_plane_cleanup, +}; + +static enum drm_mode_status st7920_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct st7920_device *st7920 = drm_to_st7920(crtc->dev); + + return drm_crtc_helper_mode_valid_fixed(crtc, mode, &st7920->mode); +} + +static int st7920_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct st7920_crtc_state *st7920_state = to_st7920_crtc_state(crtc_state); + int ret; + + ret = drm_crtc_helper_atomic_check(crtc, state); + if (ret) + return ret; + + st7920_state->data_array = kmalloc(BYTES_IN_DISPLAY, GFP_KERNEL); + if (!st7920_state->data_array) + return -ENOMEM; + + return 0; +} + +static void st7920_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *drm = crtc->dev; + struct st7920_device *st7920 = drm_to_st7920(drm); + int idx; + int ret; + + if (!drm_dev_enter(drm, &idx)) + return; + + st7920_hw_reset(st7920); + + ret = st7920_init(st7920); + if (ret) + drm_err(drm, "Failed to init hardware: %d\n", ret); + + drm_dev_exit(idx); +} + +static void st7920_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct spi7920_error err = {0}; + struct drm_device *drm = crtc->dev; + struct st7920_device *st7920 = drm_to_st7920(drm); + int idx; + + drm_dev_enter(drm, &idx); + + st7920_power_off(st7920, &err); + + drm_dev_exit(idx); +} + +/* Called during init to allocate the CRTC's atomic state. */ +static void st7920_crtc_reset(struct drm_crtc *crtc) +{ + struct st7920_crtc_state *st7920_state; + + drm_WARN_ON_ONCE(crtc->dev, crtc->state); + + st7920_state = kzalloc(sizeof(*st7920_state), GFP_KERNEL); + if (!st7920_state) + return; + + __drm_atomic_helper_crtc_reset(crtc, &st7920_state->base); +} + +static struct drm_crtc_state *st7920_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct st7920_crtc_state *st7920_state; + + if (drm_WARN_ON_ONCE(crtc->dev, !crtc->state)) + return NULL; + + st7920_state = kzalloc(sizeof(*st7920_state), GFP_KERNEL); + if (!st7920_state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &st7920_state->base); + + return &st7920_state->base; +} + +static void st7920_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct st7920_crtc_state *st7920_state = to_st7920_crtc_state(state); + + kfree(st7920_state->data_array); + + __drm_atomic_helper_crtc_destroy_state(state); + + kfree(st7920_state); +} + +/* + * The CRTC is always enabled. Screen updates are performed by + * the primary plane's atomic_update function. Disabling clears + * the screen in the primary plane's atomic_disable function. + */ +static const struct drm_crtc_helper_funcs st7920_crtc_helper_funcs = { + .mode_valid = st7920_crtc_mode_valid, + .atomic_check = st7920_crtc_atomic_check, + .atomic_enable = st7920_crtc_atomic_enable, + .atomic_disable = st7920_crtc_atomic_disable, +}; + +static const struct drm_crtc_funcs st7920_crtc_funcs = { + .reset = st7920_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = st7920_crtc_duplicate_state, + .atomic_destroy_state = st7920_crtc_destroy_state, +}; + +static const struct drm_encoder_funcs st7920_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int st7920_connector_get_modes(struct drm_connector *connector) +{ + struct st7920_device *st7920 = drm_to_st7920(connector->dev); + + return drm_connector_helper_get_modes_fixed(connector, &st7920->mode); +} + +static const struct drm_connector_helper_funcs st7920_connector_helper_funcs = { + .get_modes = st7920_connector_get_modes, +}; + +static const struct drm_connector_funcs st7920_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_mode_config_funcs st7920_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const uint32_t st7920_formats[] = { + DRM_FORMAT_XRGB8888, +}; + +DEFINE_DRM_GEM_FOPS(st7920_fops); + +static const struct drm_driver st7920_drm_driver = { + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + .fops = &st7920_fops, +}; + +static int st7920_init_modeset(struct st7920_device *st7920) +{ + struct drm_display_mode *mode = &st7920->mode; + struct drm_device *drm = &st7920->drm; + unsigned long max_width, max_height; + struct drm_plane *primary_plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + int ret; + + /* + * Modesetting + */ + + ret = drmm_mode_config_init(drm); + if (ret) { + drm_err(drm, "DRM mode config init failed: %d\n", ret); + return ret; + } + + mode->type = DRM_MODE_TYPE_DRIVER; + mode->clock = 30; + mode->hdisplay = st7920->width; + mode->htotal = st7920->width; + mode->hsync_start = st7920->width; + mode->hsync_end = st7920->width; + mode->vdisplay = st7920->height; + mode->vtotal = st7920->height; + mode->vsync_start = st7920->height; + mode->vsync_end = st7920->height; + mode->width_mm = 27; + mode->height_mm = 27; + + max_width = max_t(unsigned long, mode->hdisplay, DRM_SHADOW_PLANE_MAX_WIDTH); + max_height = max_t(unsigned long, mode->vdisplay, DRM_SHADOW_PLANE_MAX_HEIGHT); + + drm->mode_config.min_width = mode->hdisplay; + drm->mode_config.max_width = max_width; + drm->mode_config.min_height = mode->vdisplay; + drm->mode_config.max_height = max_height; + drm->mode_config.preferred_depth = 24; + drm->mode_config.funcs = &st7920_mode_config_funcs; + + /* Primary plane */ + + primary_plane = &st7920->primary_plane; + ret = drm_universal_plane_init(drm, primary_plane, 0, &st7920_primary_plane_funcs, + st7920_formats, ARRAY_SIZE(st7920_formats), + NULL, DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) { + drm_err(drm, "DRM primary plane init failed: %d\n", ret); + return ret; + } + + drm_plane_helper_add(primary_plane, &st7920_primary_plane_helper_funcs); + + drm_plane_enable_fb_damage_clips(primary_plane); + + /* CRTC */ + + crtc = &st7920->crtc; + ret = drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL, + &st7920_crtc_funcs, NULL); + if (ret) { + drm_err(drm, "DRM crtc init failed: %d\n", ret); + return ret; + } + + drm_crtc_helper_add(crtc, &st7920_crtc_helper_funcs); + + /* Encoder */ + + encoder = &st7920->encoder; + ret = drm_encoder_init(drm, encoder, &st7920_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) { + drm_err(drm, "DRM encoder init failed: %d\n", ret); + return ret; + } + + encoder->possible_crtcs = drm_crtc_mask(crtc); + + /* Connector */ + + connector = &st7920->connector; + ret = drm_connector_init(drm, connector, &st7920_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) { + drm_err(drm, "DRM connector init failed: %d\n", ret); + return ret; + } + + drm_connector_helper_add(connector, &st7920_connector_helper_funcs); + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) { + drm_err(drm, "DRM attach connector to encoder failed: %d\n", ret); + return ret; + } + + drm_mode_config_reset(drm); + + return 0; +} + +static int st7920_probe(struct spi_device *spi) +{ + struct st7920_device *st7920; + struct regmap *regmap; + struct device *dev = &spi->dev; + struct drm_device *drm; + int ret; + + regmap = devm_regmap_init_spi(spi, &st7920_spi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + st7920 = devm_drm_dev_alloc(dev, &st7920_drm_driver, + struct st7920_device, drm); + if (IS_ERR(st7920)) + return PTR_ERR(st7920); + + drm = &st7920->drm; + + st7920->drm.dev = dev; + st7920->regmap = regmap; + st7920->spi = spi; + st7920->width = ST7920_DEFAULT_WIDTH; + st7920->height = ST7920_DEFAULT_HEIGHT; + + st7920->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(st7920->reset_gpio)) { + ret = PTR_ERR(st7920->reset_gpio); + return dev_err_probe(dev, ret, "Unable to retrieve reset GPIO\n"); + } + + spi_set_drvdata(spi, st7920); + + ret = st7920_init_modeset(st7920); + if (ret) + return ret; + + ret = drm_dev_register(drm, 0); + if (ret) + return dev_err_probe(dev, ret, "DRM device register failed\n"); + + drm_client_setup(drm, NULL); + + return 0; +} + +static void st7920_remove(struct spi_device *spi) +{ + struct st7920_device *st7920 = spi_get_drvdata(spi); + + drm_dev_unplug(&st7920->drm); + drm_atomic_helper_shutdown(&st7920->drm); +} + +static void st7920_shutdown(struct spi_device *spi) +{ + struct st7920_device *st7920 = spi_get_drvdata(spi); + + drm_atomic_helper_shutdown(&st7920->drm); +} + +static struct spi_driver st7920_spi_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = st7920_of_match, + }, + .id_table = st7920_spi_id, + .probe = st7920_probe, + .remove = st7920_remove, + .shutdown = st7920_shutdown, +}; +module_spi_driver(st7920_spi_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Iker Pedrosa <ipedrosam@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h index 1a3018e4a537..6ee70ad65e1f 100644 --- a/include/drm/drm_file.h +++ b/include/drm/drm_file.h @@ -33,6 +33,7 @@ #include <linux/types.h> #include <linux/completion.h> #include <linux/idr.h> +#include <linux/xarray.h> #include <uapi/drm/drm.h> @@ -316,10 +317,8 @@ struct drm_file { /** @table_lock: Protects @object_idr. */ spinlock_t table_lock; - /** @syncobj_idr: Mapping of sync object handles to object pointers. */ - struct idr syncobj_idr; - /** @syncobj_table_lock: Protects @syncobj_idr. */ - spinlock_t syncobj_table_lock; + /** @syncobj_xa: Mapping of sync object handles to object pointers. */ + struct xarray syncobj_xa; /** @filp: Pointer to the core file structure. */ struct file *filp; diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h index f4da8ed0d630..86f5846154f7 100644 --- a/include/drm/drm_gem.h +++ b/include/drm/drm_gem.h @@ -508,11 +508,11 @@ static inline int drm_gem_huge_mnt_create(struct drm_device *dev, /** * drm_gem_get_huge_mnt - Get the huge tmpfs mountpoint used by a DRM device * @dev: DRM device - + * * This function gets the huge tmpfs mountpoint used by DRM device @dev. A huge * tmpfs mountpoint is used instead of `shm_mnt` after a successful call to * drm_gem_huge_mnt_create() when CONFIG_TRANSPARENT_HUGEPAGE is enabled. - + * * Returns: * The huge tmpfs mountpoint in use, NULL otherwise. */ diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h index fb88301b3c45..78e07c2507c7 100644 --- a/include/drm/gpu_scheduler.h +++ b/include/drm/gpu_scheduler.h @@ -645,6 +645,7 @@ void drm_sched_stop(struct drm_gpu_scheduler *sched, struct drm_sched_job *bad); void drm_sched_start(struct drm_gpu_scheduler *sched, int errno); void drm_sched_resubmit_jobs(struct drm_gpu_scheduler *sched); void drm_sched_fault(struct drm_gpu_scheduler *sched); +bool drm_sched_is_stopped(struct drm_gpu_scheduler *sched); struct drm_gpu_scheduler * drm_sched_pick_best(struct drm_gpu_scheduler **sched_list, @@ -674,6 +675,7 @@ bool drm_sched_job_has_dependency(struct drm_sched_job *job, struct dma_fence *fence); void drm_sched_job_cleanup(struct drm_sched_job *job); void drm_sched_increase_karma(struct drm_sched_job *bad); +bool drm_sched_job_is_signaled(struct drm_sched_job *job); static inline bool drm_sched_invalidate_job(struct drm_sched_job *s_job, int threshold) @@ -698,4 +700,54 @@ void drm_sched_entity_modify_sched(struct drm_sched_entity *entity, struct drm_gpu_scheduler **sched_list, unsigned int num_sched_list); +/** + * struct drm_sched_pending_job_iter - DRM scheduler pending job iterator state + * @sched: DRM scheduler associated with pending job iterator + */ +struct drm_sched_pending_job_iter { + struct drm_gpu_scheduler *sched; +}; + +/* Drivers should never call this directly */ +static inline struct drm_sched_pending_job_iter +__drm_sched_pending_job_iter_begin(struct drm_gpu_scheduler *sched) +{ + struct drm_sched_pending_job_iter iter = { + .sched = sched, + }; + + WARN_ON(!drm_sched_is_stopped(sched)); + return iter; +} + +/* Drivers should never call this directly */ +static inline void +__drm_sched_pending_job_iter_end(const struct drm_sched_pending_job_iter iter) +{ + WARN_ON(!drm_sched_is_stopped(iter.sched)); +} + +DEFINE_CLASS(drm_sched_pending_job_iter, struct drm_sched_pending_job_iter, + __drm_sched_pending_job_iter_end(_T), + __drm_sched_pending_job_iter_begin(__sched), + struct drm_gpu_scheduler *__sched); +static inline void * +class_drm_sched_pending_job_iter_lock_ptr(class_drm_sched_pending_job_iter_t *_T) +{ return _T; } +#define class_drm_sched_pending_job_iter_is_conditional false + +/** + * drm_sched_for_each_pending_job() - Iterator for each pending job in scheduler + * @__job: Current pending job being iterated over + * @__sched: DRM scheduler to iterate over pending jobs + * @__entity: DRM scheduler entity to filter jobs, NULL indicates no filter + * + * Iterator for each pending job in scheduler, filtering on an entity, and + * enforcing scheduler is fully stopped + */ +#define drm_sched_for_each_pending_job(__job, __sched, __entity) \ + scoped_guard(drm_sched_pending_job_iter, (__sched)) \ + list_for_each_entry((__job), &(__sched)->pending_list, list) \ + for_each_if(!(__entity) || (__job)->entity == (__entity)) + #endif diff --git a/include/trace/events/dma_buf.h b/include/trace/events/dma_buf.h new file mode 100644 index 000000000000..35f8140095f4 --- /dev/null +++ b/include/trace/events/dma_buf.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dma_buf + +#if !defined(_TRACE_DMA_BUF_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_DMA_BUF_H + +#include <linux/dma-buf.h> +#include <linux/tracepoint.h> + +DECLARE_EVENT_CLASS(dma_buf, + + TP_PROTO(struct dma_buf *dmabuf), + + TP_ARGS(dmabuf), + + TP_STRUCT__entry( + __string(exp_name, dmabuf->exp_name) + __field(size_t, size) + __field(ino_t, ino) + ), + + TP_fast_assign( + __assign_str(exp_name); + __entry->size = dmabuf->size; + __entry->ino = dmabuf->file->f_inode->i_ino; + ), + + TP_printk("exp_name=%s size=%zu ino=%lu", + __get_str(exp_name), + __entry->size, + __entry->ino) +); + +DECLARE_EVENT_CLASS(dma_buf_attach_dev, + + TP_PROTO(struct dma_buf *dmabuf, struct dma_buf_attachment *attach, + bool is_dynamic, struct device *dev), + + TP_ARGS(dmabuf, attach, is_dynamic, dev), + + TP_STRUCT__entry( + __string(dev_name, dev_name(dev)) + __string(exp_name, dmabuf->exp_name) + __field(size_t, size) + __field(ino_t, ino) + __field(struct dma_buf_attachment *, attach) + __field(bool, is_dynamic) + ), + + TP_fast_assign( + __assign_str(dev_name); + __assign_str(exp_name); + __entry->size = dmabuf->size; + __entry->ino = dmabuf->file->f_inode->i_ino; + __entry->is_dynamic = is_dynamic; + __entry->attach = attach; + ), + + TP_printk("exp_name=%s size=%zu ino=%lu attachment:%p is_dynamic=%d dev_name=%s", + __get_str(exp_name), + __entry->size, + __entry->ino, + __entry->attach, + __entry->is_dynamic, + __get_str(dev_name)) +); + +DECLARE_EVENT_CLASS(dma_buf_fd, + + TP_PROTO(struct dma_buf *dmabuf, int fd), + + TP_ARGS(dmabuf, fd), + + TP_STRUCT__entry( + __string(exp_name, dmabuf->exp_name) + __field(size_t, size) + __field(ino_t, ino) + __field(int, fd) + ), + + TP_fast_assign( + __assign_str(exp_name); + __entry->size = dmabuf->size; + __entry->ino = dmabuf->file->f_inode->i_ino; + __entry->fd = fd; + ), + + TP_printk("exp_name=%s size=%zu ino=%lu fd=%d", + __get_str(exp_name), + __entry->size, + __entry->ino, + __entry->fd) +); + +DEFINE_EVENT(dma_buf, dma_buf_export, + + TP_PROTO(struct dma_buf *dmabuf), + + TP_ARGS(dmabuf) +); + +DEFINE_EVENT(dma_buf, dma_buf_mmap_internal, + + TP_PROTO(struct dma_buf *dmabuf), + + TP_ARGS(dmabuf) +); + +DEFINE_EVENT(dma_buf, dma_buf_mmap, + + TP_PROTO(struct dma_buf *dmabuf), + + TP_ARGS(dmabuf) +); + +DEFINE_EVENT(dma_buf, dma_buf_put, + + TP_PROTO(struct dma_buf *dmabuf), + + TP_ARGS(dmabuf) +); + +DEFINE_EVENT(dma_buf_attach_dev, dma_buf_dynamic_attach, + + TP_PROTO(struct dma_buf *dmabuf, struct dma_buf_attachment *attach, + bool is_dynamic, struct device *dev), + + TP_ARGS(dmabuf, attach, is_dynamic, dev) +); + +DEFINE_EVENT(dma_buf_attach_dev, dma_buf_detach, + + TP_PROTO(struct dma_buf *dmabuf, struct dma_buf_attachment *attach, + bool is_dynamic, struct device *dev), + + TP_ARGS(dmabuf, attach, is_dynamic, dev) +); + +DEFINE_EVENT(dma_buf_fd, dma_buf_fd, + + TP_PROTO(struct dma_buf *dmabuf, int fd), + + TP_ARGS(dmabuf, fd) +); + +DEFINE_EVENT(dma_buf_fd, dma_buf_get, + + TP_PROTO(struct dma_buf *dmabuf, int fd), + + TP_ARGS(dmabuf, fd) +); + +#endif /* _TRACE_DMA_BUF_H */ + +/* This part must be outside protection */ +#include <trace/define_trace.h> diff --git a/include/uapi/drm/amdxdna_accel.h b/include/uapi/drm/amdxdna_accel.h index 62c917fd4f7b..9c44db2b3dcd 100644 --- a/include/uapi/drm/amdxdna_accel.h +++ b/include/uapi/drm/amdxdna_accel.h @@ -19,6 +19,14 @@ extern "C" { #define AMDXDNA_INVALID_BO_HANDLE 0 #define AMDXDNA_INVALID_FENCE_HANDLE 0 +/* + * Define hardware context priority + */ +#define AMDXDNA_QOS_REALTIME_PRIORITY 0x100 +#define AMDXDNA_QOS_HIGH_PRIORITY 0x180 +#define AMDXDNA_QOS_NORMAL_PRIORITY 0x200 +#define AMDXDNA_QOS_LOW_PRIORITY 0x280 + enum amdxdna_device_type { AMDXDNA_DEV_TYPE_UNKNOWN = -1, AMDXDNA_DEV_TYPE_KMQ, diff --git a/include/uapi/drm/panthor_drm.h b/include/uapi/drm/panthor_drm.h index e238c6264fa1..b401ac585d6a 100644 --- a/include/uapi/drm/panthor_drm.h +++ b/include/uapi/drm/panthor_drm.h @@ -350,7 +350,7 @@ struct drm_panthor_gpu_info { __u32 as_present; /** - * @select_coherency: Coherency selected for this device. + * @selected_coherency: Coherency selected for this device. * * One of drm_panthor_gpu_coherency. */ |
