1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2012 Sascha Hauer, Pengutronix 4 * Copyright 2019,2020,2022 NXP 5 */ 6 7 #include <linux/export.h> 8 #include <linux/media-bus-format.h> 9 #include <linux/mfd/syscon.h> 10 #include <linux/module.h> 11 #include <linux/of.h> 12 #include <linux/regmap.h> 13 14 #include <drm/drm_bridge.h> 15 #include <drm/drm_of.h> 16 #include <drm/drm_print.h> 17 18 #include "imx-ldb-helper.h" 19 20 bool ldb_channel_is_single_link(struct ldb_channel *ldb_ch) 21 { 22 return ldb_ch->link_type == LDB_CH_SINGLE_LINK; 23 } 24 EXPORT_SYMBOL_GPL(ldb_channel_is_single_link); 25 26 bool ldb_channel_is_split_link(struct ldb_channel *ldb_ch) 27 { 28 return ldb_ch->link_type == LDB_CH_DUAL_LINK_EVEN_ODD_PIXELS || 29 ldb_ch->link_type == LDB_CH_DUAL_LINK_ODD_EVEN_PIXELS; 30 } 31 EXPORT_SYMBOL_GPL(ldb_channel_is_split_link); 32 33 int ldb_bridge_atomic_check_helper(struct drm_bridge *bridge, 34 struct drm_bridge_state *bridge_state, 35 struct drm_crtc_state *crtc_state, 36 struct drm_connector_state *conn_state) 37 { 38 struct ldb_channel *ldb_ch = bridge->driver_private; 39 40 ldb_ch->in_bus_format = bridge_state->input_bus_cfg.format; 41 ldb_ch->out_bus_format = bridge_state->output_bus_cfg.format; 42 43 return 0; 44 } 45 EXPORT_SYMBOL_GPL(ldb_bridge_atomic_check_helper); 46 47 void ldb_bridge_mode_set_helper(struct drm_bridge *bridge, 48 const struct drm_display_mode *mode, 49 const struct drm_display_mode *adjusted_mode) 50 { 51 struct ldb_channel *ldb_ch = bridge->driver_private; 52 struct ldb *ldb = ldb_ch->ldb; 53 bool is_split = ldb_channel_is_split_link(ldb_ch); 54 55 if (is_split) 56 ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; 57 58 switch (ldb_ch->out_bus_format) { 59 case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: 60 break; 61 case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: 62 if (ldb_ch->chno == 0 || is_split) 63 ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24; 64 if (ldb_ch->chno == 1 || is_split) 65 ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24; 66 break; 67 case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: 68 if (ldb_ch->chno == 0 || is_split) 69 ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 | 70 LDB_BIT_MAP_CH0_JEIDA; 71 if (ldb_ch->chno == 1 || is_split) 72 ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 | 73 LDB_BIT_MAP_CH1_JEIDA; 74 break; 75 } 76 } 77 EXPORT_SYMBOL_GPL(ldb_bridge_mode_set_helper); 78 79 void ldb_bridge_enable_helper(struct drm_bridge *bridge) 80 { 81 struct ldb_channel *ldb_ch = bridge->driver_private; 82 struct ldb *ldb = ldb_ch->ldb; 83 84 /* 85 * Platform specific bridge drivers should set ldb_ctrl properly 86 * for the enablement, so just write the ctrl_reg here. 87 */ 88 regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); 89 } 90 EXPORT_SYMBOL_GPL(ldb_bridge_enable_helper); 91 92 void ldb_bridge_disable_helper(struct drm_bridge *bridge) 93 { 94 struct ldb_channel *ldb_ch = bridge->driver_private; 95 struct ldb *ldb = ldb_ch->ldb; 96 bool is_split = ldb_channel_is_split_link(ldb_ch); 97 98 if (ldb_ch->chno == 0 || is_split) 99 ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; 100 if (ldb_ch->chno == 1 || is_split) 101 ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; 102 103 regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); 104 } 105 EXPORT_SYMBOL_GPL(ldb_bridge_disable_helper); 106 107 int ldb_bridge_attach_helper(struct drm_bridge *bridge, 108 enum drm_bridge_attach_flags flags) 109 { 110 struct ldb_channel *ldb_ch = bridge->driver_private; 111 struct ldb *ldb = ldb_ch->ldb; 112 113 if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { 114 DRM_DEV_ERROR(ldb->dev, 115 "do not support creating a drm_connector\n"); 116 return -EINVAL; 117 } 118 119 return drm_bridge_attach(bridge->encoder, 120 ldb_ch->next_bridge, bridge, 121 DRM_BRIDGE_ATTACH_NO_CONNECTOR); 122 } 123 EXPORT_SYMBOL_GPL(ldb_bridge_attach_helper); 124 125 int ldb_init_helper(struct ldb *ldb) 126 { 127 struct device *dev = ldb->dev; 128 struct device_node *np = dev->of_node; 129 struct device_node *child; 130 int ret; 131 u32 i; 132 133 ldb->regmap = syscon_node_to_regmap(np->parent); 134 if (IS_ERR(ldb->regmap)) { 135 ret = PTR_ERR(ldb->regmap); 136 if (ret != -EPROBE_DEFER) 137 DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret); 138 return ret; 139 } 140 141 for_each_available_child_of_node(np, child) { 142 struct ldb_channel *ldb_ch; 143 144 ret = of_property_read_u32(child, "reg", &i); 145 if (ret || i > MAX_LDB_CHAN_NUM - 1) { 146 ret = -EINVAL; 147 DRM_DEV_ERROR(dev, 148 "invalid channel node address: %u\n", i); 149 of_node_put(child); 150 return ret; 151 } 152 153 ldb_ch = ldb->channel[i]; 154 ldb_ch->ldb = ldb; 155 ldb_ch->chno = i; 156 ldb_ch->is_available = true; 157 ldb_ch->np = child; 158 159 ldb->available_ch_cnt++; 160 } 161 162 return 0; 163 } 164 EXPORT_SYMBOL_GPL(ldb_init_helper); 165 166 int ldb_find_next_bridge_helper(struct ldb *ldb) 167 { 168 struct device *dev = ldb->dev; 169 struct ldb_channel *ldb_ch; 170 int ret, i; 171 172 for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { 173 ldb_ch = ldb->channel[i]; 174 175 if (!ldb_ch->is_available) 176 continue; 177 178 ldb_ch->next_bridge = devm_drm_of_get_bridge(dev, ldb_ch->np, 179 1, 0); 180 if (IS_ERR(ldb_ch->next_bridge)) { 181 ret = PTR_ERR(ldb_ch->next_bridge); 182 if (ret != -EPROBE_DEFER) 183 DRM_DEV_ERROR(dev, 184 "failed to get next bridge: %d\n", 185 ret); 186 return ret; 187 } 188 } 189 190 return 0; 191 } 192 EXPORT_SYMBOL_GPL(ldb_find_next_bridge_helper); 193 194 void ldb_add_bridge_helper(struct ldb *ldb, 195 const struct drm_bridge_funcs *bridge_funcs) 196 { 197 struct ldb_channel *ldb_ch; 198 int i; 199 200 for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { 201 ldb_ch = ldb->channel[i]; 202 203 if (!ldb_ch->is_available) 204 continue; 205 206 ldb_ch->bridge.driver_private = ldb_ch; 207 ldb_ch->bridge.funcs = bridge_funcs; 208 ldb_ch->bridge.of_node = ldb_ch->np; 209 210 drm_bridge_add(&ldb_ch->bridge); 211 } 212 } 213 EXPORT_SYMBOL_GPL(ldb_add_bridge_helper); 214 215 void ldb_remove_bridge_helper(struct ldb *ldb) 216 { 217 struct ldb_channel *ldb_ch; 218 int i; 219 220 for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { 221 ldb_ch = ldb->channel[i]; 222 223 if (!ldb_ch->is_available) 224 continue; 225 226 drm_bridge_remove(&ldb_ch->bridge); 227 } 228 } 229 EXPORT_SYMBOL_GPL(ldb_remove_bridge_helper); 230 231 MODULE_DESCRIPTION("i.MX8 LVDS Display Bridge(LDB)/Pixel Mapper bridge helper"); 232 MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>"); 233 MODULE_LICENSE("GPL"); 234