162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * THC63LVD1024 LVDS to parallel data DRM bridge driver.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/of.h>
1162306a36Sopenharmony_ci#include <linux/of_graph.h>
1262306a36Sopenharmony_ci#include <linux/platform_device.h>
1362306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <drm/drm_bridge.h>
1762306a36Sopenharmony_ci#include <drm/drm_panel.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cienum thc63_ports {
2062306a36Sopenharmony_ci	THC63_LVDS_IN0,
2162306a36Sopenharmony_ci	THC63_LVDS_IN1,
2262306a36Sopenharmony_ci	THC63_RGB_OUT0,
2362306a36Sopenharmony_ci	THC63_RGB_OUT1,
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct thc63_dev {
2762306a36Sopenharmony_ci	struct device *dev;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	struct regulator *vcc;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	struct gpio_desc *pdwn;
3262306a36Sopenharmony_ci	struct gpio_desc *oe;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	struct drm_bridge bridge;
3562306a36Sopenharmony_ci	struct drm_bridge *next;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	struct drm_bridge_timings timings;
3862306a36Sopenharmony_ci};
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	return container_of(bridge, struct thc63_dev, bridge);
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic int thc63_attach(struct drm_bridge *bridge,
4662306a36Sopenharmony_ci			enum drm_bridge_attach_flags flags)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct thc63_dev *thc63 = to_thc63(bridge);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return drm_bridge_attach(bridge->encoder, thc63->next, bridge, flags);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
5462306a36Sopenharmony_ci					const struct drm_display_info *info,
5562306a36Sopenharmony_ci					const struct drm_display_mode *mode)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct thc63_dev *thc63 = to_thc63(bridge);
5862306a36Sopenharmony_ci	unsigned int min_freq;
5962306a36Sopenharmony_ci	unsigned int max_freq;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/*
6262306a36Sopenharmony_ci	 * The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but
6362306a36Sopenharmony_ci	 * dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out
6462306a36Sopenharmony_ci	 * isn't supported by the driver yet, simply derive the limits from the
6562306a36Sopenharmony_ci	 * input mode.
6662306a36Sopenharmony_ci	 */
6762306a36Sopenharmony_ci	if (thc63->timings.dual_link) {
6862306a36Sopenharmony_ci		min_freq = 40000;
6962306a36Sopenharmony_ci		max_freq = 150000;
7062306a36Sopenharmony_ci	} else {
7162306a36Sopenharmony_ci		min_freq = 8000;
7262306a36Sopenharmony_ci		max_freq = 135000;
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	if (mode->clock < min_freq)
7662306a36Sopenharmony_ci		return MODE_CLOCK_LOW;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (mode->clock > max_freq)
7962306a36Sopenharmony_ci		return MODE_CLOCK_HIGH;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return MODE_OK;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic void thc63_enable(struct drm_bridge *bridge)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	struct thc63_dev *thc63 = to_thc63(bridge);
8762306a36Sopenharmony_ci	int ret;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	ret = regulator_enable(thc63->vcc);
9062306a36Sopenharmony_ci	if (ret) {
9162306a36Sopenharmony_ci		dev_err(thc63->dev,
9262306a36Sopenharmony_ci			"Failed to enable regulator \"vcc\": %d\n", ret);
9362306a36Sopenharmony_ci		return;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	gpiod_set_value(thc63->pdwn, 0);
9762306a36Sopenharmony_ci	gpiod_set_value(thc63->oe, 1);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic void thc63_disable(struct drm_bridge *bridge)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct thc63_dev *thc63 = to_thc63(bridge);
10362306a36Sopenharmony_ci	int ret;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	gpiod_set_value(thc63->oe, 0);
10662306a36Sopenharmony_ci	gpiod_set_value(thc63->pdwn, 1);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	ret = regulator_disable(thc63->vcc);
10962306a36Sopenharmony_ci	if (ret)
11062306a36Sopenharmony_ci		dev_err(thc63->dev,
11162306a36Sopenharmony_ci			"Failed to disable regulator \"vcc\": %d\n", ret);
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic const struct drm_bridge_funcs thc63_bridge_func = {
11562306a36Sopenharmony_ci	.attach	= thc63_attach,
11662306a36Sopenharmony_ci	.mode_valid = thc63_mode_valid,
11762306a36Sopenharmony_ci	.enable = thc63_enable,
11862306a36Sopenharmony_ci	.disable = thc63_disable,
11962306a36Sopenharmony_ci};
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic int thc63_parse_dt(struct thc63_dev *thc63)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	struct device_node *endpoint;
12462306a36Sopenharmony_ci	struct device_node *remote;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
12762306a36Sopenharmony_ci						 THC63_RGB_OUT0, -1);
12862306a36Sopenharmony_ci	if (!endpoint) {
12962306a36Sopenharmony_ci		dev_err(thc63->dev, "Missing endpoint in port@%u\n",
13062306a36Sopenharmony_ci			THC63_RGB_OUT0);
13162306a36Sopenharmony_ci		return -ENODEV;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	remote = of_graph_get_remote_port_parent(endpoint);
13562306a36Sopenharmony_ci	of_node_put(endpoint);
13662306a36Sopenharmony_ci	if (!remote) {
13762306a36Sopenharmony_ci		dev_err(thc63->dev, "Endpoint in port@%u unconnected\n",
13862306a36Sopenharmony_ci			THC63_RGB_OUT0);
13962306a36Sopenharmony_ci		return -ENODEV;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (!of_device_is_available(remote)) {
14362306a36Sopenharmony_ci		dev_err(thc63->dev, "port@%u remote endpoint is disabled\n",
14462306a36Sopenharmony_ci			THC63_RGB_OUT0);
14562306a36Sopenharmony_ci		of_node_put(remote);
14662306a36Sopenharmony_ci		return -ENODEV;
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	thc63->next = of_drm_find_bridge(remote);
15062306a36Sopenharmony_ci	of_node_put(remote);
15162306a36Sopenharmony_ci	if (!thc63->next)
15262306a36Sopenharmony_ci		return -EPROBE_DEFER;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
15562306a36Sopenharmony_ci						 THC63_LVDS_IN1, -1);
15662306a36Sopenharmony_ci	if (endpoint) {
15762306a36Sopenharmony_ci		remote = of_graph_get_remote_port_parent(endpoint);
15862306a36Sopenharmony_ci		of_node_put(endpoint);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci		if (remote) {
16162306a36Sopenharmony_ci			if (of_device_is_available(remote))
16262306a36Sopenharmony_ci				thc63->timings.dual_link = true;
16362306a36Sopenharmony_ci			of_node_put(remote);
16462306a36Sopenharmony_ci		}
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	dev_dbg(thc63->dev, "operating in %s-link mode\n",
16862306a36Sopenharmony_ci		thc63->timings.dual_link ? "dual" : "single");
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	return 0;
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic int thc63_gpio_init(struct thc63_dev *thc63)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	thc63->oe = devm_gpiod_get_optional(thc63->dev, "oe", GPIOD_OUT_LOW);
17662306a36Sopenharmony_ci	if (IS_ERR(thc63->oe)) {
17762306a36Sopenharmony_ci		dev_err(thc63->dev, "Unable to get \"oe-gpios\": %ld\n",
17862306a36Sopenharmony_ci			PTR_ERR(thc63->oe));
17962306a36Sopenharmony_ci		return PTR_ERR(thc63->oe);
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	thc63->pdwn = devm_gpiod_get_optional(thc63->dev, "powerdown",
18362306a36Sopenharmony_ci					      GPIOD_OUT_HIGH);
18462306a36Sopenharmony_ci	if (IS_ERR(thc63->pdwn)) {
18562306a36Sopenharmony_ci		dev_err(thc63->dev, "Unable to get \"powerdown-gpios\": %ld\n",
18662306a36Sopenharmony_ci			PTR_ERR(thc63->pdwn));
18762306a36Sopenharmony_ci		return PTR_ERR(thc63->pdwn);
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	return 0;
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic int thc63_probe(struct platform_device *pdev)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	struct thc63_dev *thc63;
19662306a36Sopenharmony_ci	int ret;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	thc63 = devm_kzalloc(&pdev->dev, sizeof(*thc63), GFP_KERNEL);
19962306a36Sopenharmony_ci	if (!thc63)
20062306a36Sopenharmony_ci		return -ENOMEM;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	thc63->dev = &pdev->dev;
20362306a36Sopenharmony_ci	platform_set_drvdata(pdev, thc63);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	thc63->vcc = devm_regulator_get(thc63->dev, "vcc");
20662306a36Sopenharmony_ci	if (IS_ERR(thc63->vcc)) {
20762306a36Sopenharmony_ci		if (PTR_ERR(thc63->vcc) == -EPROBE_DEFER)
20862306a36Sopenharmony_ci			return -EPROBE_DEFER;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		dev_err(thc63->dev, "Unable to get \"vcc\" supply: %ld\n",
21162306a36Sopenharmony_ci			PTR_ERR(thc63->vcc));
21262306a36Sopenharmony_ci		return PTR_ERR(thc63->vcc);
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	ret = thc63_gpio_init(thc63);
21662306a36Sopenharmony_ci	if (ret)
21762306a36Sopenharmony_ci		return ret;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	ret = thc63_parse_dt(thc63);
22062306a36Sopenharmony_ci	if (ret)
22162306a36Sopenharmony_ci		return ret;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	thc63->bridge.driver_private = thc63;
22462306a36Sopenharmony_ci	thc63->bridge.of_node = pdev->dev.of_node;
22562306a36Sopenharmony_ci	thc63->bridge.funcs = &thc63_bridge_func;
22662306a36Sopenharmony_ci	thc63->bridge.timings = &thc63->timings;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	drm_bridge_add(&thc63->bridge);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	return 0;
23162306a36Sopenharmony_ci}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_cistatic void thc63_remove(struct platform_device *pdev)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	struct thc63_dev *thc63 = platform_get_drvdata(pdev);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	drm_bridge_remove(&thc63->bridge);
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic const struct of_device_id thc63_match[] = {
24162306a36Sopenharmony_ci	{ .compatible = "thine,thc63lvd1024", },
24262306a36Sopenharmony_ci	{ },
24362306a36Sopenharmony_ci};
24462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, thc63_match);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistatic struct platform_driver thc63_driver = {
24762306a36Sopenharmony_ci	.probe	= thc63_probe,
24862306a36Sopenharmony_ci	.remove_new = thc63_remove,
24962306a36Sopenharmony_ci	.driver	= {
25062306a36Sopenharmony_ci		.name		= "thc63lvd1024",
25162306a36Sopenharmony_ci		.of_match_table	= thc63_match,
25262306a36Sopenharmony_ci	},
25362306a36Sopenharmony_ci};
25462306a36Sopenharmony_cimodule_platform_driver(thc63_driver);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ciMODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>");
25762306a36Sopenharmony_ciMODULE_DESCRIPTION("Thine THC63LVD1024 LVDS decoder DRM bridge driver");
25862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
259