Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Copyright (C) 2020 BayLibre, SAS
0004  * Author: Neil Armstrong <narmstrong@baylibre.com>
0005  */
0006 
0007 #include <linux/delay.h>
0008 #include <linux/gpio/consumer.h>
0009 #include <linux/module.h>
0010 #include <linux/of.h>
0011 #include <linux/regulator/consumer.h>
0012 
0013 #include <video/mipi_display.h>
0014 
0015 #include <drm/drm_crtc.h>
0016 #include <drm/drm_device.h>
0017 #include <drm/drm_mipi_dsi.h>
0018 #include <drm/drm_modes.h>
0019 #include <drm/drm_panel.h>
0020 
0021 struct tdo_tl070wsh30_panel {
0022     struct drm_panel base;
0023     struct mipi_dsi_device *link;
0024 
0025     struct regulator *supply;
0026     struct gpio_desc *reset_gpio;
0027 
0028     bool prepared;
0029 };
0030 
0031 static inline
0032 struct tdo_tl070wsh30_panel *to_tdo_tl070wsh30_panel(struct drm_panel *panel)
0033 {
0034     return container_of(panel, struct tdo_tl070wsh30_panel, base);
0035 }
0036 
0037 static int tdo_tl070wsh30_panel_prepare(struct drm_panel *panel)
0038 {
0039     struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel);
0040     int err;
0041 
0042     if (tdo_tl070wsh30->prepared)
0043         return 0;
0044 
0045     err = regulator_enable(tdo_tl070wsh30->supply);
0046     if (err < 0)
0047         return err;
0048 
0049     usleep_range(10000, 11000);
0050 
0051     gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 1);
0052 
0053     usleep_range(10000, 11000);
0054 
0055     gpiod_set_value_cansleep(tdo_tl070wsh30->reset_gpio, 0);
0056 
0057     msleep(200);
0058 
0059     err = mipi_dsi_dcs_exit_sleep_mode(tdo_tl070wsh30->link);
0060     if (err < 0) {
0061         dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
0062         regulator_disable(tdo_tl070wsh30->supply);
0063         return err;
0064     }
0065 
0066     msleep(200);
0067 
0068     err = mipi_dsi_dcs_set_display_on(tdo_tl070wsh30->link);
0069     if (err < 0) {
0070         dev_err(panel->dev, "failed to set display on: %d\n", err);
0071         regulator_disable(tdo_tl070wsh30->supply);
0072         return err;
0073     }
0074 
0075     msleep(20);
0076 
0077     tdo_tl070wsh30->prepared = true;
0078 
0079     return 0;
0080 }
0081 
0082 static int tdo_tl070wsh30_panel_unprepare(struct drm_panel *panel)
0083 {
0084     struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel);
0085     int err;
0086 
0087     if (!tdo_tl070wsh30->prepared)
0088         return 0;
0089 
0090     err = mipi_dsi_dcs_set_display_off(tdo_tl070wsh30->link);
0091     if (err < 0)
0092         dev_err(panel->dev, "failed to set display off: %d\n", err);
0093 
0094     usleep_range(10000, 11000);
0095 
0096     err = mipi_dsi_dcs_enter_sleep_mode(tdo_tl070wsh30->link);
0097     if (err < 0) {
0098         dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
0099         return err;
0100     }
0101 
0102     usleep_range(10000, 11000);
0103 
0104     regulator_disable(tdo_tl070wsh30->supply);
0105 
0106     tdo_tl070wsh30->prepared = false;
0107 
0108     return 0;
0109 }
0110 
0111 static const struct drm_display_mode default_mode = {
0112     .clock = 47250,
0113     .hdisplay = 1024,
0114     .hsync_start = 1024 + 46,
0115     .hsync_end = 1024 + 46 + 80,
0116     .htotal = 1024 + 46 + 80 + 100,
0117     .vdisplay = 600,
0118     .vsync_start = 600 + 5,
0119     .vsync_end = 600 + 5 + 5,
0120     .vtotal = 600 + 5 + 5 + 20,
0121     .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
0122 };
0123 
0124 static int tdo_tl070wsh30_panel_get_modes(struct drm_panel *panel,
0125                        struct drm_connector *connector)
0126 {
0127     struct drm_display_mode *mode;
0128 
0129     mode = drm_mode_duplicate(connector->dev, &default_mode);
0130     if (!mode) {
0131         dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
0132             default_mode.hdisplay, default_mode.vdisplay,
0133             drm_mode_vrefresh(&default_mode));
0134         return -ENOMEM;
0135     }
0136 
0137     drm_mode_set_name(mode);
0138 
0139     drm_mode_probed_add(connector, mode);
0140 
0141     connector->display_info.width_mm = 154;
0142     connector->display_info.height_mm = 85;
0143     connector->display_info.bpc = 8;
0144 
0145     return 1;
0146 }
0147 
0148 static const struct drm_panel_funcs tdo_tl070wsh30_panel_funcs = {
0149     .unprepare = tdo_tl070wsh30_panel_unprepare,
0150     .prepare = tdo_tl070wsh30_panel_prepare,
0151     .get_modes = tdo_tl070wsh30_panel_get_modes,
0152 };
0153 
0154 static const struct of_device_id tdo_tl070wsh30_of_match[] = {
0155     { .compatible = "tdo,tl070wsh30", },
0156     { /* sentinel */ }
0157 };
0158 MODULE_DEVICE_TABLE(of, tdo_tl070wsh30_of_match);
0159 
0160 static int tdo_tl070wsh30_panel_add(struct tdo_tl070wsh30_panel *tdo_tl070wsh30)
0161 {
0162     struct device *dev = &tdo_tl070wsh30->link->dev;
0163     int err;
0164 
0165     tdo_tl070wsh30->supply = devm_regulator_get(dev, "power");
0166     if (IS_ERR(tdo_tl070wsh30->supply))
0167         return PTR_ERR(tdo_tl070wsh30->supply);
0168 
0169     tdo_tl070wsh30->reset_gpio = devm_gpiod_get(dev, "reset",
0170                             GPIOD_OUT_LOW);
0171     if (IS_ERR(tdo_tl070wsh30->reset_gpio)) {
0172         err = PTR_ERR(tdo_tl070wsh30->reset_gpio);
0173         dev_dbg(dev, "failed to get reset gpio: %d\n", err);
0174         return err;
0175     }
0176 
0177     drm_panel_init(&tdo_tl070wsh30->base, &tdo_tl070wsh30->link->dev,
0178                &tdo_tl070wsh30_panel_funcs, DRM_MODE_CONNECTOR_DSI);
0179 
0180     err = drm_panel_of_backlight(&tdo_tl070wsh30->base);
0181     if (err)
0182         return err;
0183 
0184     drm_panel_add(&tdo_tl070wsh30->base);
0185 
0186     return 0;
0187 }
0188 
0189 static int tdo_tl070wsh30_panel_probe(struct mipi_dsi_device *dsi)
0190 {
0191     struct tdo_tl070wsh30_panel *tdo_tl070wsh30;
0192     int err;
0193 
0194     dsi->lanes = 4;
0195     dsi->format = MIPI_DSI_FMT_RGB888;
0196     dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM;
0197 
0198     tdo_tl070wsh30 = devm_kzalloc(&dsi->dev, sizeof(*tdo_tl070wsh30),
0199                     GFP_KERNEL);
0200     if (!tdo_tl070wsh30)
0201         return -ENOMEM;
0202 
0203     mipi_dsi_set_drvdata(dsi, tdo_tl070wsh30);
0204     tdo_tl070wsh30->link = dsi;
0205 
0206     err = tdo_tl070wsh30_panel_add(tdo_tl070wsh30);
0207     if (err < 0)
0208         return err;
0209 
0210     return mipi_dsi_attach(dsi);
0211 }
0212 
0213 static int tdo_tl070wsh30_panel_remove(struct mipi_dsi_device *dsi)
0214 {
0215     struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi);
0216     int err;
0217 
0218     err = mipi_dsi_detach(dsi);
0219     if (err < 0)
0220         dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
0221 
0222     drm_panel_remove(&tdo_tl070wsh30->base);
0223     drm_panel_disable(&tdo_tl070wsh30->base);
0224     drm_panel_unprepare(&tdo_tl070wsh30->base);
0225 
0226     return 0;
0227 }
0228 
0229 static void tdo_tl070wsh30_panel_shutdown(struct mipi_dsi_device *dsi)
0230 {
0231     struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = mipi_dsi_get_drvdata(dsi);
0232 
0233     drm_panel_disable(&tdo_tl070wsh30->base);
0234     drm_panel_unprepare(&tdo_tl070wsh30->base);
0235 }
0236 
0237 static struct mipi_dsi_driver tdo_tl070wsh30_panel_driver = {
0238     .driver = {
0239         .name = "panel-tdo-tl070wsh30",
0240         .of_match_table = tdo_tl070wsh30_of_match,
0241     },
0242     .probe = tdo_tl070wsh30_panel_probe,
0243     .remove = tdo_tl070wsh30_panel_remove,
0244     .shutdown = tdo_tl070wsh30_panel_shutdown,
0245 };
0246 module_mipi_dsi_driver(tdo_tl070wsh30_panel_driver);
0247 
0248 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
0249 MODULE_DESCRIPTION("TDO TL070WSH30 panel driver");
0250 MODULE_LICENSE("GPL v2");