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_atomic_state_helper.h> 16 #include <drm/drm_bridge.h> 17 #include <drm/drm_mipi_dsi.h> 18 #include <drm/drm_of.h> 19 #include <drm/drm_panel.h> 20 21 struct ws_bridge { 22 struct drm_bridge bridge; 23 struct drm_bridge *next_bridge; 24 struct backlight_device *backlight; 25 struct device *dev; 26 struct regmap *reg_map; 27 }; 28 29 static const struct regmap_config ws_regmap_config = { 30 .reg_bits = 8, 31 .val_bits = 8, 32 .max_register = 0xff, 33 }; 34 35 static struct ws_bridge *bridge_to_ws_bridge(struct drm_bridge *bridge) 36 { 37 return container_of(bridge, struct ws_bridge, bridge); 38 } 39 40 static int ws_bridge_attach_dsi(struct ws_bridge *ws) 41 { 42 const struct mipi_dsi_device_info info = { 43 .type = "ws-bridge", 44 .channel = 0, 45 .node = NULL, 46 }; 47 struct device_node *dsi_host_node; 48 struct device *dev = ws->dev; 49 struct mipi_dsi_device *dsi; 50 struct mipi_dsi_host *host; 51 int ret; 52 53 dsi_host_node = of_graph_get_remote_node(dev->of_node, 0, 0); 54 if (!dsi_host_node) { 55 dev_err(dev, "Failed to get remote port\n"); 56 return -ENODEV; 57 } 58 host = of_find_mipi_dsi_host_by_node(dsi_host_node); 59 of_node_put(dsi_host_node); 60 if (!host) 61 return dev_err_probe(dev, -EPROBE_DEFER, "Failed to find dsi_host\n"); 62 63 dsi = devm_mipi_dsi_device_register_full(dev, host, &info); 64 if (IS_ERR(dsi)) 65 return dev_err_probe(dev, PTR_ERR(dsi), "Failed to create dsi device\n"); 66 67 dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | 68 MIPI_DSI_CLOCK_NON_CONTINUOUS; 69 dsi->format = MIPI_DSI_FMT_RGB888; 70 ret = drm_of_get_data_lanes_count_ep(dev->of_node, 0, 0, 1, 4); 71 if (ret < 0) { 72 dev_warn(dev, "Invalid or missing DSI lane count %d, falling back to 2 lanes\n", 73 ret); 74 dsi->lanes = 2; /* Old DT backward compatibility */ 75 } else { 76 dsi->lanes = ret; 77 } 78 79 ret = devm_mipi_dsi_attach(dev, dsi); 80 if (ret < 0) 81 return dev_err_probe(dev, ret, "Failed to attach dsi to host\n"); 82 83 return 0; 84 } 85 86 static int ws_bridge_bridge_attach(struct drm_bridge *bridge, 87 struct drm_encoder *encoder, 88 enum drm_bridge_attach_flags flags) 89 { 90 struct ws_bridge *ws = bridge_to_ws_bridge(bridge); 91 92 return drm_bridge_attach(encoder, ws->next_bridge, 93 &ws->bridge, flags); 94 } 95 96 static void ws_bridge_bridge_enable(struct drm_bridge *bridge, 97 struct drm_atomic_commit *commit) 98 { 99 struct ws_bridge *ws = bridge_to_ws_bridge(bridge); 100 101 regmap_write(ws->reg_map, 0xad, 0x01); 102 backlight_enable(ws->backlight); 103 } 104 105 static void ws_bridge_bridge_disable(struct drm_bridge *bridge, 106 struct drm_atomic_commit *commit) 107 { 108 struct ws_bridge *ws = bridge_to_ws_bridge(bridge); 109 110 backlight_disable(ws->backlight); 111 regmap_write(ws->reg_map, 0xad, 0x00); 112 } 113 114 static const struct drm_bridge_funcs ws_bridge_bridge_funcs = { 115 .atomic_create_state = drm_atomic_helper_bridge_create_state, 116 .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, 117 .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, 118 .atomic_enable = ws_bridge_bridge_enable, 119 .atomic_disable = ws_bridge_bridge_disable, 120 .attach = ws_bridge_bridge_attach, 121 }; 122 123 static int ws_bridge_bl_update_status(struct backlight_device *bl) 124 { 125 struct ws_bridge *ws = bl_get_data(bl); 126 127 regmap_write(ws->reg_map, 0xab, 0xff - backlight_get_brightness(bl)); 128 regmap_write(ws->reg_map, 0xaa, 0x01); 129 130 return 0; 131 } 132 133 static const struct backlight_ops ws_bridge_bl_ops = { 134 .update_status = ws_bridge_bl_update_status, 135 }; 136 137 static struct backlight_device *ws_bridge_create_backlight(struct ws_bridge *ws) 138 { 139 const struct backlight_properties props = { 140 .type = BACKLIGHT_RAW, 141 .brightness = 255, 142 .max_brightness = 255, 143 }; 144 struct device *dev = ws->dev; 145 146 return devm_backlight_device_register(dev, dev_name(dev), dev, ws, 147 &ws_bridge_bl_ops, &props); 148 } 149 150 static int ws_bridge_probe(struct i2c_client *i2c) 151 { 152 struct device *dev = &i2c->dev; 153 struct drm_panel *panel; 154 struct ws_bridge *ws; 155 int ret; 156 157 ws = devm_drm_bridge_alloc(dev, struct ws_bridge, bridge, &ws_bridge_bridge_funcs); 158 if (IS_ERR(ws)) 159 return PTR_ERR(ws); 160 161 ws->dev = dev; 162 163 ws->reg_map = devm_regmap_init_i2c(i2c, &ws_regmap_config); 164 if (IS_ERR(ws->reg_map)) 165 return dev_err_probe(dev, PTR_ERR(ws->reg_map), "Failed to allocate regmap\n"); 166 167 ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, &panel, NULL); 168 if (ret) 169 return dev_err_probe(dev, ret, "Failed to find remote panel\n"); 170 171 ws->next_bridge = devm_drm_panel_bridge_add(dev, panel); 172 drm_panel_put(panel); 173 if (IS_ERR(ws->next_bridge)) 174 return PTR_ERR(ws->next_bridge); 175 176 ws->backlight = ws_bridge_create_backlight(ws); 177 if (IS_ERR(ws->backlight)) { 178 ret = PTR_ERR(ws->backlight); 179 dev_err(dev, "Failed to create backlight: %d\n", ret); 180 return ret; 181 } 182 183 regmap_write(ws->reg_map, 0xc0, 0x01); 184 regmap_write(ws->reg_map, 0xc2, 0x01); 185 regmap_write(ws->reg_map, 0xac, 0x01); 186 187 ws->bridge.type = (uintptr_t)i2c_get_match_data(i2c); 188 ws->bridge.of_node = dev->of_node; 189 devm_drm_bridge_add(dev, &ws->bridge); 190 191 return ws_bridge_attach_dsi(ws); 192 } 193 194 static const struct of_device_id ws_bridge_of_ids[] = { 195 {.compatible = "waveshare,dsi2dpi", .data = (void *)DRM_MODE_CONNECTOR_DPI, }, 196 {.compatible = "waveshare,dsi2lvds", .data = (void *)DRM_MODE_CONNECTOR_LVDS, }, 197 { } 198 }; 199 200 MODULE_DEVICE_TABLE(of, ws_bridge_of_ids); 201 202 static struct i2c_driver ws_bridge_driver = { 203 .driver = { 204 .name = "ws_dsi2dpi", 205 .of_match_table = ws_bridge_of_ids, 206 }, 207 .probe = ws_bridge_probe, 208 }; 209 module_i2c_driver(ws_bridge_driver); 210 211 MODULE_AUTHOR("Joseph Guo <qijian.guo@nxp.com>"); 212 MODULE_DESCRIPTION("Waveshare DSI2DPI bridge driver"); 213 MODULE_LICENSE("GPL"); 214