linux/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (C) 2020 BayLibre, SAS
   4 * Author: Neil Armstrong <narmstrong@baylibre.com>
   5 */
   6
   7#include <linux/delay.h>
   8#include <linux/gpio/consumer.h>
   9#include <linux/module.h>
  10#include <linux/of.h>
  11#include <linux/regulator/consumer.h>
  12
  13#include <video/mipi_display.h>
  14
  15#include <drm/drm_crtc.h>
  16#include <drm/drm_device.h>
  17#include <drm/drm_mipi_dsi.h>
  18#include <drm/drm_modes.h>
  19#include <drm/drm_panel.h>
  20
  21struct tdo_tl070wsh30_panel {
  22        struct drm_panel base;
  23        struct mipi_dsi_device *link;
  24
  25        struct regulator *supply;
  26        struct gpio_desc *reset_gpio;
  27
  28        bool prepared;
  29};
  30
  31static inline
  32struct tdo_tl070wsh30_panel *to_tdo_tl070wsh30_panel(struct drm_panel *panel)
  33{
  34        return container_of(panel, struct tdo_tl070wsh30_panel, base);
  35}
  36
  37static int tdo_tl070wsh30_panel_prepare(struct drm_panel *panel)
  38{
  39        struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel);
  40        int err;
  41
  42        if (tdo_tl070wsh30->prepared)
  43                return 0;
  44
  45        err = regulator_enable(tdo_tl070wsh30->supply);
  46        if (err < 0)
  47                return err;
  48
  49        usleep_range(10000, 11000);
  50
  51        gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 1);
  52
  53        usleep_range(10000, 11000);
  54
  55        gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 0);
  56
  57        msleep(200);
  58
  59        err = mipi_dsi_dcs_exit_sleep_mode(tdo_tl070wsh30->link);
  60        if (err < 0) {
  61                dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
  62                regulator_disable(tdo_tl070wsh30->supply);
  63                return err;
  64        }
  65
  66        msleep(200);
  67
  68        err = mipi_dsi_dcs_set_display_on(tdo_tl070wsh30->link);
  69        if (err < 0) {
  70                dev_err(panel->dev, "failed to set display on: %d\n", err);
  71                regulator_disable(tdo_tl070wsh30->supply);
  72                return err;
  73        }
  74
  75        msleep(20);
  76
  77        tdo_tl070wsh30->prepared = true;
  78
  79        return 0;
  80}
  81
  82static int tdo_tl070wsh30_panel_unprepare(struct drm_panel *panel)
  83{
  84        struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel);
  85        int err;
  86
  87        if (!tdo_tl070wsh30->prepared)
  88                return 0;
  89
  90        err = mipi_dsi_dcs_set_display_off(tdo_tl070wsh30->link);
  91        if (err < 0)
  92                dev_err(panel->dev, "failed to set display off: %d\n", err);
  93
  94        usleep_range(10000, 11000);
  95
  96        err = mipi_dsi_dcs_enter_sleep_mode(tdo_tl070wsh30->link);
  97        if (err < 0) {
  98                dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
  99                return err;
 100        }
 101
 102        usleep_range(10000, 11000);
 103
 104        regulator_disable(tdo_tl070wsh30->supply);
 105
 106        tdo_tl070wsh30->prepared = false;
 107
 108        return 0;
 109}
 110
 111static const struct drm_display_mode default_mode = {
 112        .clock = 47250,
 113        .hdisplay = 1024,
 114        .hsync_start = 1024 + 46,
 115        .hsync_end = 1024 + 46 + 80,
 116        .htotal = 1024 + 46 + 80 + 100,
 117        .vdisplay = 600,
 118        .vsync_start = 600 + 5,
 119        .vsync_end = 600 + 5 + 5,
 120        .vtotal = 600 + 5 + 5 + 20,
 121        .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
 122};
 123
 124static int tdo_tl070wsh30_panel_get_modes(struct drm_panel *panel,
 125                                       struct drm_connector *connector)
 126{
 127        struct drm_display_mode *mode;
 128
 129        mode = drm_mode_duplicate(connector->dev, &default_mode);
 130        if (!mode) {
 131                dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
 132                        default_mode.hdisplay, default_mode.vdisplay,
 133                        drm_mode_vrefresh(&default_mode));
 134                return -ENOMEM;
 135        }
 136
 137        drm_mode_set_name(mode);
 138
 139        drm_mode_probed_add(connector, mode);
 140
 141        connector->display_info.width_mm = 154;
 142        connector->display_info.height_mm = 85;
 143        connector->display_info.bpc = 8;
 144
 145        return 1;
 146}
 147
 148static const struct drm_panel_funcs tdo_tl070wsh30_panel_funcs = {
 149        .unprepare = tdo_tl070wsh30_panel_unprepare,
 150        .prepare = tdo_tl070wsh30_panel_prepare,
 151        .get_modes = tdo_tl070wsh30_panel_get_modes,
 152};
 153
 154static const struct of_device_id tdo_tl070wsh30_of_match[] = {
 155        { .compatible = "tdo,tl070wsh30", },
 156        { /* sentinel */ }
 157};
 158MODULE_DEVICE_TABLE(of, tdo_tl070wsh30_of_match);
 159
 160static int tdo_tl070wsh30_panel_add(struct tdo_tl070wsh30_panel *tdo_tl070wsh30)
 161{
 162        struct device *dev = &tdo_tl070wsh30->link->dev;
 163        int err;
 164
 165        tdo_tl070wsh30->supply = devm_regulator_get(dev, "power");
 166        if (IS_ERR(tdo_tl070wsh30->supply))
 167                return PTR_ERR(tdo_tl070wsh30->supply);
 168
 169        tdo_tl070wsh30->reset_gpio = devm_gpiod_get(dev, "reset",
 170                                                    GPIOD_OUT_LOW);
 171        if (IS_ERR(tdo_tl070wsh30->reset_gpio)) {
 172                err = PTR_ERR(tdo_tl070wsh30->reset_gpio);
 173                dev_dbg(dev, "failed to get reset gpio: %d\n", err);
 174                return err;
 175        }
 176
 177        drm_panel_init(&tdo_tl070wsh30->base, &tdo_tl070wsh30->link->dev,
 178                       &tdo_tl070wsh30_panel_funcs, DRM_MODE_CONNECTOR_DSI);
 179
 180        err = drm_panel_of_backlight(&tdo_tl070wsh30->base);
 181        if (err)
 182                return err;
 183
 184        drm_panel_add(&tdo_tl070wsh30->base);
 185
 186        return 0;
 187}
 188
 189static int tdo_tl070wsh30_panel_probe(struct mipi_dsi_device *dsi)
 190{
 191        struct tdo_tl070wsh30_panel *tdo_tl070wsh30;
 192        int err;
 193
 194        dsi->lanes = 4;
 195        dsi->format = MIPI_DSI_FMT_RGB888;
 196        dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM;
 197
 198        tdo_tl070wsh30 = devm_kzalloc(&dsi->dev, sizeof(*tdo_tl070wsh30),
 199                                    GFP_KERNEL);
 200        if (!tdo_tl070wsh30)
 201                return -ENOMEM;
 202
 203        mipi_dsi_set_drvdata(dsi, tdo_tl070wsh30);
 204        tdo_tl070wsh30->link = dsi;
 205
 206        err = tdo_tl070wsh30_panel_add(tdo_tl070wsh30);
 207        if (err < 0)
 208                return err;
 209
 210        return mipi_dsi_attach(dsi);
 211}
 212
 213static int tdo_tl070wsh30_panel_remove(struct mipi_dsi_device *dsi)
 214{
 215        struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi);
 216        int err;
 217
 218        err = mipi_dsi_detach(dsi);
 219        if (err < 0)
 220                dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
 221
 222        drm_panel_remove(&tdo_tl070wsh30->base);
 223        drm_panel_disable(&tdo_tl070wsh30->base);
 224        drm_panel_unprepare(&tdo_tl070wsh30->base);
 225
 226        return 0;
 227}
 228
 229static void tdo_tl070wsh30_panel_shutdown(struct mipi_dsi_device *dsi)
 230{
 231        struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi);
 232
 233        drm_panel_disable(&tdo_tl070wsh30->base);
 234        drm_panel_unprepare(&tdo_tl070wsh30->base);
 235}
 236
 237static struct mipi_dsi_driver tdo_tl070wsh30_panel_driver = {
 238        .driver = {
 239                .name = "panel-tdo-tl070wsh30",
 240                .of_match_table = tdo_tl070wsh30_of_match,
 241        },
 242        .probe = tdo_tl070wsh30_panel_probe,
 243        .remove = tdo_tl070wsh30_panel_remove,
 244        .shutdown = tdo_tl070wsh30_panel_shutdown,
 245};
 246module_mipi_dsi_driver(tdo_tl070wsh30_panel_driver);
 247
 248MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
 249MODULE_DESCRIPTION("TDO TL070WSH30 panel driver");
 250MODULE_LICENSE("GPL v2");
 251