18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * THC63LVD1024 LVDS to parallel data DRM bridge driver. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/of.h> 118c2ecf20Sopenharmony_ci#include <linux/of_graph.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <drm/drm_bridge.h> 178c2ecf20Sopenharmony_ci#include <drm/drm_panel.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cienum thc63_ports { 208c2ecf20Sopenharmony_ci THC63_LVDS_IN0, 218c2ecf20Sopenharmony_ci THC63_LVDS_IN1, 228c2ecf20Sopenharmony_ci THC63_RGB_OUT0, 238c2ecf20Sopenharmony_ci THC63_RGB_OUT1, 248c2ecf20Sopenharmony_ci}; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistruct thc63_dev { 278c2ecf20Sopenharmony_ci struct device *dev; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci struct regulator *vcc; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci struct gpio_desc *pdwn; 328c2ecf20Sopenharmony_ci struct gpio_desc *oe; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci struct drm_bridge bridge; 358c2ecf20Sopenharmony_ci struct drm_bridge *next; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci struct drm_bridge_timings timings; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic inline struct thc63_dev *to_thc63(struct drm_bridge *bridge) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci return container_of(bridge, struct thc63_dev, bridge); 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic int thc63_attach(struct drm_bridge *bridge, 468c2ecf20Sopenharmony_ci enum drm_bridge_attach_flags flags) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci struct thc63_dev *thc63 = to_thc63(bridge); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci return drm_bridge_attach(bridge->encoder, thc63->next, bridge, flags); 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge, 548c2ecf20Sopenharmony_ci const struct drm_display_info *info, 558c2ecf20Sopenharmony_ci const struct drm_display_mode *mode) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci struct thc63_dev *thc63 = to_thc63(bridge); 588c2ecf20Sopenharmony_ci unsigned int min_freq; 598c2ecf20Sopenharmony_ci unsigned int max_freq; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci /* 628c2ecf20Sopenharmony_ci * The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but 638c2ecf20Sopenharmony_ci * dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out 648c2ecf20Sopenharmony_ci * isn't supported by the driver yet, simply derive the limits from the 658c2ecf20Sopenharmony_ci * input mode. 668c2ecf20Sopenharmony_ci */ 678c2ecf20Sopenharmony_ci if (thc63->timings.dual_link) { 688c2ecf20Sopenharmony_ci min_freq = 40000; 698c2ecf20Sopenharmony_ci max_freq = 150000; 708c2ecf20Sopenharmony_ci } else { 718c2ecf20Sopenharmony_ci min_freq = 8000; 728c2ecf20Sopenharmony_ci max_freq = 135000; 738c2ecf20Sopenharmony_ci } 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (mode->clock < min_freq) 768c2ecf20Sopenharmony_ci return MODE_CLOCK_LOW; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci if (mode->clock > max_freq) 798c2ecf20Sopenharmony_ci return MODE_CLOCK_HIGH; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return MODE_OK; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic void thc63_enable(struct drm_bridge *bridge) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci struct thc63_dev *thc63 = to_thc63(bridge); 878c2ecf20Sopenharmony_ci int ret; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci ret = regulator_enable(thc63->vcc); 908c2ecf20Sopenharmony_ci if (ret) { 918c2ecf20Sopenharmony_ci dev_err(thc63->dev, 928c2ecf20Sopenharmony_ci "Failed to enable regulator \"vcc\": %d\n", ret); 938c2ecf20Sopenharmony_ci return; 948c2ecf20Sopenharmony_ci } 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci gpiod_set_value(thc63->pdwn, 0); 978c2ecf20Sopenharmony_ci gpiod_set_value(thc63->oe, 1); 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic void thc63_disable(struct drm_bridge *bridge) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct thc63_dev *thc63 = to_thc63(bridge); 1038c2ecf20Sopenharmony_ci int ret; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci gpiod_set_value(thc63->oe, 0); 1068c2ecf20Sopenharmony_ci gpiod_set_value(thc63->pdwn, 1); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci ret = regulator_disable(thc63->vcc); 1098c2ecf20Sopenharmony_ci if (ret) 1108c2ecf20Sopenharmony_ci dev_err(thc63->dev, 1118c2ecf20Sopenharmony_ci "Failed to disable regulator \"vcc\": %d\n", ret); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic const struct drm_bridge_funcs thc63_bridge_func = { 1158c2ecf20Sopenharmony_ci .attach = thc63_attach, 1168c2ecf20Sopenharmony_ci .mode_valid = thc63_mode_valid, 1178c2ecf20Sopenharmony_ci .enable = thc63_enable, 1188c2ecf20Sopenharmony_ci .disable = thc63_disable, 1198c2ecf20Sopenharmony_ci}; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic int thc63_parse_dt(struct thc63_dev *thc63) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct device_node *endpoint; 1248c2ecf20Sopenharmony_ci struct device_node *remote; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node, 1278c2ecf20Sopenharmony_ci THC63_RGB_OUT0, -1); 1288c2ecf20Sopenharmony_ci if (!endpoint) { 1298c2ecf20Sopenharmony_ci dev_err(thc63->dev, "Missing endpoint in port@%u\n", 1308c2ecf20Sopenharmony_ci THC63_RGB_OUT0); 1318c2ecf20Sopenharmony_ci return -ENODEV; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci remote = of_graph_get_remote_port_parent(endpoint); 1358c2ecf20Sopenharmony_ci of_node_put(endpoint); 1368c2ecf20Sopenharmony_ci if (!remote) { 1378c2ecf20Sopenharmony_ci dev_err(thc63->dev, "Endpoint in port@%u unconnected\n", 1388c2ecf20Sopenharmony_ci THC63_RGB_OUT0); 1398c2ecf20Sopenharmony_ci return -ENODEV; 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci if (!of_device_is_available(remote)) { 1438c2ecf20Sopenharmony_ci dev_err(thc63->dev, "port@%u remote endpoint is disabled\n", 1448c2ecf20Sopenharmony_ci THC63_RGB_OUT0); 1458c2ecf20Sopenharmony_ci of_node_put(remote); 1468c2ecf20Sopenharmony_ci return -ENODEV; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci thc63->next = of_drm_find_bridge(remote); 1508c2ecf20Sopenharmony_ci of_node_put(remote); 1518c2ecf20Sopenharmony_ci if (!thc63->next) 1528c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node, 1558c2ecf20Sopenharmony_ci THC63_LVDS_IN1, -1); 1568c2ecf20Sopenharmony_ci if (endpoint) { 1578c2ecf20Sopenharmony_ci remote = of_graph_get_remote_port_parent(endpoint); 1588c2ecf20Sopenharmony_ci of_node_put(endpoint); 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (remote) { 1618c2ecf20Sopenharmony_ci if (of_device_is_available(remote)) 1628c2ecf20Sopenharmony_ci thc63->timings.dual_link = true; 1638c2ecf20Sopenharmony_ci of_node_put(remote); 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci dev_dbg(thc63->dev, "operating in %s-link mode\n", 1688c2ecf20Sopenharmony_ci thc63->timings.dual_link ? "dual" : "single"); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci return 0; 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_cistatic int thc63_gpio_init(struct thc63_dev *thc63) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci thc63->oe = devm_gpiod_get_optional(thc63->dev, "oe", GPIOD_OUT_LOW); 1768c2ecf20Sopenharmony_ci if (IS_ERR(thc63->oe)) { 1778c2ecf20Sopenharmony_ci dev_err(thc63->dev, "Unable to get \"oe-gpios\": %ld\n", 1788c2ecf20Sopenharmony_ci PTR_ERR(thc63->oe)); 1798c2ecf20Sopenharmony_ci return PTR_ERR(thc63->oe); 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci thc63->pdwn = devm_gpiod_get_optional(thc63->dev, "powerdown", 1838c2ecf20Sopenharmony_ci GPIOD_OUT_HIGH); 1848c2ecf20Sopenharmony_ci if (IS_ERR(thc63->pdwn)) { 1858c2ecf20Sopenharmony_ci dev_err(thc63->dev, "Unable to get \"powerdown-gpios\": %ld\n", 1868c2ecf20Sopenharmony_ci PTR_ERR(thc63->pdwn)); 1878c2ecf20Sopenharmony_ci return PTR_ERR(thc63->pdwn); 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci return 0; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic int thc63_probe(struct platform_device *pdev) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci struct thc63_dev *thc63; 1968c2ecf20Sopenharmony_ci int ret; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci thc63 = devm_kzalloc(&pdev->dev, sizeof(*thc63), GFP_KERNEL); 1998c2ecf20Sopenharmony_ci if (!thc63) 2008c2ecf20Sopenharmony_ci return -ENOMEM; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci thc63->dev = &pdev->dev; 2038c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, thc63); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci thc63->vcc = devm_regulator_get_optional(thc63->dev, "vcc"); 2068c2ecf20Sopenharmony_ci if (IS_ERR(thc63->vcc)) { 2078c2ecf20Sopenharmony_ci if (PTR_ERR(thc63->vcc) == -EPROBE_DEFER) 2088c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci dev_err(thc63->dev, "Unable to get \"vcc\" supply: %ld\n", 2118c2ecf20Sopenharmony_ci PTR_ERR(thc63->vcc)); 2128c2ecf20Sopenharmony_ci return PTR_ERR(thc63->vcc); 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci ret = thc63_gpio_init(thc63); 2168c2ecf20Sopenharmony_ci if (ret) 2178c2ecf20Sopenharmony_ci return ret; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci ret = thc63_parse_dt(thc63); 2208c2ecf20Sopenharmony_ci if (ret) 2218c2ecf20Sopenharmony_ci return ret; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci thc63->bridge.driver_private = thc63; 2248c2ecf20Sopenharmony_ci thc63->bridge.of_node = pdev->dev.of_node; 2258c2ecf20Sopenharmony_ci thc63->bridge.funcs = &thc63_bridge_func; 2268c2ecf20Sopenharmony_ci thc63->bridge.timings = &thc63->timings; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci drm_bridge_add(&thc63->bridge); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci return 0; 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cistatic int thc63_remove(struct platform_device *pdev) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci struct thc63_dev *thc63 = platform_get_drvdata(pdev); 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci drm_bridge_remove(&thc63->bridge); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci return 0; 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cistatic const struct of_device_id thc63_match[] = { 2438c2ecf20Sopenharmony_ci { .compatible = "thine,thc63lvd1024", }, 2448c2ecf20Sopenharmony_ci { }, 2458c2ecf20Sopenharmony_ci}; 2468c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, thc63_match); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_cistatic struct platform_driver thc63_driver = { 2498c2ecf20Sopenharmony_ci .probe = thc63_probe, 2508c2ecf20Sopenharmony_ci .remove = thc63_remove, 2518c2ecf20Sopenharmony_ci .driver = { 2528c2ecf20Sopenharmony_ci .name = "thc63lvd1024", 2538c2ecf20Sopenharmony_ci .of_match_table = thc63_match, 2548c2ecf20Sopenharmony_ci }, 2558c2ecf20Sopenharmony_ci}; 2568c2ecf20Sopenharmony_cimodule_platform_driver(thc63_driver); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>"); 2598c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Thine THC63LVD1024 LVDS decoder DRM bridge driver"); 2608c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 261