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(map: ldb->regmap, reg: ldb->ctrl_reg, val: 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(map: ldb->regmap, reg: ldb->ctrl_reg, val: 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 | if (!bridge->encoder) { |
120 | DRM_DEV_ERROR(ldb->dev, "missing encoder\n" ); |
121 | return -ENODEV; |
122 | } |
123 | |
124 | return drm_bridge_attach(encoder: bridge->encoder, |
125 | bridge: ldb_ch->next_bridge, previous: bridge, |
126 | flags: DRM_BRIDGE_ATTACH_NO_CONNECTOR); |
127 | } |
128 | EXPORT_SYMBOL_GPL(ldb_bridge_attach_helper); |
129 | |
130 | int ldb_init_helper(struct ldb *ldb) |
131 | { |
132 | struct device *dev = ldb->dev; |
133 | struct device_node *np = dev->of_node; |
134 | struct device_node *child; |
135 | int ret; |
136 | u32 i; |
137 | |
138 | ldb->regmap = syscon_node_to_regmap(np: np->parent); |
139 | if (IS_ERR(ptr: ldb->regmap)) { |
140 | ret = PTR_ERR(ptr: ldb->regmap); |
141 | if (ret != -EPROBE_DEFER) |
142 | DRM_DEV_ERROR(dev, "failed to get regmap: %d\n" , ret); |
143 | return ret; |
144 | } |
145 | |
146 | for_each_available_child_of_node(np, child) { |
147 | struct ldb_channel *ldb_ch; |
148 | |
149 | ret = of_property_read_u32(np: child, propname: "reg" , out_value: &i); |
150 | if (ret || i > MAX_LDB_CHAN_NUM - 1) { |
151 | ret = -EINVAL; |
152 | DRM_DEV_ERROR(dev, |
153 | "invalid channel node address: %u\n" , i); |
154 | of_node_put(node: child); |
155 | return ret; |
156 | } |
157 | |
158 | ldb_ch = ldb->channel[i]; |
159 | ldb_ch->ldb = ldb; |
160 | ldb_ch->chno = i; |
161 | ldb_ch->is_available = true; |
162 | ldb_ch->np = child; |
163 | |
164 | ldb->available_ch_cnt++; |
165 | } |
166 | |
167 | return 0; |
168 | } |
169 | EXPORT_SYMBOL_GPL(ldb_init_helper); |
170 | |
171 | int ldb_find_next_bridge_helper(struct ldb *ldb) |
172 | { |
173 | struct device *dev = ldb->dev; |
174 | struct ldb_channel *ldb_ch; |
175 | int ret, i; |
176 | |
177 | for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { |
178 | ldb_ch = ldb->channel[i]; |
179 | |
180 | if (!ldb_ch->is_available) |
181 | continue; |
182 | |
183 | ldb_ch->next_bridge = devm_drm_of_get_bridge(dev, node: ldb_ch->np, |
184 | port: 1, endpoint: 0); |
185 | if (IS_ERR(ptr: ldb_ch->next_bridge)) { |
186 | ret = PTR_ERR(ptr: ldb_ch->next_bridge); |
187 | if (ret != -EPROBE_DEFER) |
188 | DRM_DEV_ERROR(dev, |
189 | "failed to get next bridge: %d\n" , |
190 | ret); |
191 | return ret; |
192 | } |
193 | } |
194 | |
195 | return 0; |
196 | } |
197 | EXPORT_SYMBOL_GPL(ldb_find_next_bridge_helper); |
198 | |
199 | void ldb_add_bridge_helper(struct ldb *ldb, |
200 | const struct drm_bridge_funcs *bridge_funcs) |
201 | { |
202 | struct ldb_channel *ldb_ch; |
203 | int i; |
204 | |
205 | for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { |
206 | ldb_ch = ldb->channel[i]; |
207 | |
208 | if (!ldb_ch->is_available) |
209 | continue; |
210 | |
211 | ldb_ch->bridge.driver_private = ldb_ch; |
212 | ldb_ch->bridge.funcs = bridge_funcs; |
213 | ldb_ch->bridge.of_node = ldb_ch->np; |
214 | |
215 | drm_bridge_add(bridge: &ldb_ch->bridge); |
216 | } |
217 | } |
218 | EXPORT_SYMBOL_GPL(ldb_add_bridge_helper); |
219 | |
220 | void ldb_remove_bridge_helper(struct ldb *ldb) |
221 | { |
222 | struct ldb_channel *ldb_ch; |
223 | int i; |
224 | |
225 | for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { |
226 | ldb_ch = ldb->channel[i]; |
227 | |
228 | if (!ldb_ch->is_available) |
229 | continue; |
230 | |
231 | drm_bridge_remove(bridge: &ldb_ch->bridge); |
232 | } |
233 | } |
234 | EXPORT_SYMBOL_GPL(ldb_remove_bridge_helper); |
235 | |
236 | MODULE_DESCRIPTION("i.MX8 LVDS Display Bridge(LDB)/Pixel Mapper bridge helper" ); |
237 | MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>" ); |
238 | MODULE_LICENSE("GPL" ); |
239 | |