// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2022-2025 NXP */ #include #include #include #include #include #include #include #include #include #include #include #define IMX93_DISPLAY_MUX_REG 0x60 #define PARALLEL_DISP_FORMAT GENMASK(10, 8) #define FORMAT_RGB888_TO_RGB888 FIELD_PREP(PARALLEL_DISP_FORMAT, 0) #define FORMAT_RGB888_TO_RGB666 FIELD_PREP(PARALLEL_DISP_FORMAT, 1) #define FORMAT_RGB565_TO_RGB565 FIELD_PREP(PARALLEL_DISP_FORMAT, 2) struct imx93_pdfc { struct drm_bridge bridge; struct device *dev; struct regmap *regmap; u32 phy_bus_width; }; static struct imx93_pdfc *bridge_to_imx93_pdfc(struct drm_bridge *bridge) { return container_of(bridge, struct imx93_pdfc, bridge); } static int imx93_pdfc_bridge_attach(struct drm_bridge *bridge, struct drm_encoder *encoder, enum drm_bridge_attach_flags flags) { return drm_bridge_attach(bridge->encoder, bridge->next_bridge, bridge, flags); } static void imx93_pdfc_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_commit *state) { struct imx93_pdfc *pdfc = bridge_to_imx93_pdfc(bridge); const struct drm_bridge_state *bridge_state; unsigned int mask = PARALLEL_DISP_FORMAT; unsigned int val; bridge_state = drm_atomic_get_new_bridge_state(state, bridge); switch (bridge_state->output_bus_cfg.format) { case MEDIA_BUS_FMT_RGB888_1X24: case MEDIA_BUS_FMT_FIXED: val = FORMAT_RGB888_TO_RGB888; if (pdfc->phy_bus_width == 18) { /* * Can be valid if physical bus limitation exists, * therefore use dev_dbg(). */ dev_dbg(pdfc->dev, "Truncate two LSBs from each color\n"); val = FORMAT_RGB888_TO_RGB666; } break; case MEDIA_BUS_FMT_RGB666_1X18: val = FORMAT_RGB888_TO_RGB666; break; case MEDIA_BUS_FMT_RGB565_1X16: val = FORMAT_RGB565_TO_RGB565; break; } regmap_update_bits(pdfc->regmap, IMX93_DISPLAY_MUX_REG, mask, val); } /* TODO: Add YUV formats */ static const u32 imx93_pdfc_bus_output_fmts[] = { MEDIA_BUS_FMT_FIXED, MEDIA_BUS_FMT_RGB888_1X24, MEDIA_BUS_FMT_RGB666_1X18, MEDIA_BUS_FMT_RGB565_1X16, }; static bool imx93_pdfc_bus_output_fmt_supported(u32 fmt) { int i; for (i = 0; i < ARRAY_SIZE(imx93_pdfc_bus_output_fmts); i++) { if (imx93_pdfc_bus_output_fmts[i] == fmt) return true; } return false; } static u32 * imx93_pdfc_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, struct drm_bridge_state *bridge_state, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state, u32 output_fmt, unsigned int *num_input_fmts) { u32 *input_fmts; *num_input_fmts = 0; if (!imx93_pdfc_bus_output_fmt_supported(output_fmt)) return NULL; input_fmts = kmalloc_obj(*input_fmts); if (!input_fmts) return NULL; *num_input_fmts = 1; switch (output_fmt) { case MEDIA_BUS_FMT_RGB888_1X24: case MEDIA_BUS_FMT_RGB565_1X16: input_fmts[0] = output_fmt; break; case MEDIA_BUS_FMT_RGB666_1X18: case MEDIA_BUS_FMT_FIXED: input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; break; } return input_fmts; } static int imx93_pdfc_bridge_atomic_check(struct drm_bridge *bridge, struct drm_bridge_state *bridge_state, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { struct imx93_pdfc *pdfc = bridge_to_imx93_pdfc(bridge); u32 format = bridge_state->output_bus_cfg.format; if (imx93_pdfc_bus_output_fmt_supported(format)) return 0; dev_warn(pdfc->dev, "Unsupported output bus format: 0x%x\n", format); return -EINVAL; } static const struct drm_bridge_funcs funcs = { .attach = imx93_pdfc_bridge_attach, .atomic_enable = imx93_pdfc_bridge_atomic_enable, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_get_input_bus_fmts = imx93_pdfc_bridge_atomic_get_input_bus_fmts, .atomic_check = imx93_pdfc_bridge_atomic_check, .atomic_create_state = drm_atomic_helper_bridge_create_state, }; static int imx93_pdfc_bridge_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct drm_bridge *next_bridge; struct imx93_pdfc *pdfc; struct device_node *ep; int err; pdfc = devm_drm_bridge_alloc(dev, struct imx93_pdfc, bridge, &funcs); if (IS_ERR(pdfc)) return PTR_ERR(pdfc); pdfc->regmap = syscon_node_to_regmap(dev->of_node->parent); if (IS_ERR(pdfc->regmap)) return dev_err_probe(dev, PTR_ERR(pdfc->regmap), "failed to get regmap\n"); /* No limits per default */ pdfc->phy_bus_width = 24; /* Get output ep (port1/endpoint) */ ep = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1); if (ep) { err = of_property_read_u32(ep, "bus-width", &pdfc->phy_bus_width); of_node_put(ep); /* bus-width is optional but it must have valid data if present */ if (err && err != -EINVAL) return dev_err_probe(dev, err, "failed to query bus-width\n"); } next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0); if (IS_ERR(next_bridge)) return dev_err_probe(dev, PTR_ERR(next_bridge), "failed to get next bridge\n"); pdfc->dev = dev; pdfc->bridge.of_node = dev->of_node; pdfc->bridge.type = DRM_MODE_CONNECTOR_DPI; pdfc->bridge.next_bridge = drm_bridge_get(next_bridge); return devm_drm_bridge_add(dev, &pdfc->bridge); } static const struct of_device_id imx93_pdfc_dt_ids[] = { { .compatible = "nxp,imx91-pdfc", }, { .compatible = "nxp,imx93-pdfc", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx93_pdfc_dt_ids); static struct platform_driver imx93_pdfc_bridge_driver = { .probe = imx93_pdfc_bridge_probe, .driver = { .of_match_table = imx93_pdfc_dt_ids, .name = "imx93_pdfc", }, }; module_platform_driver(imx93_pdfc_bridge_driver); MODULE_DESCRIPTION("NXP i.MX93 parallel display format configuration driver"); MODULE_AUTHOR("Liu Ying "); MODULE_LICENSE("GPL");