thc63lvd1024.c (5882B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * THC63LVD1024 LVDS to parallel data DRM bridge driver. 4 * 5 * Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org> 6 */ 7 8#include <linux/gpio/consumer.h> 9#include <linux/module.h> 10#include <linux/of.h> 11#include <linux/of_graph.h> 12#include <linux/platform_device.h> 13#include <linux/regulator/consumer.h> 14#include <linux/slab.h> 15 16#include <drm/drm_bridge.h> 17#include <drm/drm_panel.h> 18 19enum thc63_ports { 20 THC63_LVDS_IN0, 21 THC63_LVDS_IN1, 22 THC63_RGB_OUT0, 23 THC63_RGB_OUT1, 24}; 25 26struct thc63_dev { 27 struct device *dev; 28 29 struct regulator *vcc; 30 31 struct gpio_desc *pdwn; 32 struct gpio_desc *oe; 33 34 struct drm_bridge bridge; 35 struct drm_bridge *next; 36 37 struct drm_bridge_timings timings; 38}; 39 40static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge) 41{ 42 return container_of(bridge, struct thc63_dev, bridge); 43} 44 45static int thc63_attach(struct drm_bridge *bridge, 46 enum drm_bridge_attach_flags flags) 47{ 48 struct thc63_dev *thc63 = to_thc63(bridge); 49 50 return drm_bridge_attach(bridge->encoder, thc63->next, bridge, flags); 51} 52 53static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge, 54 const struct drm_display_info *info, 55 const struct drm_display_mode *mode) 56{ 57 struct thc63_dev *thc63 = to_thc63(bridge); 58 unsigned int min_freq; 59 unsigned int max_freq; 60 61 /* 62 * The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but 63 * dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out 64 * isn't supported by the driver yet, simply derive the limits from the 65 * input mode. 66 */ 67 if (thc63->timings.dual_link) { 68 min_freq = 40000; 69 max_freq = 150000; 70 } else { 71 min_freq = 8000; 72 max_freq = 135000; 73 } 74 75 if (mode->clock < min_freq) 76 return MODE_CLOCK_LOW; 77 78 if (mode->clock > max_freq) 79 return MODE_CLOCK_HIGH; 80 81 return MODE_OK; 82} 83 84static void thc63_enable(struct drm_bridge *bridge) 85{ 86 struct thc63_dev *thc63 = to_thc63(bridge); 87 int ret; 88 89 ret = regulator_enable(thc63->vcc); 90 if (ret) { 91 dev_err(thc63->dev, 92 "Failed to enable regulator \"vcc\": %d\n", ret); 93 return; 94 } 95 96 gpiod_set_value(thc63->pdwn, 0); 97 gpiod_set_value(thc63->oe, 1); 98} 99 100static void thc63_disable(struct drm_bridge *bridge) 101{ 102 struct thc63_dev *thc63 = to_thc63(bridge); 103 int ret; 104 105 gpiod_set_value(thc63->oe, 0); 106 gpiod_set_value(thc63->pdwn, 1); 107 108 ret = regulator_disable(thc63->vcc); 109 if (ret) 110 dev_err(thc63->dev, 111 "Failed to disable regulator \"vcc\": %d\n", ret); 112} 113 114static const struct drm_bridge_funcs thc63_bridge_func = { 115 .attach = thc63_attach, 116 .mode_valid = thc63_mode_valid, 117 .enable = thc63_enable, 118 .disable = thc63_disable, 119}; 120 121static int thc63_parse_dt(struct thc63_dev *thc63) 122{ 123 struct device_node *endpoint; 124 struct device_node *remote; 125 126 endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node, 127 THC63_RGB_OUT0, -1); 128 if (!endpoint) { 129 dev_err(thc63->dev, "Missing endpoint in port@%u\n", 130 THC63_RGB_OUT0); 131 return -ENODEV; 132 } 133 134 remote = of_graph_get_remote_port_parent(endpoint); 135 of_node_put(endpoint); 136 if (!remote) { 137 dev_err(thc63->dev, "Endpoint in port@%u unconnected\n", 138 THC63_RGB_OUT0); 139 return -ENODEV; 140 } 141 142 if (!of_device_is_available(remote)) { 143 dev_err(thc63->dev, "port@%u remote endpoint is disabled\n", 144 THC63_RGB_OUT0); 145 of_node_put(remote); 146 return -ENODEV; 147 } 148 149 thc63->next = of_drm_find_bridge(remote); 150 of_node_put(remote); 151 if (!thc63->next) 152 return -EPROBE_DEFER; 153 154 endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node, 155 THC63_LVDS_IN1, -1); 156 if (endpoint) { 157 remote = of_graph_get_remote_port_parent(endpoint); 158 of_node_put(endpoint); 159 160 if (remote) { 161 if (of_device_is_available(remote)) 162 thc63->timings.dual_link = true; 163 of_node_put(remote); 164 } 165 } 166 167 dev_dbg(thc63->dev, "operating in %s-link mode\n", 168 thc63->timings.dual_link ? "dual" : "single"); 169 170 return 0; 171} 172 173static int thc63_gpio_init(struct thc63_dev *thc63) 174{ 175 thc63->oe = devm_gpiod_get_optional(thc63->dev, "oe", GPIOD_OUT_LOW); 176 if (IS_ERR(thc63->oe)) { 177 dev_err(thc63->dev, "Unable to get \"oe-gpios\": %ld\n", 178 PTR_ERR(thc63->oe)); 179 return PTR_ERR(thc63->oe); 180 } 181 182 thc63->pdwn = devm_gpiod_get_optional(thc63->dev, "powerdown", 183 GPIOD_OUT_HIGH); 184 if (IS_ERR(thc63->pdwn)) { 185 dev_err(thc63->dev, "Unable to get \"powerdown-gpios\": %ld\n", 186 PTR_ERR(thc63->pdwn)); 187 return PTR_ERR(thc63->pdwn); 188 } 189 190 return 0; 191} 192 193static int thc63_probe(struct platform_device *pdev) 194{ 195 struct thc63_dev *thc63; 196 int ret; 197 198 thc63 = devm_kzalloc(&pdev->dev, sizeof(*thc63), GFP_KERNEL); 199 if (!thc63) 200 return -ENOMEM; 201 202 thc63->dev = &pdev->dev; 203 platform_set_drvdata(pdev, thc63); 204 205 thc63->vcc = devm_regulator_get(thc63->dev, "vcc"); 206 if (IS_ERR(thc63->vcc)) { 207 if (PTR_ERR(thc63->vcc) == -EPROBE_DEFER) 208 return -EPROBE_DEFER; 209 210 dev_err(thc63->dev, "Unable to get \"vcc\" supply: %ld\n", 211 PTR_ERR(thc63->vcc)); 212 return PTR_ERR(thc63->vcc); 213 } 214 215 ret = thc63_gpio_init(thc63); 216 if (ret) 217 return ret; 218 219 ret = thc63_parse_dt(thc63); 220 if (ret) 221 return ret; 222 223 thc63->bridge.driver_private = thc63; 224 thc63->bridge.of_node = pdev->dev.of_node; 225 thc63->bridge.funcs = &thc63_bridge_func; 226 thc63->bridge.timings = &thc63->timings; 227 228 drm_bridge_add(&thc63->bridge); 229 230 return 0; 231} 232 233static int thc63_remove(struct platform_device *pdev) 234{ 235 struct thc63_dev *thc63 = platform_get_drvdata(pdev); 236 237 drm_bridge_remove(&thc63->bridge); 238 239 return 0; 240} 241 242static const struct of_device_id thc63_match[] = { 243 { .compatible = "thine,thc63lvd1024", }, 244 { }, 245}; 246MODULE_DEVICE_TABLE(of, thc63_match); 247 248static struct platform_driver thc63_driver = { 249 .probe = thc63_probe, 250 .remove = thc63_remove, 251 .driver = { 252 .name = "thc63lvd1024", 253 .of_match_table = thc63_match, 254 }, 255}; 256module_platform_driver(thc63_driver); 257 258MODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>"); 259MODULE_DESCRIPTION("Thine THC63LVD1024 LVDS decoder DRM bridge driver"); 260MODULE_LICENSE("GPL v2");