linux/drivers/gpu/drm/bridge/thc63lvd1024.c
<<
>>
Prefs
   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");
 261