Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm: bridge: Add waveshare DSI2DPI unit driver

Waveshare touchscreen consists of a DPI panel and a driver board.
The waveshare driver board consists of ICN6211 and a MCU to
convert DSI to DPI and control the backlight.
This driver treats the MCU and ICN6211 board as a whole unit.
It can support all resolution waveshare DSI2DPI based panel,
the timing table should come from 'panel-dpi' panel in the device tree.

Signed-off-by: Joseph Guo <qijian.guo@nxp.com>
Suggested-by: Liu Ying <victor.liu@nxp.com>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://lore.kernel.org/r/20250806-waveshare-v3-3-fd28e01f064f@nxp.com

authored by

Joseph Guo and committed by
Neil Armstrong
dbdea37a 80b0eb11

+216
+12
drivers/gpu/drm/bridge/Kconfig
··· 451 451 Texas Instruments TPD12S015 HDMI level shifter and ESD protection 452 452 driver. 453 453 454 + config DRM_WAVESHARE_BRIDGE 455 + tristate "Waveshare DSI bridge" 456 + depends on OF 457 + depends on BACKLIGHT_CLASS_DEVICE 458 + select DRM_PANEL_BRIDGE 459 + select DRM_KMS_HELPER 460 + select DRM_MIPI_DSI 461 + select REGMAP_I2C 462 + help 463 + Driver for waveshare DSI to DPI bridge board. 464 + Please say Y if you have such hardware 465 + 454 466 source "drivers/gpu/drm/bridge/analogix/Kconfig" 455 467 456 468 source "drivers/gpu/drm/bridge/adv7511/Kconfig"
+1
drivers/gpu/drm/bridge/Makefile
··· 41 41 obj-$(CONFIG_DRM_TI_TDP158) += ti-tdp158.o 42 42 obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o 43 43 obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o 44 + obj-$(CONFIG_DRM_WAVESHARE_BRIDGE) += waveshare-dsi.o 44 45 obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi.o 45 46 obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o 46 47
+203
drivers/gpu/drm/bridge/waveshare-dsi.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright 2025 NXP 4 + * Based on panel-raspberrypi-touchscreen by Broadcom 5 + */ 6 + 7 + #include <linux/backlight.h> 8 + #include <linux/err.h> 9 + #include <linux/i2c.h> 10 + #include <linux/module.h> 11 + #include <linux/of.h> 12 + #include <linux/of_graph.h> 13 + #include <linux/regmap.h> 14 + 15 + #include <drm/drm_bridge.h> 16 + #include <drm/drm_mipi_dsi.h> 17 + #include <drm/drm_of.h> 18 + #include <drm/drm_panel.h> 19 + 20 + struct ws_bridge { 21 + struct drm_bridge bridge; 22 + struct drm_bridge *next_bridge; 23 + struct backlight_device *backlight; 24 + struct device *dev; 25 + struct regmap *reg_map; 26 + }; 27 + 28 + static const struct regmap_config ws_regmap_config = { 29 + .reg_bits = 8, 30 + .val_bits = 8, 31 + .max_register = 0xff, 32 + }; 33 + 34 + static struct ws_bridge *bridge_to_ws_bridge(struct drm_bridge *bridge) 35 + { 36 + return container_of(bridge, struct ws_bridge, bridge); 37 + } 38 + 39 + static int ws_bridge_attach_dsi(struct ws_bridge *ws) 40 + { 41 + const struct mipi_dsi_device_info info = { 42 + .type = "ws-bridge", 43 + .channel = 0, 44 + .node = NULL, 45 + }; 46 + struct device_node *dsi_host_node; 47 + struct device *dev = ws->dev; 48 + struct mipi_dsi_device *dsi; 49 + struct mipi_dsi_host *host; 50 + int ret; 51 + 52 + dsi_host_node = of_graph_get_remote_node(dev->of_node, 0, 0); 53 + if (!dsi_host_node) { 54 + dev_err(dev, "Failed to get remote port\n"); 55 + return -ENODEV; 56 + } 57 + host = of_find_mipi_dsi_host_by_node(dsi_host_node); 58 + of_node_put(dsi_host_node); 59 + if (!host) 60 + return dev_err_probe(dev, -EPROBE_DEFER, "Failed to find dsi_host\n"); 61 + 62 + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); 63 + if (IS_ERR(dsi)) 64 + return dev_err_probe(dev, PTR_ERR(dsi), "Failed to create dsi device\n"); 65 + 66 + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | 67 + MIPI_DSI_CLOCK_NON_CONTINUOUS; 68 + dsi->format = MIPI_DSI_FMT_RGB888; 69 + dsi->lanes = 2; 70 + 71 + ret = devm_mipi_dsi_attach(dev, dsi); 72 + if (ret < 0) 73 + return dev_err_probe(dev, ret, "Failed to attach dsi to host\n"); 74 + 75 + return 0; 76 + } 77 + 78 + static int ws_bridge_bridge_attach(struct drm_bridge *bridge, 79 + struct drm_encoder *encoder, 80 + enum drm_bridge_attach_flags flags) 81 + { 82 + struct ws_bridge *ws = bridge_to_ws_bridge(bridge); 83 + int ret; 84 + 85 + ret = ws_bridge_attach_dsi(ws); 86 + if (ret) 87 + return ret; 88 + 89 + return drm_bridge_attach(encoder, ws->next_bridge, 90 + &ws->bridge, flags); 91 + } 92 + 93 + static void ws_bridge_bridge_enable(struct drm_bridge *bridge) 94 + { 95 + struct ws_bridge *ws = bridge_to_ws_bridge(bridge); 96 + 97 + regmap_write(ws->reg_map, 0xad, 0x01); 98 + backlight_enable(ws->backlight); 99 + } 100 + 101 + static void ws_bridge_bridge_disable(struct drm_bridge *bridge) 102 + { 103 + struct ws_bridge *ws = bridge_to_ws_bridge(bridge); 104 + 105 + backlight_disable(ws->backlight); 106 + regmap_write(ws->reg_map, 0xad, 0x00); 107 + } 108 + 109 + static const struct drm_bridge_funcs ws_bridge_bridge_funcs = { 110 + .enable = ws_bridge_bridge_enable, 111 + .disable = ws_bridge_bridge_disable, 112 + .attach = ws_bridge_bridge_attach, 113 + }; 114 + 115 + static int ws_bridge_bl_update_status(struct backlight_device *bl) 116 + { 117 + struct ws_bridge *ws = bl_get_data(bl); 118 + 119 + regmap_write(ws->reg_map, 0xab, 0xff - backlight_get_brightness(bl)); 120 + regmap_write(ws->reg_map, 0xaa, 0x01); 121 + 122 + return 0; 123 + } 124 + 125 + static const struct backlight_ops ws_bridge_bl_ops = { 126 + .update_status = ws_bridge_bl_update_status, 127 + }; 128 + 129 + static struct backlight_device *ws_bridge_create_backlight(struct ws_bridge *ws) 130 + { 131 + const struct backlight_properties props = { 132 + .type = BACKLIGHT_RAW, 133 + .brightness = 255, 134 + .max_brightness = 255, 135 + }; 136 + struct device *dev = ws->dev; 137 + 138 + return devm_backlight_device_register(dev, dev_name(dev), dev, ws, 139 + &ws_bridge_bl_ops, &props); 140 + } 141 + 142 + static int ws_bridge_probe(struct i2c_client *i2c) 143 + { 144 + struct device *dev = &i2c->dev; 145 + struct drm_panel *panel; 146 + struct ws_bridge *ws; 147 + int ret; 148 + 149 + ws = devm_drm_bridge_alloc(dev, struct ws_bridge, bridge, &ws_bridge_bridge_funcs); 150 + if (!ws) 151 + return -ENOMEM; 152 + 153 + ws->dev = dev; 154 + 155 + ws->reg_map = devm_regmap_init_i2c(i2c, &ws_regmap_config); 156 + if (IS_ERR(ws->reg_map)) 157 + return dev_err_probe(dev, PTR_ERR(ws->reg_map), "Failed to allocate regmap\n"); 158 + 159 + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, &panel, NULL); 160 + if (ret) 161 + return dev_err_probe(dev, ret, "Failed to find remote panel\n"); 162 + 163 + ws->next_bridge = devm_drm_panel_bridge_add(dev, panel); 164 + if (IS_ERR(ws->next_bridge)) 165 + return PTR_ERR(ws->next_bridge); 166 + 167 + ws->backlight = ws_bridge_create_backlight(ws); 168 + if (IS_ERR(ws->backlight)) { 169 + ret = PTR_ERR(ws->backlight); 170 + dev_err(dev, "Failed to create backlight: %d\n", ret); 171 + return ret; 172 + } 173 + 174 + regmap_write(ws->reg_map, 0xc0, 0x01); 175 + regmap_write(ws->reg_map, 0xc2, 0x01); 176 + regmap_write(ws->reg_map, 0xac, 0x01); 177 + 178 + ws->bridge.type = DRM_MODE_CONNECTOR_DPI; 179 + ws->bridge.of_node = dev->of_node; 180 + devm_drm_bridge_add(dev, &ws->bridge); 181 + 182 + return 0; 183 + } 184 + 185 + static const struct of_device_id ws_bridge_of_ids[] = { 186 + {.compatible = "waveshare,dsi2dpi",}, 187 + { } 188 + }; 189 + 190 + MODULE_DEVICE_TABLE(of, ws_bridge_of_ids); 191 + 192 + static struct i2c_driver ws_bridge_driver = { 193 + .driver = { 194 + .name = "ws_dsi2dpi", 195 + .of_match_table = ws_bridge_of_ids, 196 + }, 197 + .probe = ws_bridge_probe, 198 + }; 199 + module_i2c_driver(ws_bridge_driver); 200 + 201 + MODULE_AUTHOR("Joseph Guo <qijian.guo@nxp.com>"); 202 + MODULE_DESCRIPTION("Waveshare DSI2DPI bridge driver"); 203 + MODULE_LICENSE("GPL");