Path: blob/master/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c
26517 views
// SPDX-License-Identifier: GPL-2.0+12/*3* Copyright 2020 NXP4*/56#include <linux/firmware/imx/svc/misc.h>7#include <linux/media-bus-format.h>8#include <linux/mfd/syscon.h>9#include <linux/module.h>10#include <linux/of.h>11#include <linux/of_device.h>12#include <linux/of_graph.h>13#include <linux/platform_device.h>14#include <linux/pm_runtime.h>15#include <linux/regmap.h>1617#include <drm/drm_atomic_state_helper.h>18#include <drm/drm_bridge.h>19#include <drm/drm_of.h>20#include <drm/drm_print.h>2122#include <dt-bindings/firmware/imx/rsrc.h>2324#define PXL2DPI_CTRL 0x4025#define CFG1_16BIT 0x026#define CFG2_16BIT 0x127#define CFG3_16BIT 0x228#define CFG1_18BIT 0x329#define CFG2_18BIT 0x430#define CFG_24BIT 0x53132#define DRIVER_NAME "imx8qxp-pxl2dpi"3334struct imx8qxp_pxl2dpi {35struct regmap *regmap;36struct drm_bridge bridge;37struct drm_bridge *next_bridge;38struct drm_bridge *companion;39struct device *dev;40struct imx_sc_ipc *ipc_handle;41u32 sc_resource;42u32 in_bus_format;43u32 out_bus_format;44u32 pl_sel;45};4647#define bridge_to_p2d(b) container_of(b, struct imx8qxp_pxl2dpi, bridge)4849static int imx8qxp_pxl2dpi_bridge_attach(struct drm_bridge *bridge,50struct drm_encoder *encoder,51enum drm_bridge_attach_flags flags)52{53struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;5455if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {56DRM_DEV_ERROR(p2d->dev,57"do not support creating a drm_connector\n");58return -EINVAL;59}6061return drm_bridge_attach(encoder,62p2d->next_bridge, bridge,63DRM_BRIDGE_ATTACH_NO_CONNECTOR);64}6566static int67imx8qxp_pxl2dpi_bridge_atomic_check(struct drm_bridge *bridge,68struct drm_bridge_state *bridge_state,69struct drm_crtc_state *crtc_state,70struct drm_connector_state *conn_state)71{72struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;7374p2d->in_bus_format = bridge_state->input_bus_cfg.format;75p2d->out_bus_format = bridge_state->output_bus_cfg.format;7677return 0;78}7980static void81imx8qxp_pxl2dpi_bridge_mode_set(struct drm_bridge *bridge,82const struct drm_display_mode *mode,83const struct drm_display_mode *adjusted_mode)84{85struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;86struct imx8qxp_pxl2dpi *companion_p2d;87int ret;8889ret = pm_runtime_get_sync(p2d->dev);90if (ret < 0)91DRM_DEV_ERROR(p2d->dev,92"failed to get runtime PM sync: %d\n", ret);9394ret = imx_sc_misc_set_control(p2d->ipc_handle, p2d->sc_resource,95IMX_SC_C_PXL_LINK_SEL, p2d->pl_sel);96if (ret)97DRM_DEV_ERROR(p2d->dev,98"failed to set pixel link selection(%u): %d\n",99p2d->pl_sel, ret);100101switch (p2d->out_bus_format) {102case MEDIA_BUS_FMT_RGB888_1X24:103regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG_24BIT);104break;105case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:106regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG2_18BIT);107break;108default:109DRM_DEV_ERROR(p2d->dev,110"unsupported output bus format 0x%08x\n",111p2d->out_bus_format);112}113114if (p2d->companion) {115companion_p2d = bridge_to_p2d(p2d->companion);116117companion_p2d->in_bus_format = p2d->in_bus_format;118companion_p2d->out_bus_format = p2d->out_bus_format;119120p2d->companion->funcs->mode_set(p2d->companion, mode,121adjusted_mode);122}123}124125static void imx8qxp_pxl2dpi_bridge_atomic_disable(struct drm_bridge *bridge,126struct drm_atomic_state *state)127{128struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;129int ret;130131ret = pm_runtime_put(p2d->dev);132if (ret < 0)133DRM_DEV_ERROR(p2d->dev, "failed to put runtime PM: %d\n", ret);134135if (p2d->companion)136p2d->companion->funcs->atomic_disable(p2d->companion, state);137}138139static const u32 imx8qxp_pxl2dpi_bus_output_fmts[] = {140MEDIA_BUS_FMT_RGB888_1X24,141MEDIA_BUS_FMT_RGB666_1X24_CPADHI,142};143144static bool imx8qxp_pxl2dpi_bus_output_fmt_supported(u32 fmt)145{146int i;147148for (i = 0; i < ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts); i++) {149if (imx8qxp_pxl2dpi_bus_output_fmts[i] == fmt)150return true;151}152153return false;154}155156static u32 *157imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,158struct drm_bridge_state *bridge_state,159struct drm_crtc_state *crtc_state,160struct drm_connector_state *conn_state,161u32 output_fmt,162unsigned int *num_input_fmts)163{164u32 *input_fmts;165166if (!imx8qxp_pxl2dpi_bus_output_fmt_supported(output_fmt))167return NULL;168169*num_input_fmts = 1;170171input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);172if (!input_fmts)173return NULL;174175switch (output_fmt) {176case MEDIA_BUS_FMT_RGB888_1X24:177input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO;178break;179case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:180input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO;181break;182default:183kfree(input_fmts);184input_fmts = NULL;185break;186}187188return input_fmts;189}190191static u32 *192imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,193struct drm_bridge_state *bridge_state,194struct drm_crtc_state *crtc_state,195struct drm_connector_state *conn_state,196unsigned int *num_output_fmts)197{198*num_output_fmts = ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts);199return kmemdup(imx8qxp_pxl2dpi_bus_output_fmts,200sizeof(imx8qxp_pxl2dpi_bus_output_fmts), GFP_KERNEL);201}202203static const struct drm_bridge_funcs imx8qxp_pxl2dpi_bridge_funcs = {204.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,205.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,206.atomic_reset = drm_atomic_helper_bridge_reset,207.attach = imx8qxp_pxl2dpi_bridge_attach,208.atomic_check = imx8qxp_pxl2dpi_bridge_atomic_check,209.mode_set = imx8qxp_pxl2dpi_bridge_mode_set,210.atomic_disable = imx8qxp_pxl2dpi_bridge_atomic_disable,211.atomic_get_input_bus_fmts =212imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts,213.atomic_get_output_bus_fmts =214imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts,215};216217static struct device_node *218imx8qxp_pxl2dpi_get_available_ep_from_port(struct imx8qxp_pxl2dpi *p2d,219u32 port_id)220{221struct device_node *port, *ep;222int ep_cnt;223224port = of_graph_get_port_by_id(p2d->dev->of_node, port_id);225if (!port) {226DRM_DEV_ERROR(p2d->dev, "failed to get port@%u\n", port_id);227return ERR_PTR(-ENODEV);228}229230ep_cnt = of_get_available_child_count(port);231if (ep_cnt == 0) {232DRM_DEV_ERROR(p2d->dev, "no available endpoints of port@%u\n",233port_id);234ep = ERR_PTR(-ENODEV);235goto out;236} else if (ep_cnt > 1) {237DRM_DEV_ERROR(p2d->dev,238"invalid available endpoints of port@%u\n",239port_id);240ep = ERR_PTR(-EINVAL);241goto out;242}243244ep = of_get_next_available_child(port, NULL);245if (!ep) {246DRM_DEV_ERROR(p2d->dev,247"failed to get available endpoint of port@%u\n",248port_id);249ep = ERR_PTR(-ENODEV);250goto out;251}252out:253of_node_put(port);254return ep;255}256257static struct drm_bridge *258imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d)259{260struct device_node *ep, *remote;261struct drm_bridge *next_bridge;262int ret;263264ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 1);265if (IS_ERR(ep)) {266ret = PTR_ERR(ep);267return ERR_PTR(ret);268}269270remote = of_graph_get_remote_port_parent(ep);271if (!remote || !of_device_is_available(remote)) {272DRM_DEV_ERROR(p2d->dev, "no available remote\n");273next_bridge = ERR_PTR(-ENODEV);274goto out;275} else if (!of_device_is_available(remote->parent)) {276DRM_DEV_ERROR(p2d->dev, "remote parent is not available\n");277next_bridge = ERR_PTR(-ENODEV);278goto out;279}280281next_bridge = of_drm_find_bridge(remote);282if (!next_bridge) {283next_bridge = ERR_PTR(-EPROBE_DEFER);284goto out;285}286out:287of_node_put(remote);288of_node_put(ep);289290return next_bridge;291}292293static int imx8qxp_pxl2dpi_set_pixel_link_sel(struct imx8qxp_pxl2dpi *p2d)294{295struct device_node *ep;296struct of_endpoint endpoint;297int ret;298299ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 0);300if (IS_ERR(ep))301return PTR_ERR(ep);302303ret = of_graph_parse_endpoint(ep, &endpoint);304if (ret) {305DRM_DEV_ERROR(p2d->dev,306"failed to parse endpoint of port@0: %d\n", ret);307goto out;308}309310p2d->pl_sel = endpoint.id;311out:312of_node_put(ep);313314return ret;315}316317static int imx8qxp_pxl2dpi_parse_dt_companion(struct imx8qxp_pxl2dpi *p2d)318{319struct imx8qxp_pxl2dpi *companion_p2d;320struct device *dev = p2d->dev;321struct device_node *companion;322struct device_node *port1, *port2;323const struct of_device_id *match;324int dual_link;325int ret = 0;326327/* Locate the companion PXL2DPI for dual-link operation, if any. */328companion = of_parse_phandle(dev->of_node, "fsl,companion-pxl2dpi", 0);329if (!companion)330return 0;331332if (!of_device_is_available(companion)) {333DRM_DEV_ERROR(dev, "companion PXL2DPI is not available\n");334ret = -ENODEV;335goto out;336}337338/*339* Sanity check: the companion bridge must have the same compatible340* string.341*/342match = of_match_device(dev->driver->of_match_table, dev);343if (!of_device_is_compatible(companion, match->compatible)) {344DRM_DEV_ERROR(dev, "companion PXL2DPI is incompatible\n");345ret = -ENXIO;346goto out;347}348349p2d->companion = of_drm_find_bridge(companion);350if (!p2d->companion) {351ret = -EPROBE_DEFER;352DRM_DEV_DEBUG_DRIVER(p2d->dev,353"failed to find companion bridge: %d\n",354ret);355goto out;356}357358companion_p2d = bridge_to_p2d(p2d->companion);359360/*361* We need to work out if the sink is expecting us to function in362* dual-link mode. We do this by looking at the DT port nodes that363* the next bridges are connected to. If they are marked as expecting364* even pixels and odd pixels than we need to use the companion PXL2DPI.365*/366port1 = of_graph_get_port_by_id(p2d->next_bridge->of_node, 1);367port2 = of_graph_get_port_by_id(companion_p2d->next_bridge->of_node, 1);368dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);369of_node_put(port1);370of_node_put(port2);371372if (dual_link < 0) {373ret = dual_link;374DRM_DEV_ERROR(dev, "failed to get dual link pixel order: %d\n",375ret);376goto out;377}378379DRM_DEV_DEBUG_DRIVER(dev,380"dual-link configuration detected (companion bridge %pOF)\n",381companion);382out:383of_node_put(companion);384return ret;385}386387static int imx8qxp_pxl2dpi_bridge_probe(struct platform_device *pdev)388{389struct imx8qxp_pxl2dpi *p2d;390struct device *dev = &pdev->dev;391struct device_node *np = dev->of_node;392int ret;393394p2d = devm_drm_bridge_alloc(dev, struct imx8qxp_pxl2dpi, bridge,395&imx8qxp_pxl2dpi_bridge_funcs);396if (IS_ERR(p2d))397return PTR_ERR(p2d);398399p2d->regmap = syscon_node_to_regmap(np->parent);400if (IS_ERR(p2d->regmap)) {401ret = PTR_ERR(p2d->regmap);402if (ret != -EPROBE_DEFER)403DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret);404return ret;405}406407ret = imx_scu_get_handle(&p2d->ipc_handle);408if (ret) {409if (ret != -EPROBE_DEFER)410DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n",411ret);412return ret;413}414415p2d->dev = dev;416417ret = of_property_read_u32(np, "fsl,sc-resource", &p2d->sc_resource);418if (ret) {419DRM_DEV_ERROR(dev, "failed to get SC resource %d\n", ret);420return ret;421}422423p2d->next_bridge = imx8qxp_pxl2dpi_find_next_bridge(p2d);424if (IS_ERR(p2d->next_bridge)) {425ret = PTR_ERR(p2d->next_bridge);426if (ret != -EPROBE_DEFER)427DRM_DEV_ERROR(dev, "failed to find next bridge: %d\n",428ret);429return ret;430}431432ret = imx8qxp_pxl2dpi_set_pixel_link_sel(p2d);433if (ret)434return ret;435436ret = imx8qxp_pxl2dpi_parse_dt_companion(p2d);437if (ret)438return ret;439440platform_set_drvdata(pdev, p2d);441pm_runtime_enable(dev);442443p2d->bridge.driver_private = p2d;444p2d->bridge.of_node = np;445446drm_bridge_add(&p2d->bridge);447448return ret;449}450451static void imx8qxp_pxl2dpi_bridge_remove(struct platform_device *pdev)452{453struct imx8qxp_pxl2dpi *p2d = platform_get_drvdata(pdev);454455drm_bridge_remove(&p2d->bridge);456457pm_runtime_disable(&pdev->dev);458}459460static const struct of_device_id imx8qxp_pxl2dpi_dt_ids[] = {461{ .compatible = "fsl,imx8qxp-pxl2dpi", },462{ /* sentinel */ }463};464MODULE_DEVICE_TABLE(of, imx8qxp_pxl2dpi_dt_ids);465466static struct platform_driver imx8qxp_pxl2dpi_bridge_driver = {467.probe = imx8qxp_pxl2dpi_bridge_probe,468.remove = imx8qxp_pxl2dpi_bridge_remove,469.driver = {470.of_match_table = imx8qxp_pxl2dpi_dt_ids,471.name = DRIVER_NAME,472},473};474module_platform_driver(imx8qxp_pxl2dpi_bridge_driver);475476MODULE_DESCRIPTION("i.MX8QXP pixel link to DPI bridge driver");477MODULE_AUTHOR("Liu Ying <[email protected]>");478MODULE_LICENSE("GPL v2");479MODULE_ALIAS("platform:" DRIVER_NAME);480481482