Path: blob/master/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c
52524 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 *companion;38struct device *dev;39struct imx_sc_ipc *ipc_handle;40u32 sc_resource;41u32 in_bus_format;42u32 out_bus_format;43u32 pl_sel;44};4546#define bridge_to_p2d(b) container_of(b, struct imx8qxp_pxl2dpi, bridge)4748static int imx8qxp_pxl2dpi_bridge_attach(struct drm_bridge *bridge,49struct drm_encoder *encoder,50enum drm_bridge_attach_flags flags)51{52struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;5354if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {55DRM_DEV_ERROR(p2d->dev,56"do not support creating a drm_connector\n");57return -EINVAL;58}5960return drm_bridge_attach(encoder,61p2d->bridge.next_bridge, bridge,62DRM_BRIDGE_ATTACH_NO_CONNECTOR);63}6465static void imx8qxp_pxl2dpi_bridge_destroy(struct drm_bridge *bridge)66{67struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;6869if (!p2d)70return;7172drm_bridge_put(p2d->companion);73}7475static int76imx8qxp_pxl2dpi_bridge_atomic_check(struct drm_bridge *bridge,77struct drm_bridge_state *bridge_state,78struct drm_crtc_state *crtc_state,79struct drm_connector_state *conn_state)80{81struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;8283p2d->in_bus_format = bridge_state->input_bus_cfg.format;84p2d->out_bus_format = bridge_state->output_bus_cfg.format;8586return 0;87}8889static void90imx8qxp_pxl2dpi_bridge_mode_set(struct drm_bridge *bridge,91const struct drm_display_mode *mode,92const struct drm_display_mode *adjusted_mode)93{94struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;95struct imx8qxp_pxl2dpi *companion_p2d;96int ret;9798ret = pm_runtime_get_sync(p2d->dev);99if (ret < 0)100DRM_DEV_ERROR(p2d->dev,101"failed to get runtime PM sync: %d\n", ret);102103ret = imx_sc_misc_set_control(p2d->ipc_handle, p2d->sc_resource,104IMX_SC_C_PXL_LINK_SEL, p2d->pl_sel);105if (ret)106DRM_DEV_ERROR(p2d->dev,107"failed to set pixel link selection(%u): %d\n",108p2d->pl_sel, ret);109110switch (p2d->out_bus_format) {111case MEDIA_BUS_FMT_RGB888_1X24:112regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG_24BIT);113break;114case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:115regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG2_18BIT);116break;117default:118DRM_DEV_ERROR(p2d->dev,119"unsupported output bus format 0x%08x\n",120p2d->out_bus_format);121}122123if (p2d->companion) {124companion_p2d = bridge_to_p2d(p2d->companion);125126companion_p2d->in_bus_format = p2d->in_bus_format;127companion_p2d->out_bus_format = p2d->out_bus_format;128129p2d->companion->funcs->mode_set(p2d->companion, mode,130adjusted_mode);131}132}133134static void imx8qxp_pxl2dpi_bridge_atomic_disable(struct drm_bridge *bridge,135struct drm_atomic_state *state)136{137struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;138139pm_runtime_put(p2d->dev);140141if (p2d->companion)142p2d->companion->funcs->atomic_disable(p2d->companion, state);143}144145static const u32 imx8qxp_pxl2dpi_bus_output_fmts[] = {146MEDIA_BUS_FMT_RGB888_1X24,147MEDIA_BUS_FMT_RGB666_1X24_CPADHI,148};149150static bool imx8qxp_pxl2dpi_bus_output_fmt_supported(u32 fmt)151{152int i;153154for (i = 0; i < ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts); i++) {155if (imx8qxp_pxl2dpi_bus_output_fmts[i] == fmt)156return true;157}158159return false;160}161162static u32 *163imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,164struct drm_bridge_state *bridge_state,165struct drm_crtc_state *crtc_state,166struct drm_connector_state *conn_state,167u32 output_fmt,168unsigned int *num_input_fmts)169{170u32 *input_fmts;171172if (!imx8qxp_pxl2dpi_bus_output_fmt_supported(output_fmt))173return NULL;174175*num_input_fmts = 1;176177input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);178if (!input_fmts)179return NULL;180181switch (output_fmt) {182case MEDIA_BUS_FMT_RGB888_1X24:183input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO;184break;185case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:186input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO;187break;188default:189kfree(input_fmts);190input_fmts = NULL;191break;192}193194return input_fmts;195}196197static u32 *198imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,199struct drm_bridge_state *bridge_state,200struct drm_crtc_state *crtc_state,201struct drm_connector_state *conn_state,202unsigned int *num_output_fmts)203{204*num_output_fmts = ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts);205return kmemdup(imx8qxp_pxl2dpi_bus_output_fmts,206sizeof(imx8qxp_pxl2dpi_bus_output_fmts), GFP_KERNEL);207}208209static const struct drm_bridge_funcs imx8qxp_pxl2dpi_bridge_funcs = {210.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,211.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,212.atomic_reset = drm_atomic_helper_bridge_reset,213.attach = imx8qxp_pxl2dpi_bridge_attach,214.destroy = imx8qxp_pxl2dpi_bridge_destroy,215.atomic_check = imx8qxp_pxl2dpi_bridge_atomic_check,216.mode_set = imx8qxp_pxl2dpi_bridge_mode_set,217.atomic_disable = imx8qxp_pxl2dpi_bridge_atomic_disable,218.atomic_get_input_bus_fmts =219imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts,220.atomic_get_output_bus_fmts =221imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts,222};223224static struct device_node *225imx8qxp_pxl2dpi_get_available_ep_from_port(struct imx8qxp_pxl2dpi *p2d,226u32 port_id)227{228struct device_node *port, *ep;229int ep_cnt;230231port = of_graph_get_port_by_id(p2d->dev->of_node, port_id);232if (!port) {233DRM_DEV_ERROR(p2d->dev, "failed to get port@%u\n", port_id);234return ERR_PTR(-ENODEV);235}236237ep_cnt = of_get_available_child_count(port);238if (ep_cnt == 0) {239DRM_DEV_ERROR(p2d->dev, "no available endpoints of port@%u\n",240port_id);241ep = ERR_PTR(-ENODEV);242goto out;243} else if (ep_cnt > 1) {244DRM_DEV_ERROR(p2d->dev,245"invalid available endpoints of port@%u\n",246port_id);247ep = ERR_PTR(-EINVAL);248goto out;249}250251ep = of_get_next_available_child(port, NULL);252if (!ep) {253DRM_DEV_ERROR(p2d->dev,254"failed to get available endpoint of port@%u\n",255port_id);256ep = ERR_PTR(-ENODEV);257goto out;258}259out:260of_node_put(port);261return ep;262}263264static int imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d)265{266struct device_node *ep __free(device_node) =267imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 1);268if (IS_ERR(ep))269return PTR_ERR(ep);270271struct device_node *remote __free(device_node) = of_graph_get_remote_port_parent(ep);272if (!remote || !of_device_is_available(remote)) {273DRM_DEV_ERROR(p2d->dev, "no available remote\n");274return -ENODEV;275} else if (!of_device_is_available(remote->parent)) {276DRM_DEV_ERROR(p2d->dev, "remote parent is not available\n");277return -ENODEV;278}279280p2d->bridge.next_bridge = of_drm_find_and_get_bridge(remote);281if (!p2d->bridge.next_bridge)282return -EPROBE_DEFER;283284return 0;285}286287static int imx8qxp_pxl2dpi_set_pixel_link_sel(struct imx8qxp_pxl2dpi *p2d)288{289struct device_node *ep;290struct of_endpoint endpoint;291int ret;292293ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 0);294if (IS_ERR(ep))295return PTR_ERR(ep);296297ret = of_graph_parse_endpoint(ep, &endpoint);298if (ret) {299DRM_DEV_ERROR(p2d->dev,300"failed to parse endpoint of port@0: %d\n", ret);301goto out;302}303304p2d->pl_sel = endpoint.id;305out:306of_node_put(ep);307308return ret;309}310311static int imx8qxp_pxl2dpi_parse_dt_companion(struct imx8qxp_pxl2dpi *p2d)312{313struct imx8qxp_pxl2dpi *companion_p2d;314struct device *dev = p2d->dev;315struct device_node *companion;316struct device_node *port1, *port2;317const struct of_device_id *match;318int dual_link;319int ret = 0;320321/* Locate the companion PXL2DPI for dual-link operation, if any. */322companion = of_parse_phandle(dev->of_node, "fsl,companion-pxl2dpi", 0);323if (!companion)324return 0;325326if (!of_device_is_available(companion)) {327DRM_DEV_ERROR(dev, "companion PXL2DPI is not available\n");328ret = -ENODEV;329goto out;330}331332/*333* Sanity check: the companion bridge must have the same compatible334* string.335*/336match = of_match_device(dev->driver->of_match_table, dev);337if (!of_device_is_compatible(companion, match->compatible)) {338DRM_DEV_ERROR(dev, "companion PXL2DPI is incompatible\n");339ret = -ENXIO;340goto out;341}342343p2d->companion = of_drm_find_and_get_bridge(companion);344if (!p2d->companion) {345ret = -EPROBE_DEFER;346DRM_DEV_DEBUG_DRIVER(p2d->dev,347"failed to find companion bridge: %d\n",348ret);349goto out;350}351352companion_p2d = bridge_to_p2d(p2d->companion);353354/*355* We need to work out if the sink is expecting us to function in356* dual-link mode. We do this by looking at the DT port nodes that357* the next bridges are connected to. If they are marked as expecting358* even pixels and odd pixels than we need to use the companion PXL2DPI.359*/360port1 = of_graph_get_port_by_id(p2d->bridge.next_bridge->of_node, 1);361port2 = of_graph_get_port_by_id(companion_p2d->bridge.next_bridge->of_node, 1);362dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);363of_node_put(port1);364of_node_put(port2);365366if (dual_link < 0) {367ret = dual_link;368DRM_DEV_ERROR(dev, "failed to get dual link pixel order: %d\n",369ret);370goto out;371}372373DRM_DEV_DEBUG_DRIVER(dev,374"dual-link configuration detected (companion bridge %pOF)\n",375companion);376out:377of_node_put(companion);378return ret;379}380381static int imx8qxp_pxl2dpi_bridge_probe(struct platform_device *pdev)382{383struct imx8qxp_pxl2dpi *p2d;384struct device *dev = &pdev->dev;385struct device_node *np = dev->of_node;386int ret;387388p2d = devm_drm_bridge_alloc(dev, struct imx8qxp_pxl2dpi, bridge,389&imx8qxp_pxl2dpi_bridge_funcs);390if (IS_ERR(p2d))391return PTR_ERR(p2d);392393p2d->regmap = syscon_node_to_regmap(np->parent);394if (IS_ERR(p2d->regmap)) {395ret = PTR_ERR(p2d->regmap);396if (ret != -EPROBE_DEFER)397DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret);398return ret;399}400401ret = imx_scu_get_handle(&p2d->ipc_handle);402if (ret) {403if (ret != -EPROBE_DEFER)404DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n",405ret);406return ret;407}408409p2d->dev = dev;410411ret = of_property_read_u32(np, "fsl,sc-resource", &p2d->sc_resource);412if (ret) {413DRM_DEV_ERROR(dev, "failed to get SC resource %d\n", ret);414return ret;415}416417ret = imx8qxp_pxl2dpi_find_next_bridge(p2d);418if (ret)419return ret;420421ret = imx8qxp_pxl2dpi_set_pixel_link_sel(p2d);422if (ret)423return ret;424425ret = imx8qxp_pxl2dpi_parse_dt_companion(p2d);426if (ret)427return ret;428429platform_set_drvdata(pdev, p2d);430pm_runtime_enable(dev);431432p2d->bridge.driver_private = p2d;433p2d->bridge.of_node = np;434435drm_bridge_add(&p2d->bridge);436437return ret;438}439440static void imx8qxp_pxl2dpi_bridge_remove(struct platform_device *pdev)441{442struct imx8qxp_pxl2dpi *p2d = platform_get_drvdata(pdev);443444drm_bridge_remove(&p2d->bridge);445446pm_runtime_disable(&pdev->dev);447}448449static const struct of_device_id imx8qxp_pxl2dpi_dt_ids[] = {450{ .compatible = "fsl,imx8qxp-pxl2dpi", },451{ /* sentinel */ }452};453MODULE_DEVICE_TABLE(of, imx8qxp_pxl2dpi_dt_ids);454455static struct platform_driver imx8qxp_pxl2dpi_bridge_driver = {456.probe = imx8qxp_pxl2dpi_bridge_probe,457.remove = imx8qxp_pxl2dpi_bridge_remove,458.driver = {459.of_match_table = imx8qxp_pxl2dpi_dt_ids,460.name = DRIVER_NAME,461},462};463module_platform_driver(imx8qxp_pxl2dpi_bridge_driver);464465MODULE_DESCRIPTION("i.MX8QXP pixel link to DPI bridge driver");466MODULE_AUTHOR("Liu Ying <[email protected]>");467MODULE_LICENSE("GPL v2");468MODULE_ALIAS("platform:" DRIVER_NAME);469470471