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