1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * THC63LVD1024 LVDS to parallel data DRM bridge driver. |
4 | * |
5 | * Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org> |
6 | */ |
7 | |
8 | #include <linux/gpio/consumer.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_graph.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/regulator/consumer.h> |
14 | #include <linux/slab.h> |
15 | |
16 | #include <drm/drm_bridge.h> |
17 | #include <drm/drm_panel.h> |
18 | |
19 | enum thc63_ports { |
20 | THC63_LVDS_IN0, |
21 | THC63_LVDS_IN1, |
22 | THC63_RGB_OUT0, |
23 | THC63_RGB_OUT1, |
24 | }; |
25 | |
26 | struct thc63_dev { |
27 | struct device *dev; |
28 | |
29 | struct regulator *vcc; |
30 | |
31 | struct gpio_desc *pdwn; |
32 | struct gpio_desc *oe; |
33 | |
34 | struct drm_bridge bridge; |
35 | struct drm_bridge *next; |
36 | |
37 | struct drm_bridge_timings timings; |
38 | }; |
39 | |
40 | static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge) |
41 | { |
42 | return container_of(bridge, struct thc63_dev, bridge); |
43 | } |
44 | |
45 | static int thc63_attach(struct drm_bridge *bridge, |
46 | enum drm_bridge_attach_flags flags) |
47 | { |
48 | struct thc63_dev *thc63 = to_thc63(bridge); |
49 | |
50 | return drm_bridge_attach(encoder: bridge->encoder, bridge: thc63->next, previous: bridge, flags); |
51 | } |
52 | |
53 | static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge, |
54 | const struct drm_display_info *info, |
55 | const struct drm_display_mode *mode) |
56 | { |
57 | struct thc63_dev *thc63 = to_thc63(bridge); |
58 | unsigned int min_freq; |
59 | unsigned int max_freq; |
60 | |
61 | /* |
62 | * The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but |
63 | * dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out |
64 | * isn't supported by the driver yet, simply derive the limits from the |
65 | * input mode. |
66 | */ |
67 | if (thc63->timings.dual_link) { |
68 | min_freq = 40000; |
69 | max_freq = 150000; |
70 | } else { |
71 | min_freq = 8000; |
72 | max_freq = 135000; |
73 | } |
74 | |
75 | if (mode->clock < min_freq) |
76 | return MODE_CLOCK_LOW; |
77 | |
78 | if (mode->clock > max_freq) |
79 | return MODE_CLOCK_HIGH; |
80 | |
81 | return MODE_OK; |
82 | } |
83 | |
84 | static void thc63_enable(struct drm_bridge *bridge) |
85 | { |
86 | struct thc63_dev *thc63 = to_thc63(bridge); |
87 | int ret; |
88 | |
89 | ret = regulator_enable(regulator: thc63->vcc); |
90 | if (ret) { |
91 | dev_err(thc63->dev, |
92 | "Failed to enable regulator \"vcc\": %d\n" , ret); |
93 | return; |
94 | } |
95 | |
96 | gpiod_set_value(desc: thc63->pdwn, value: 0); |
97 | gpiod_set_value(desc: thc63->oe, value: 1); |
98 | } |
99 | |
100 | static void thc63_disable(struct drm_bridge *bridge) |
101 | { |
102 | struct thc63_dev *thc63 = to_thc63(bridge); |
103 | int ret; |
104 | |
105 | gpiod_set_value(desc: thc63->oe, value: 0); |
106 | gpiod_set_value(desc: thc63->pdwn, value: 1); |
107 | |
108 | ret = regulator_disable(regulator: thc63->vcc); |
109 | if (ret) |
110 | dev_err(thc63->dev, |
111 | "Failed to disable regulator \"vcc\": %d\n" , ret); |
112 | } |
113 | |
114 | static const struct drm_bridge_funcs thc63_bridge_func = { |
115 | .attach = thc63_attach, |
116 | .mode_valid = thc63_mode_valid, |
117 | .enable = thc63_enable, |
118 | .disable = thc63_disable, |
119 | }; |
120 | |
121 | static int thc63_parse_dt(struct thc63_dev *thc63) |
122 | { |
123 | struct device_node *endpoint; |
124 | struct device_node *remote; |
125 | |
126 | endpoint = of_graph_get_endpoint_by_regs(parent: thc63->dev->of_node, |
127 | port_reg: THC63_RGB_OUT0, reg: -1); |
128 | if (!endpoint) { |
129 | dev_err(thc63->dev, "Missing endpoint in port@%u\n" , |
130 | THC63_RGB_OUT0); |
131 | return -ENODEV; |
132 | } |
133 | |
134 | remote = of_graph_get_remote_port_parent(node: endpoint); |
135 | of_node_put(node: endpoint); |
136 | if (!remote) { |
137 | dev_err(thc63->dev, "Endpoint in port@%u unconnected\n" , |
138 | THC63_RGB_OUT0); |
139 | return -ENODEV; |
140 | } |
141 | |
142 | if (!of_device_is_available(device: remote)) { |
143 | dev_err(thc63->dev, "port@%u remote endpoint is disabled\n" , |
144 | THC63_RGB_OUT0); |
145 | of_node_put(node: remote); |
146 | return -ENODEV; |
147 | } |
148 | |
149 | thc63->next = of_drm_find_bridge(np: remote); |
150 | of_node_put(node: remote); |
151 | if (!thc63->next) |
152 | return -EPROBE_DEFER; |
153 | |
154 | endpoint = of_graph_get_endpoint_by_regs(parent: thc63->dev->of_node, |
155 | port_reg: THC63_LVDS_IN1, reg: -1); |
156 | if (endpoint) { |
157 | remote = of_graph_get_remote_port_parent(node: endpoint); |
158 | of_node_put(node: endpoint); |
159 | |
160 | if (remote) { |
161 | if (of_device_is_available(device: remote)) |
162 | thc63->timings.dual_link = true; |
163 | of_node_put(node: remote); |
164 | } |
165 | } |
166 | |
167 | dev_dbg(thc63->dev, "operating in %s-link mode\n" , |
168 | thc63->timings.dual_link ? "dual" : "single" ); |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static int thc63_gpio_init(struct thc63_dev *thc63) |
174 | { |
175 | thc63->oe = devm_gpiod_get_optional(dev: thc63->dev, con_id: "oe" , flags: GPIOD_OUT_LOW); |
176 | if (IS_ERR(ptr: thc63->oe)) { |
177 | dev_err(thc63->dev, "Unable to get \"oe-gpios\": %ld\n" , |
178 | PTR_ERR(thc63->oe)); |
179 | return PTR_ERR(ptr: thc63->oe); |
180 | } |
181 | |
182 | thc63->pdwn = devm_gpiod_get_optional(dev: thc63->dev, con_id: "powerdown" , |
183 | flags: GPIOD_OUT_HIGH); |
184 | if (IS_ERR(ptr: thc63->pdwn)) { |
185 | dev_err(thc63->dev, "Unable to get \"powerdown-gpios\": %ld\n" , |
186 | PTR_ERR(thc63->pdwn)); |
187 | return PTR_ERR(ptr: thc63->pdwn); |
188 | } |
189 | |
190 | return 0; |
191 | } |
192 | |
193 | static int thc63_probe(struct platform_device *pdev) |
194 | { |
195 | struct thc63_dev *thc63; |
196 | int ret; |
197 | |
198 | thc63 = devm_kzalloc(dev: &pdev->dev, size: sizeof(*thc63), GFP_KERNEL); |
199 | if (!thc63) |
200 | return -ENOMEM; |
201 | |
202 | thc63->dev = &pdev->dev; |
203 | platform_set_drvdata(pdev, data: thc63); |
204 | |
205 | thc63->vcc = devm_regulator_get(dev: thc63->dev, id: "vcc" ); |
206 | if (IS_ERR(ptr: thc63->vcc)) { |
207 | if (PTR_ERR(ptr: thc63->vcc) == -EPROBE_DEFER) |
208 | return -EPROBE_DEFER; |
209 | |
210 | dev_err(thc63->dev, "Unable to get \"vcc\" supply: %ld\n" , |
211 | PTR_ERR(thc63->vcc)); |
212 | return PTR_ERR(ptr: thc63->vcc); |
213 | } |
214 | |
215 | ret = thc63_gpio_init(thc63); |
216 | if (ret) |
217 | return ret; |
218 | |
219 | ret = thc63_parse_dt(thc63); |
220 | if (ret) |
221 | return ret; |
222 | |
223 | thc63->bridge.driver_private = thc63; |
224 | thc63->bridge.of_node = pdev->dev.of_node; |
225 | thc63->bridge.funcs = &thc63_bridge_func; |
226 | thc63->bridge.timings = &thc63->timings; |
227 | |
228 | drm_bridge_add(bridge: &thc63->bridge); |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static void thc63_remove(struct platform_device *pdev) |
234 | { |
235 | struct thc63_dev *thc63 = platform_get_drvdata(pdev); |
236 | |
237 | drm_bridge_remove(bridge: &thc63->bridge); |
238 | } |
239 | |
240 | static const struct of_device_id thc63_match[] = { |
241 | { .compatible = "thine,thc63lvd1024" , }, |
242 | { }, |
243 | }; |
244 | MODULE_DEVICE_TABLE(of, thc63_match); |
245 | |
246 | static struct platform_driver thc63_driver = { |
247 | .probe = thc63_probe, |
248 | .remove_new = thc63_remove, |
249 | .driver = { |
250 | .name = "thc63lvd1024" , |
251 | .of_match_table = thc63_match, |
252 | }, |
253 | }; |
254 | module_platform_driver(thc63_driver); |
255 | |
256 | MODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>" ); |
257 | MODULE_DESCRIPTION("Thine THC63LVD1024 LVDS decoder DRM bridge driver" ); |
258 | MODULE_LICENSE("GPL v2" ); |
259 | |