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