1cdc33b86SJacopo Mondi // SPDX-License-Identifier: GPL-2.0
2cdc33b86SJacopo Mondi /*
3cdc33b86SJacopo Mondi * THC63LVD1024 LVDS to parallel data DRM bridge driver.
4cdc33b86SJacopo Mondi *
5cdc33b86SJacopo Mondi * Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org>
6cdc33b86SJacopo Mondi */
7cdc33b86SJacopo Mondi
8cdc33b86SJacopo Mondi #include <linux/gpio/consumer.h>
995b60804SSam Ravnborg #include <linux/module.h>
1095b60804SSam Ravnborg #include <linux/of.h>
11cdc33b86SJacopo Mondi #include <linux/of_graph.h>
1295b60804SSam Ravnborg #include <linux/platform_device.h>
13cdc33b86SJacopo Mondi #include <linux/regulator/consumer.h>
14cdc33b86SJacopo Mondi #include <linux/slab.h>
15cdc33b86SJacopo Mondi
1695b60804SSam Ravnborg #include <drm/drm_bridge.h>
1795b60804SSam Ravnborg #include <drm/drm_panel.h>
1895b60804SSam Ravnborg
19cdc33b86SJacopo Mondi enum thc63_ports {
20cdc33b86SJacopo Mondi THC63_LVDS_IN0,
21cdc33b86SJacopo Mondi THC63_LVDS_IN1,
22cdc33b86SJacopo Mondi THC63_RGB_OUT0,
23cdc33b86SJacopo Mondi THC63_RGB_OUT1,
24cdc33b86SJacopo Mondi };
25cdc33b86SJacopo Mondi
26cdc33b86SJacopo Mondi struct thc63_dev {
27cdc33b86SJacopo Mondi struct device *dev;
28cdc33b86SJacopo Mondi
29cdc33b86SJacopo Mondi struct regulator *vcc;
30cdc33b86SJacopo Mondi
31cdc33b86SJacopo Mondi struct gpio_desc *pdwn;
32cdc33b86SJacopo Mondi struct gpio_desc *oe;
33cdc33b86SJacopo Mondi
34cdc33b86SJacopo Mondi struct drm_bridge bridge;
35cdc33b86SJacopo Mondi struct drm_bridge *next;
361ddf2d4aSLaurent Pinchart
371ddf2d4aSLaurent Pinchart struct drm_bridge_timings timings;
38cdc33b86SJacopo Mondi };
39cdc33b86SJacopo Mondi
to_thc63(struct drm_bridge * bridge)40cdc33b86SJacopo Mondi static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
41cdc33b86SJacopo Mondi {
42cdc33b86SJacopo Mondi return container_of(bridge, struct thc63_dev, bridge);
43cdc33b86SJacopo Mondi }
44cdc33b86SJacopo Mondi
thc63_attach(struct drm_bridge * bridge,enum drm_bridge_attach_flags flags)45a25b988fSLaurent Pinchart static int thc63_attach(struct drm_bridge *bridge,
46a25b988fSLaurent Pinchart enum drm_bridge_attach_flags flags)
47cdc33b86SJacopo Mondi {
48cdc33b86SJacopo Mondi struct thc63_dev *thc63 = to_thc63(bridge);
49cdc33b86SJacopo Mondi
50a25b988fSLaurent Pinchart return drm_bridge_attach(bridge->encoder, thc63->next, bridge, flags);
51cdc33b86SJacopo Mondi }
52cdc33b86SJacopo Mondi
thc63_mode_valid(struct drm_bridge * bridge,const struct drm_display_info * info,const struct drm_display_mode * mode)53399d9f2fSLaurent Pinchart static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
5412c683e1SLaurent Pinchart const struct drm_display_info *info,
55399d9f2fSLaurent Pinchart const struct drm_display_mode *mode)
56399d9f2fSLaurent Pinchart {
571ddf2d4aSLaurent Pinchart struct thc63_dev *thc63 = to_thc63(bridge);
581ddf2d4aSLaurent Pinchart unsigned int min_freq;
591ddf2d4aSLaurent Pinchart unsigned int max_freq;
601ddf2d4aSLaurent Pinchart
61399d9f2fSLaurent Pinchart /*
621ddf2d4aSLaurent Pinchart * The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but
631ddf2d4aSLaurent Pinchart * dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out
641ddf2d4aSLaurent Pinchart * isn't supported by the driver yet, simply derive the limits from the
651ddf2d4aSLaurent Pinchart * input mode.
66399d9f2fSLaurent Pinchart */
671ddf2d4aSLaurent Pinchart if (thc63->timings.dual_link) {
681ddf2d4aSLaurent Pinchart min_freq = 40000;
691ddf2d4aSLaurent Pinchart max_freq = 150000;
701ddf2d4aSLaurent Pinchart } else {
711ddf2d4aSLaurent Pinchart min_freq = 8000;
721ddf2d4aSLaurent Pinchart max_freq = 135000;
731ddf2d4aSLaurent Pinchart }
741ddf2d4aSLaurent Pinchart
751ddf2d4aSLaurent Pinchart if (mode->clock < min_freq)
76399d9f2fSLaurent Pinchart return MODE_CLOCK_LOW;
77399d9f2fSLaurent Pinchart
781ddf2d4aSLaurent Pinchart if (mode->clock > max_freq)
79399d9f2fSLaurent Pinchart return MODE_CLOCK_HIGH;
80399d9f2fSLaurent Pinchart
81399d9f2fSLaurent Pinchart return MODE_OK;
82399d9f2fSLaurent Pinchart }
83399d9f2fSLaurent Pinchart
thc63_enable(struct drm_bridge * bridge)84cdc33b86SJacopo Mondi static void thc63_enable(struct drm_bridge *bridge)
85cdc33b86SJacopo Mondi {
86cdc33b86SJacopo Mondi struct thc63_dev *thc63 = to_thc63(bridge);
87cdc33b86SJacopo Mondi int ret;
88cdc33b86SJacopo Mondi
89cdc33b86SJacopo Mondi ret = regulator_enable(thc63->vcc);
90cdc33b86SJacopo Mondi if (ret) {
91cdc33b86SJacopo Mondi dev_err(thc63->dev,
92cdc33b86SJacopo Mondi "Failed to enable regulator \"vcc\": %d\n", ret);
93cdc33b86SJacopo Mondi return;
94cdc33b86SJacopo Mondi }
95cdc33b86SJacopo Mondi
96cdc33b86SJacopo Mondi gpiod_set_value(thc63->pdwn, 0);
97cdc33b86SJacopo Mondi gpiod_set_value(thc63->oe, 1);
98cdc33b86SJacopo Mondi }
99cdc33b86SJacopo Mondi
thc63_disable(struct drm_bridge * bridge)100cdc33b86SJacopo Mondi static void thc63_disable(struct drm_bridge *bridge)
101cdc33b86SJacopo Mondi {
102cdc33b86SJacopo Mondi struct thc63_dev *thc63 = to_thc63(bridge);
103cdc33b86SJacopo Mondi int ret;
104cdc33b86SJacopo Mondi
105cdc33b86SJacopo Mondi gpiod_set_value(thc63->oe, 0);
106cdc33b86SJacopo Mondi gpiod_set_value(thc63->pdwn, 1);
107cdc33b86SJacopo Mondi
108cdc33b86SJacopo Mondi ret = regulator_disable(thc63->vcc);
109cdc33b86SJacopo Mondi if (ret)
110cdc33b86SJacopo Mondi dev_err(thc63->dev,
111cdc33b86SJacopo Mondi "Failed to disable regulator \"vcc\": %d\n", ret);
112cdc33b86SJacopo Mondi }
113cdc33b86SJacopo Mondi
114cdc33b86SJacopo Mondi static const struct drm_bridge_funcs thc63_bridge_func = {
115cdc33b86SJacopo Mondi .attach = thc63_attach,
116399d9f2fSLaurent Pinchart .mode_valid = thc63_mode_valid,
117cdc33b86SJacopo Mondi .enable = thc63_enable,
118cdc33b86SJacopo Mondi .disable = thc63_disable,
119cdc33b86SJacopo Mondi };
120cdc33b86SJacopo Mondi
thc63_parse_dt(struct thc63_dev * thc63)121cdc33b86SJacopo Mondi static int thc63_parse_dt(struct thc63_dev *thc63)
122cdc33b86SJacopo Mondi {
1231ddf2d4aSLaurent Pinchart struct device_node *endpoint;
124cdc33b86SJacopo Mondi struct device_node *remote;
125cdc33b86SJacopo Mondi
1261ddf2d4aSLaurent Pinchart endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
127cdc33b86SJacopo Mondi THC63_RGB_OUT0, -1);
1281ddf2d4aSLaurent Pinchart if (!endpoint) {
129cdc33b86SJacopo Mondi dev_err(thc63->dev, "Missing endpoint in port@%u\n",
130cdc33b86SJacopo Mondi THC63_RGB_OUT0);
131cdc33b86SJacopo Mondi return -ENODEV;
132cdc33b86SJacopo Mondi }
133cdc33b86SJacopo Mondi
1341ddf2d4aSLaurent Pinchart remote = of_graph_get_remote_port_parent(endpoint);
1351ddf2d4aSLaurent Pinchart of_node_put(endpoint);
136cdc33b86SJacopo Mondi if (!remote) {
137cdc33b86SJacopo Mondi dev_err(thc63->dev, "Endpoint in port@%u unconnected\n",
138cdc33b86SJacopo Mondi THC63_RGB_OUT0);
139cdc33b86SJacopo Mondi return -ENODEV;
140cdc33b86SJacopo Mondi }
141cdc33b86SJacopo Mondi
142cdc33b86SJacopo Mondi if (!of_device_is_available(remote)) {
143cdc33b86SJacopo Mondi dev_err(thc63->dev, "port@%u remote endpoint is disabled\n",
144cdc33b86SJacopo Mondi THC63_RGB_OUT0);
145cdc33b86SJacopo Mondi of_node_put(remote);
146cdc33b86SJacopo Mondi return -ENODEV;
147cdc33b86SJacopo Mondi }
148cdc33b86SJacopo Mondi
149cdc33b86SJacopo Mondi thc63->next = of_drm_find_bridge(remote);
150cdc33b86SJacopo Mondi of_node_put(remote);
151cdc33b86SJacopo Mondi if (!thc63->next)
152cdc33b86SJacopo Mondi return -EPROBE_DEFER;
153cdc33b86SJacopo Mondi
1541ddf2d4aSLaurent Pinchart endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
1551ddf2d4aSLaurent Pinchart THC63_LVDS_IN1, -1);
1561ddf2d4aSLaurent Pinchart if (endpoint) {
1571ddf2d4aSLaurent Pinchart remote = of_graph_get_remote_port_parent(endpoint);
1581ddf2d4aSLaurent Pinchart of_node_put(endpoint);
1591ddf2d4aSLaurent Pinchart
1601ddf2d4aSLaurent Pinchart if (remote) {
1611ddf2d4aSLaurent Pinchart if (of_device_is_available(remote))
1621ddf2d4aSLaurent Pinchart thc63->timings.dual_link = true;
1631ddf2d4aSLaurent Pinchart of_node_put(remote);
1641ddf2d4aSLaurent Pinchart }
1651ddf2d4aSLaurent Pinchart }
1661ddf2d4aSLaurent Pinchart
1671ddf2d4aSLaurent Pinchart dev_dbg(thc63->dev, "operating in %s-link mode\n",
1681ddf2d4aSLaurent Pinchart thc63->timings.dual_link ? "dual" : "single");
1691ddf2d4aSLaurent Pinchart
170cdc33b86SJacopo Mondi return 0;
171cdc33b86SJacopo Mondi }
172cdc33b86SJacopo Mondi
thc63_gpio_init(struct thc63_dev * thc63)173cdc33b86SJacopo Mondi static int thc63_gpio_init(struct thc63_dev *thc63)
174cdc33b86SJacopo Mondi {
175cdc33b86SJacopo Mondi thc63->oe = devm_gpiod_get_optional(thc63->dev, "oe", GPIOD_OUT_LOW);
176cdc33b86SJacopo Mondi if (IS_ERR(thc63->oe)) {
177cdc33b86SJacopo Mondi dev_err(thc63->dev, "Unable to get \"oe-gpios\": %ld\n",
178cdc33b86SJacopo Mondi PTR_ERR(thc63->oe));
179cdc33b86SJacopo Mondi return PTR_ERR(thc63->oe);
180cdc33b86SJacopo Mondi }
181cdc33b86SJacopo Mondi
182cdc33b86SJacopo Mondi thc63->pdwn = devm_gpiod_get_optional(thc63->dev, "powerdown",
183cdc33b86SJacopo Mondi GPIOD_OUT_HIGH);
184cdc33b86SJacopo Mondi if (IS_ERR(thc63->pdwn)) {
185cdc33b86SJacopo Mondi dev_err(thc63->dev, "Unable to get \"powerdown-gpios\": %ld\n",
186cdc33b86SJacopo Mondi PTR_ERR(thc63->pdwn));
187cdc33b86SJacopo Mondi return PTR_ERR(thc63->pdwn);
188cdc33b86SJacopo Mondi }
189cdc33b86SJacopo Mondi
190cdc33b86SJacopo Mondi return 0;
191cdc33b86SJacopo Mondi }
192cdc33b86SJacopo Mondi
thc63_probe(struct platform_device * pdev)193cdc33b86SJacopo Mondi static int thc63_probe(struct platform_device *pdev)
194cdc33b86SJacopo Mondi {
195cdc33b86SJacopo Mondi struct thc63_dev *thc63;
196cdc33b86SJacopo Mondi int ret;
197cdc33b86SJacopo Mondi
198cdc33b86SJacopo Mondi thc63 = devm_kzalloc(&pdev->dev, sizeof(*thc63), GFP_KERNEL);
199cdc33b86SJacopo Mondi if (!thc63)
200cdc33b86SJacopo Mondi return -ENOMEM;
201cdc33b86SJacopo Mondi
202cdc33b86SJacopo Mondi thc63->dev = &pdev->dev;
203cdc33b86SJacopo Mondi platform_set_drvdata(pdev, thc63);
204cdc33b86SJacopo Mondi
205a1bc5e31SMark Brown thc63->vcc = devm_regulator_get(thc63->dev, "vcc");
206cdc33b86SJacopo Mondi if (IS_ERR(thc63->vcc)) {
207cdc33b86SJacopo Mondi if (PTR_ERR(thc63->vcc) == -EPROBE_DEFER)
208cdc33b86SJacopo Mondi return -EPROBE_DEFER;
209cdc33b86SJacopo Mondi
210cdc33b86SJacopo Mondi dev_err(thc63->dev, "Unable to get \"vcc\" supply: %ld\n",
211cdc33b86SJacopo Mondi PTR_ERR(thc63->vcc));
212cdc33b86SJacopo Mondi return PTR_ERR(thc63->vcc);
213cdc33b86SJacopo Mondi }
214cdc33b86SJacopo Mondi
215cdc33b86SJacopo Mondi ret = thc63_gpio_init(thc63);
216cdc33b86SJacopo Mondi if (ret)
217cdc33b86SJacopo Mondi return ret;
218cdc33b86SJacopo Mondi
219cdc33b86SJacopo Mondi ret = thc63_parse_dt(thc63);
220cdc33b86SJacopo Mondi if (ret)
221cdc33b86SJacopo Mondi return ret;
222cdc33b86SJacopo Mondi
223cdc33b86SJacopo Mondi thc63->bridge.driver_private = thc63;
224cdc33b86SJacopo Mondi thc63->bridge.of_node = pdev->dev.of_node;
225cdc33b86SJacopo Mondi thc63->bridge.funcs = &thc63_bridge_func;
2261ddf2d4aSLaurent Pinchart thc63->bridge.timings = &thc63->timings;
227cdc33b86SJacopo Mondi
228cdc33b86SJacopo Mondi drm_bridge_add(&thc63->bridge);
229cdc33b86SJacopo Mondi
230cdc33b86SJacopo Mondi return 0;
231cdc33b86SJacopo Mondi }
232cdc33b86SJacopo Mondi
thc63_remove(struct platform_device * pdev)233*5e3ea764SUwe Kleine-König static void thc63_remove(struct platform_device *pdev)
234cdc33b86SJacopo Mondi {
235cdc33b86SJacopo Mondi struct thc63_dev *thc63 = platform_get_drvdata(pdev);
236cdc33b86SJacopo Mondi
237cdc33b86SJacopo Mondi drm_bridge_remove(&thc63->bridge);
238cdc33b86SJacopo Mondi }
239cdc33b86SJacopo Mondi
240cdc33b86SJacopo Mondi static const struct of_device_id thc63_match[] = {
241cdc33b86SJacopo Mondi { .compatible = "thine,thc63lvd1024", },
242cdc33b86SJacopo Mondi { },
243cdc33b86SJacopo Mondi };
244cdc33b86SJacopo Mondi MODULE_DEVICE_TABLE(of, thc63_match);
245cdc33b86SJacopo Mondi
246cdc33b86SJacopo Mondi static struct platform_driver thc63_driver = {
247cdc33b86SJacopo Mondi .probe = thc63_probe,
248*5e3ea764SUwe Kleine-König .remove_new = thc63_remove,
249cdc33b86SJacopo Mondi .driver = {
250cdc33b86SJacopo Mondi .name = "thc63lvd1024",
251cdc33b86SJacopo Mondi .of_match_table = thc63_match,
252cdc33b86SJacopo Mondi },
253cdc33b86SJacopo Mondi };
254cdc33b86SJacopo Mondi module_platform_driver(thc63_driver);
255cdc33b86SJacopo Mondi
256cdc33b86SJacopo Mondi MODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>");
257cdc33b86SJacopo Mondi MODULE_DESCRIPTION("Thine THC63LVD1024 LVDS decoder DRM bridge driver");
258cdc33b86SJacopo Mondi MODULE_LICENSE("GPL v2");
259