Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Panel driver for the WideChips WS2401 480x800 DPI RGB panel, used in
0004  * the Samsung Mobile Display (SMD) LMS380KF01.
0005  * Found in the Samsung Galaxy Ace 2 GT-I8160 mobile phone.
0006  * Linus Walleij <linus.walleij@linaro.org>
0007  * Inspired by code and know-how in the vendor driver by Gareth Phillips.
0008  */
0009 #include <drm/drm_mipi_dbi.h>
0010 #include <drm/drm_modes.h>
0011 #include <drm/drm_panel.h>
0012 
0013 #include <linux/backlight.h>
0014 #include <linux/delay.h>
0015 #include <linux/gpio/consumer.h>
0016 #include <linux/init.h>
0017 #include <linux/kernel.h>
0018 #include <linux/media-bus-format.h>
0019 #include <linux/module.h>
0020 #include <linux/regulator/consumer.h>
0021 #include <linux/spi/spi.h>
0022 
0023 #include <video/mipi_display.h>
0024 
0025 #define WS2401_RESCTL           0xb8 /* Resolution select control */
0026 #define WS2401_PSMPS            0xbd /* SMPS positive control */
0027 #define WS2401_NSMPS            0xbe /* SMPS negative control */
0028 #define WS2401_SMPS         0xbf
0029 #define WS2401_BCMODE           0xc1 /* Backlight control mode */
0030 #define WS2401_WRBLCTL          0xc3 /* Backlight control */
0031 #define WS2401_WRDISBV          0xc4 /* Write manual brightness */
0032 #define WS2401_WRCTRLD          0xc6 /* Write BL control */
0033 #define WS2401_WRMIE            0xc7 /* Write MIE mode */
0034 #define WS2401_READ_ID1         0xda /* Read panel ID 1 */
0035 #define WS2401_READ_ID2         0xdb /* Read panel ID 2 */
0036 #define WS2401_READ_ID3         0xdc /* Read panel ID 3 */
0037 #define WS2401_GAMMA_R1         0xe7 /* Gamma red 1 */
0038 #define WS2401_GAMMA_G1         0xe8 /* Gamma green 1 */
0039 #define WS2401_GAMMA_B1         0xe9 /* Gamma blue 1 */
0040 #define WS2401_GAMMA_R2         0xea /* Gamma red 2 */
0041 #define WS2401_GAMMA_G2         0xeb /* Gamma green 2 */
0042 #define WS2401_GAMMA_B2         0xec /* Gamma blue 2 */
0043 #define WS2401_PASSWD1          0xf0 /* Password command for level 2 */
0044 #define WS2401_DISCTL           0xf2 /* Display control */
0045 #define WS2401_PWRCTL           0xf3 /* Power control */
0046 #define WS2401_VCOMCTL          0xf4 /* VCOM control */
0047 #define WS2401_SRCCTL           0xf5 /* Source control */
0048 #define WS2401_PANELCTL         0xf6 /* Panel control */
0049 
0050 static const u8 ws2401_dbi_read_commands[] = {
0051     WS2401_READ_ID1,
0052     WS2401_READ_ID2,
0053     WS2401_READ_ID3,
0054     0, /* sentinel */
0055 };
0056 
0057 /**
0058  * struct ws2401 - state container for a panel controlled by the WS2401
0059  * controller
0060  */
0061 struct ws2401 {
0062     /** @dev: the container device */
0063     struct device *dev;
0064     /** @dbi: the DBI bus abstraction handle */
0065     struct mipi_dbi dbi;
0066     /** @panel: the DRM panel instance for this device */
0067     struct drm_panel panel;
0068     /** @width: the width of this panel in mm */
0069     u32 width;
0070     /** @height: the height of this panel in mm */
0071     u32 height;
0072     /** @reset: reset GPIO line */
0073     struct gpio_desc *reset;
0074     /** @regulators: VCCIO and VIO supply regulators */
0075     struct regulator_bulk_data regulators[2];
0076     /** @internal_bl: If using internal backlight */
0077     bool internal_bl;
0078 };
0079 
0080 static const struct drm_display_mode lms380kf01_480_800_mode = {
0081     /*
0082      * The vendor driver states that the "SMD panel" has a clock
0083      * frequency of 49920000 Hz / 2 = 24960000 Hz.
0084      */
0085     .clock = 24960,
0086     .hdisplay = 480,
0087     .hsync_start = 480 + 8,
0088     .hsync_end = 480 + 8 + 10,
0089     .htotal = 480 + 8 + 10 + 8,
0090     .vdisplay = 800,
0091     .vsync_start = 800 + 8,
0092     .vsync_end = 800 + 8 + 2,
0093     .vtotal = 800 + 8 + 2 + 18,
0094     .width_mm = 50,
0095     .height_mm = 84,
0096     .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
0097 };
0098 
0099 static inline struct ws2401 *to_ws2401(struct drm_panel *panel)
0100 {
0101     return container_of(panel, struct ws2401, panel);
0102 }
0103 
0104 static void ws2401_read_mtp_id(struct ws2401 *ws)
0105 {
0106     struct mipi_dbi *dbi = &ws->dbi;
0107     u8 id1, id2, id3;
0108     int ret;
0109 
0110     ret = mipi_dbi_command_read(dbi, WS2401_READ_ID1, &id1);
0111     if (ret) {
0112         dev_err(ws->dev, "unable to read MTP ID 1\n");
0113         return;
0114     }
0115     ret = mipi_dbi_command_read(dbi, WS2401_READ_ID2, &id2);
0116     if (ret) {
0117         dev_err(ws->dev, "unable to read MTP ID 2\n");
0118         return;
0119     }
0120     ret = mipi_dbi_command_read(dbi, WS2401_READ_ID3, &id3);
0121     if (ret) {
0122         dev_err(ws->dev, "unable to read MTP ID 3\n");
0123         return;
0124     }
0125     dev_info(ws->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3);
0126 }
0127 
0128 static int ws2401_power_on(struct ws2401 *ws)
0129 {
0130     struct mipi_dbi *dbi = &ws->dbi;
0131     int ret;
0132 
0133     /* Power up */
0134     ret = regulator_bulk_enable(ARRAY_SIZE(ws->regulators),
0135                     ws->regulators);
0136     if (ret) {
0137         dev_err(ws->dev, "failed to enable regulators: %d\n", ret);
0138         return ret;
0139     }
0140     msleep(10);
0141 
0142     /* Assert reset >=1 ms */
0143     gpiod_set_value_cansleep(ws->reset, 1);
0144     usleep_range(1000, 5000);
0145     /* De-assert reset */
0146     gpiod_set_value_cansleep(ws->reset, 0);
0147     /* Wait >= 10 ms */
0148     msleep(10);
0149     dev_dbg(ws->dev, "de-asserted RESET\n");
0150 
0151     /*
0152      * Exit sleep mode and initialize display - some hammering is
0153      * necessary.
0154      */
0155     mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
0156     mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
0157     msleep(50);
0158 
0159     /* Magic to unlock level 2 control of the display */
0160     mipi_dbi_command(dbi, WS2401_PASSWD1, 0x5a, 0x5a);
0161     /* Configure resolution to 480RGBx800 */
0162     mipi_dbi_command(dbi, WS2401_RESCTL, 0x12);
0163     /* Set addressing mode Flip V(d0), Flip H(d1) RGB/BGR(d3) */
0164     mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x01);
0165     /* Set pixel format: 24 bpp */
0166     mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 0x70);
0167     mipi_dbi_command(dbi, WS2401_SMPS, 0x00, 0x0f);
0168     mipi_dbi_command(dbi, WS2401_PSMPS, 0x06, 0x03, /* DDVDH: 4.6v */
0169              0x7e, 0x03, 0x12, 0x37);
0170     mipi_dbi_command(dbi, WS2401_NSMPS, 0x06, 0x03, /* DDVDH: -4.6v */
0171              0x7e, 0x02, 0x15, 0x37);
0172     mipi_dbi_command(dbi, WS2401_SMPS, 0x02, 0x0f);
0173     mipi_dbi_command(dbi, WS2401_PWRCTL, 0x10, 0xA9, 0x00, 0x01, 0x44,
0174              0xb4,  /* VGH:16.1v, VGL:-13.8v */
0175              0x50,  /* GREFP:4.2v (default) */
0176              0x50,  /* GREFN:-4.2v (default) */
0177              0x00,
0178              0x44); /* VOUTL:-10v (default) */
0179     mipi_dbi_command(dbi, WS2401_DISCTL, 0x01, 0x00, 0x00, 0x00, 0x14,
0180              0x16);
0181     mipi_dbi_command(dbi, WS2401_VCOMCTL, 0x30, 0x53, 0x53);
0182     mipi_dbi_command(dbi, WS2401_SRCCTL, 0x03, 0x0C, 0x00, 0x00, 0x00,
0183              0x01,  /* 2 dot inversion */
0184              0x01, 0x06, 0x03);
0185     mipi_dbi_command(dbi, WS2401_PANELCTL, 0x14, 0x00, 0x80, 0x00);
0186     mipi_dbi_command(dbi, WS2401_WRMIE, 0x01);
0187 
0188     /* Set up gamma, probably these are P-gamma and N-gamma for each color */
0189     mipi_dbi_command(dbi, WS2401_GAMMA_R1, 0x00,
0190              0x5b, 0x42, 0x41, 0x3f, 0x42, 0x3d, 0x38, 0x2e,
0191              0x2b, 0x2a, 0x27, 0x22, 0x27, 0x0f, 0x00, 0x00);
0192     mipi_dbi_command(dbi, WS2401_GAMMA_R2, 0x00,
0193              0x5b, 0x42, 0x41, 0x3f, 0x42, 0x3d, 0x38, 0x2e,
0194              0x2b, 0x2a, 0x27, 0x22, 0x27, 0x0f, 0x00, 0x00);
0195     mipi_dbi_command(dbi, WS2401_GAMMA_G1, 0x00,
0196              0x59, 0x40, 0x3f, 0x3e, 0x41, 0x3d, 0x39, 0x2f,
0197              0x2c, 0x2b, 0x29, 0x25, 0x29, 0x19, 0x08, 0x00);
0198     mipi_dbi_command(dbi, WS2401_GAMMA_G2, 0x00,
0199              0x59, 0x40, 0x3f, 0x3e, 0x41, 0x3d, 0x39, 0x2f,
0200              0x2c, 0x2b, 0x29, 0x25, 0x29, 0x19, 0x08, 0x00);
0201     mipi_dbi_command(dbi, WS2401_GAMMA_B1, 0x00,
0202              0x57, 0x3b, 0x3a, 0x3b, 0x3f, 0x3b, 0x38, 0x27,
0203              0x38, 0x2a, 0x26, 0x22, 0x34, 0x0c, 0x09, 0x00);
0204     mipi_dbi_command(dbi, WS2401_GAMMA_B2, 0x00,
0205              0x57, 0x3b, 0x3a, 0x3b, 0x3f, 0x3b, 0x38, 0x27,
0206              0x38, 0x2a, 0x26, 0x22, 0x34, 0x0c, 0x09, 0x00);
0207 
0208     if (ws->internal_bl) {
0209         mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x2c);
0210     } else {
0211         mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00);
0212         /*
0213          * When not using internal backlight we do not need any further
0214          * L2 accesses to the panel so we close the door on our way out.
0215          * Otherwise we need to leave the L2 door open.
0216          */
0217         mipi_dbi_command(dbi, WS2401_PASSWD1, 0xa5, 0xa5);
0218     }
0219 
0220     return 0;
0221 }
0222 
0223 static int ws2401_power_off(struct ws2401 *ws)
0224 {
0225     /* Go into RESET and disable regulators */
0226     gpiod_set_value_cansleep(ws->reset, 1);
0227     return regulator_bulk_disable(ARRAY_SIZE(ws->regulators),
0228                       ws->regulators);
0229 }
0230 
0231 static int ws2401_unprepare(struct drm_panel *panel)
0232 {
0233     struct ws2401 *ws = to_ws2401(panel);
0234     struct mipi_dbi *dbi = &ws->dbi;
0235 
0236     /* Make sure we disable backlight, if any */
0237     if (ws->internal_bl)
0238         mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00);
0239     mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE);
0240     msleep(120);
0241     return ws2401_power_off(to_ws2401(panel));
0242 }
0243 
0244 static int ws2401_disable(struct drm_panel *panel)
0245 {
0246     struct ws2401 *ws = to_ws2401(panel);
0247     struct mipi_dbi *dbi = &ws->dbi;
0248 
0249     mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF);
0250     msleep(25);
0251 
0252     return 0;
0253 }
0254 
0255 static int ws2401_prepare(struct drm_panel *panel)
0256 {
0257     return ws2401_power_on(to_ws2401(panel));
0258 }
0259 
0260 static int ws2401_enable(struct drm_panel *panel)
0261 {
0262     struct ws2401 *ws = to_ws2401(panel);
0263     struct mipi_dbi *dbi = &ws->dbi;
0264 
0265     mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
0266 
0267     return 0;
0268 }
0269 
0270 /**
0271  * ws2401_get_modes() - return the mode
0272  * @panel: the panel to get the mode for
0273  * @connector: reference to the central DRM connector control structure
0274  */
0275 static int ws2401_get_modes(struct drm_panel *panel,
0276                 struct drm_connector *connector)
0277 {
0278     struct ws2401 *ws = to_ws2401(panel);
0279     struct drm_display_mode *mode;
0280     static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
0281 
0282     /*
0283      * We just support the LMS380KF01 so far, if we implement more panels
0284      * this mode, the following connector display_info settings and
0285      * probably the custom DCS sequences needs to selected based on what
0286      * the target panel needs.
0287      */
0288     mode = drm_mode_duplicate(connector->dev, &lms380kf01_480_800_mode);
0289     if (!mode) {
0290         dev_err(ws->dev, "failed to add mode\n");
0291         return -ENOMEM;
0292     }
0293 
0294     connector->display_info.bpc = 8;
0295     connector->display_info.width_mm = mode->width_mm;
0296     connector->display_info.height_mm = mode->height_mm;
0297     connector->display_info.bus_flags =
0298         DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
0299     drm_display_info_set_bus_formats(&connector->display_info,
0300                      &bus_format, 1);
0301 
0302     drm_mode_set_name(mode);
0303     mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
0304 
0305     drm_mode_probed_add(connector, mode);
0306 
0307     return 1;
0308 }
0309 
0310 static const struct drm_panel_funcs ws2401_drm_funcs = {
0311     .disable = ws2401_disable,
0312     .unprepare = ws2401_unprepare,
0313     .prepare = ws2401_prepare,
0314     .enable = ws2401_enable,
0315     .get_modes = ws2401_get_modes,
0316 };
0317 
0318 static int ws2401_set_brightness(struct backlight_device *bl)
0319 {
0320     struct ws2401 *ws = bl_get_data(bl);
0321     struct mipi_dbi *dbi = &ws->dbi;
0322     u8 brightness = backlight_get_brightness(bl);
0323 
0324     if (backlight_is_blank(bl)) {
0325         mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x00);
0326     } else {
0327         mipi_dbi_command(dbi, WS2401_WRCTRLD, 0x2c);
0328         mipi_dbi_command(dbi, WS2401_WRDISBV, brightness);
0329     }
0330 
0331     return 0;
0332 }
0333 
0334 static const struct backlight_ops ws2401_bl_ops = {
0335     .update_status = ws2401_set_brightness,
0336 };
0337 
0338 static const struct backlight_properties ws2401_bl_props = {
0339     .type = BACKLIGHT_PLATFORM,
0340     .brightness = 120,
0341     .max_brightness = U8_MAX,
0342 };
0343 
0344 static int ws2401_probe(struct spi_device *spi)
0345 {
0346     struct device *dev = &spi->dev;
0347     struct ws2401 *ws;
0348     int ret;
0349 
0350     ws = devm_kzalloc(dev, sizeof(*ws), GFP_KERNEL);
0351     if (!ws)
0352         return -ENOMEM;
0353     ws->dev = dev;
0354 
0355     /*
0356      * VCI   is the analog voltage supply
0357      * VCCIO is the digital I/O voltage supply
0358      */
0359     ws->regulators[0].supply = "vci";
0360     ws->regulators[1].supply = "vccio";
0361     ret = devm_regulator_bulk_get(dev,
0362                       ARRAY_SIZE(ws->regulators),
0363                       ws->regulators);
0364     if (ret)
0365         return dev_err_probe(dev, ret, "failed to get regulators\n");
0366 
0367     ws->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
0368     if (IS_ERR(ws->reset)) {
0369         ret = PTR_ERR(ws->reset);
0370         return dev_err_probe(dev, ret, "no RESET GPIO\n");
0371     }
0372 
0373     ret = mipi_dbi_spi_init(spi, &ws->dbi, NULL);
0374     if (ret)
0375         return dev_err_probe(dev, ret, "MIPI DBI init failed\n");
0376     ws->dbi.read_commands = ws2401_dbi_read_commands;
0377 
0378     ws2401_power_on(ws);
0379     ws2401_read_mtp_id(ws);
0380     ws2401_power_off(ws);
0381 
0382     drm_panel_init(&ws->panel, dev, &ws2401_drm_funcs,
0383                DRM_MODE_CONNECTOR_DPI);
0384 
0385     ret = drm_panel_of_backlight(&ws->panel);
0386     if (ret)
0387         return dev_err_probe(dev, ret,
0388                 "failed to get external backlight device\n");
0389 
0390     if (!ws->panel.backlight) {
0391         dev_dbg(dev, "no external backlight, using internal backlight\n");
0392         ws->panel.backlight =
0393             devm_backlight_device_register(dev, "ws2401", dev, ws,
0394                 &ws2401_bl_ops, &ws2401_bl_props);
0395         if (IS_ERR(ws->panel.backlight))
0396             return dev_err_probe(dev, PTR_ERR(ws->panel.backlight),
0397                 "failed to register backlight device\n");
0398     } else {
0399         dev_dbg(dev, "using external backlight\n");
0400     }
0401 
0402     spi_set_drvdata(spi, ws);
0403 
0404     drm_panel_add(&ws->panel);
0405     dev_dbg(dev, "added panel\n");
0406 
0407     return 0;
0408 }
0409 
0410 static void ws2401_remove(struct spi_device *spi)
0411 {
0412     struct ws2401 *ws = spi_get_drvdata(spi);
0413 
0414     drm_panel_remove(&ws->panel);
0415 }
0416 
0417 /*
0418  * Samsung LMS380KF01 is the one instance of this display controller that we
0419  * know about, but if more are found, the controller can be parameterized
0420  * here and used for other configurations.
0421  */
0422 static const struct of_device_id ws2401_match[] = {
0423     { .compatible = "samsung,lms380kf01", },
0424     {},
0425 };
0426 MODULE_DEVICE_TABLE(of, ws2401_match);
0427 
0428 static struct spi_driver ws2401_driver = {
0429     .probe      = ws2401_probe,
0430     .remove     = ws2401_remove,
0431     .driver     = {
0432         .name   = "ws2401-panel",
0433         .of_match_table = ws2401_match,
0434     },
0435 };
0436 module_spi_driver(ws2401_driver);
0437 
0438 MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
0439 MODULE_DESCRIPTION("Samsung WS2401 panel driver");
0440 MODULE_LICENSE("GPL v2");