linux/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * MIPI-DSI Samsung s6d16d0 panel driver. This is a 864x480
   4 * AMOLED panel with a command-only DSI interface.
   5 */
   6
   7#include <drm/drm_modes.h>
   8#include <drm/drm_mipi_dsi.h>
   9#include <drm/drm_panel.h>
  10
  11#include <linux/gpio/consumer.h>
  12#include <linux/regulator/consumer.h>
  13#include <linux/delay.h>
  14#include <linux/of_device.h>
  15#include <linux/module.h>
  16
  17struct s6d16d0 {
  18        struct device *dev;
  19        struct drm_panel panel;
  20        struct regulator *supply;
  21        struct gpio_desc *reset_gpio;
  22};
  23
  24/*
  25 * The timings are not very helpful as the display is used in
  26 * command mode.
  27 */
  28static const struct drm_display_mode samsung_s6d16d0_mode = {
  29        /* HS clock, (htotal*vtotal*vrefresh)/1000 */
  30        .clock = 420160,
  31        .hdisplay = 864,
  32        .hsync_start = 864 + 154,
  33        .hsync_end = 864 + 154 + 16,
  34        .htotal = 864 + 154 + 16 + 32,
  35        .vdisplay = 480,
  36        .vsync_start = 480 + 1,
  37        .vsync_end = 480 + 1 + 1,
  38        .vtotal = 480 + 1 + 1 + 1,
  39        .width_mm = 84,
  40        .height_mm = 48,
  41};
  42
  43static inline struct s6d16d0 *panel_to_s6d16d0(struct drm_panel *panel)
  44{
  45        return container_of(panel, struct s6d16d0, panel);
  46}
  47
  48static int s6d16d0_unprepare(struct drm_panel *panel)
  49{
  50        struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
  51        struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
  52        int ret;
  53
  54        /* Enter sleep mode */
  55        ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
  56        if (ret) {
  57                dev_err(s6->dev, "failed to enter sleep mode (%d)\n", ret);
  58                return ret;
  59        }
  60
  61        /* Assert RESET */
  62        gpiod_set_value_cansleep(s6->reset_gpio, 1);
  63        regulator_disable(s6->supply);
  64
  65        return 0;
  66}
  67
  68static int s6d16d0_prepare(struct drm_panel *panel)
  69{
  70        struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
  71        struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
  72        int ret;
  73
  74        ret = regulator_enable(s6->supply);
  75        if (ret) {
  76                dev_err(s6->dev, "failed to enable supply (%d)\n", ret);
  77                return ret;
  78        }
  79
  80        /* Assert RESET */
  81        gpiod_set_value_cansleep(s6->reset_gpio, 1);
  82        udelay(10);
  83        /* De-assert RESET */
  84        gpiod_set_value_cansleep(s6->reset_gpio, 0);
  85        msleep(120);
  86
  87        /* Enabe tearing mode: send TE (tearing effect) at VBLANK */
  88        ret = mipi_dsi_dcs_set_tear_on(dsi,
  89                                       MIPI_DSI_DCS_TEAR_MODE_VBLANK);
  90        if (ret) {
  91                dev_err(s6->dev, "failed to enable vblank TE (%d)\n", ret);
  92                return ret;
  93        }
  94        /* Exit sleep mode and power on */
  95        ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
  96        if (ret) {
  97                dev_err(s6->dev, "failed to exit sleep mode (%d)\n", ret);
  98                return ret;
  99        }
 100
 101        return 0;
 102}
 103
 104static int s6d16d0_enable(struct drm_panel *panel)
 105{
 106        struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
 107        struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
 108        int ret;
 109
 110        ret = mipi_dsi_dcs_set_display_on(dsi);
 111        if (ret) {
 112                dev_err(s6->dev, "failed to turn display on (%d)\n", ret);
 113                return ret;
 114        }
 115
 116        return 0;
 117}
 118
 119static int s6d16d0_disable(struct drm_panel *panel)
 120{
 121        struct s6d16d0 *s6 = panel_to_s6d16d0(panel);
 122        struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev);
 123        int ret;
 124
 125        ret = mipi_dsi_dcs_set_display_off(dsi);
 126        if (ret) {
 127                dev_err(s6->dev, "failed to turn display off (%d)\n", ret);
 128                return ret;
 129        }
 130
 131        return 0;
 132}
 133
 134static int s6d16d0_get_modes(struct drm_panel *panel,
 135                             struct drm_connector *connector)
 136{
 137        struct drm_display_mode *mode;
 138
 139        mode = drm_mode_duplicate(connector->dev, &samsung_s6d16d0_mode);
 140        if (!mode) {
 141                dev_err(panel->dev, "bad mode or failed to add mode\n");
 142                return -EINVAL;
 143        }
 144        drm_mode_set_name(mode);
 145        mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
 146
 147        connector->display_info.width_mm = mode->width_mm;
 148        connector->display_info.height_mm = mode->height_mm;
 149
 150        drm_mode_probed_add(connector, mode);
 151
 152        return 1; /* Number of modes */
 153}
 154
 155static const struct drm_panel_funcs s6d16d0_drm_funcs = {
 156        .disable = s6d16d0_disable,
 157        .unprepare = s6d16d0_unprepare,
 158        .prepare = s6d16d0_prepare,
 159        .enable = s6d16d0_enable,
 160        .get_modes = s6d16d0_get_modes,
 161};
 162
 163static int s6d16d0_probe(struct mipi_dsi_device *dsi)
 164{
 165        struct device *dev = &dsi->dev;
 166        struct s6d16d0 *s6;
 167        int ret;
 168
 169        s6 = devm_kzalloc(dev, sizeof(struct s6d16d0), GFP_KERNEL);
 170        if (!s6)
 171                return -ENOMEM;
 172
 173        mipi_dsi_set_drvdata(dsi, s6);
 174        s6->dev = dev;
 175
 176        dsi->lanes = 2;
 177        dsi->format = MIPI_DSI_FMT_RGB888;
 178        dsi->hs_rate = 420160000;
 179        dsi->lp_rate = 19200000;
 180        /*
 181         * This display uses command mode so no MIPI_DSI_MODE_VIDEO
 182         * or MIPI_DSI_MODE_VIDEO_SYNC_PULSE
 183         *
 184         * As we only send commands we do not need to be continuously
 185         * clocked.
 186         */
 187        dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS;
 188
 189        s6->supply = devm_regulator_get(dev, "vdd1");
 190        if (IS_ERR(s6->supply))
 191                return PTR_ERR(s6->supply);
 192
 193        /* This asserts RESET by default */
 194        s6->reset_gpio = devm_gpiod_get_optional(dev, "reset",
 195                                                 GPIOD_OUT_HIGH);
 196        if (IS_ERR(s6->reset_gpio)) {
 197                ret = PTR_ERR(s6->reset_gpio);
 198                if (ret != -EPROBE_DEFER)
 199                        dev_err(dev, "failed to request GPIO (%d)\n", ret);
 200                return ret;
 201        }
 202
 203        drm_panel_init(&s6->panel, dev, &s6d16d0_drm_funcs,
 204                       DRM_MODE_CONNECTOR_DSI);
 205
 206        drm_panel_add(&s6->panel);
 207
 208        ret = mipi_dsi_attach(dsi);
 209        if (ret < 0)
 210                drm_panel_remove(&s6->panel);
 211
 212        return ret;
 213}
 214
 215static int s6d16d0_remove(struct mipi_dsi_device *dsi)
 216{
 217        struct s6d16d0 *s6 = mipi_dsi_get_drvdata(dsi);
 218
 219        mipi_dsi_detach(dsi);
 220        drm_panel_remove(&s6->panel);
 221
 222        return 0;
 223}
 224
 225static const struct of_device_id s6d16d0_of_match[] = {
 226        { .compatible = "samsung,s6d16d0" },
 227        { }
 228};
 229MODULE_DEVICE_TABLE(of, s6d16d0_of_match);
 230
 231static struct mipi_dsi_driver s6d16d0_driver = {
 232        .probe = s6d16d0_probe,
 233        .remove = s6d16d0_remove,
 234        .driver = {
 235                .name = "panel-samsung-s6d16d0",
 236                .of_match_table = s6d16d0_of_match,
 237        },
 238};
 239module_mipi_dsi_driver(s6d16d0_driver);
 240
 241MODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>");
 242MODULE_DESCRIPTION("MIPI-DSI s6d16d0 Panel Driver");
 243MODULE_LICENSE("GPL v2");
 244