This adds a new driver for the Samsung LMS397KF04 DPI display controlled over SPI. Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxx> --- MAINTAINERS | 7 + drivers/gpu/drm/panel/Kconfig | 8 + drivers/gpu/drm/panel/Makefile | 1 + .../gpu/drm/panel/panel-samsung-lms397kf04.c | 421 ++++++++++++++++++ 4 files changed, 437 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-samsung-lms397kf04.c diff --git a/MAINTAINERS b/MAINTAINERS index d92f85ca831d..6d32432adebe 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5731,6 +5731,13 @@ S: Maintained F: Documentation/devicetree/bindings/display/panel/raydium,rm67191.yaml F: drivers/gpu/drm/panel/panel-raydium-rm67191.c +DRM DRIVER FOR SAMSUNG LMS397KF04 PANELS +M: Linus Walleij <linus.walleij@xxxxxxxxxx> +S: Maintained +T: git git://anongit.freedesktop.org/drm/drm-misc +F: Documentation/devicetree/bindings/display/panel/samsung,lms397kf04.yaml +F: drivers/gpu/drm/panel/panel-samsung-lms397kf04.c + DRM DRIVER FOR SITRONIX ST7703 PANELS M: Guido Günther <agx@xxxxxxxxxxx> R: Purism Kernel Team <kernel@xxxxxxx> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 4894913936e9..8e24a92ad9ce 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -348,6 +348,14 @@ config DRM_PANEL_SAMSUNG_S6D16D0 depends on DRM_MIPI_DSI select VIDEOMODE_HELPERS +config DRM_PANEL_SAMSUNG_LMS397KF04 + tristate "Samsung LMS397KF04 DPI panel" + depends on OF && SPI && GPIOLIB + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the Samsung + LMS397KF04 480x800 DPI panel. + config DRM_PANEL_SAMSUNG_S6E3HA2 tristate "Samsung S6E3HA2 DSI video mode panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index cae4d976c069..de23d00937db 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += panel-raspberrypi-touchscreen obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM67191) += panel-raydium-rm67191.o obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM68200) += panel-raydium-rm68200.o obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.o +obj-$(CONFIG_DRM_PANEL_SAMSUNG_LMS397KF04) += panel-samsung-lms397kf04.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6D16D0) += panel-samsung-s6d16d0.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2) += panel-samsung-s6e3ha2.o diff --git a/drivers/gpu/drm/panel/panel-samsung-lms397kf04.c b/drivers/gpu/drm/panel/panel-samsung-lms397kf04.c new file mode 100644 index 000000000000..41290cadc351 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-lms397kf04.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Panel driver for the Samsung LMS397KF04 480x800 DPI RGB panel. + * According to the data sheet the display controller is called DB7430 + * Linus Walleij <linus.walleij@xxxxxxxxxx> + */ +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <video/mipi_display.h> + +#define LMS397_MANUFACTURER_CMD 0xb0 +#define LMS397_UNKNOWN_B4 0xb4 +#define LMS397_USER_SELECT 0xb5 +#define LMS397_UNKNOWN_B7 0xb7 +#define LMS397_UNKNOWN_B8 0xb8 +#define LMS397_PANEL_DRIVING 0xc0 +#define LMS397_SOURCE_CONTROL 0xc1 +#define LMS397_GATE_INTERFACE 0xc4 +#define LMS397_DISPLAY_H_TIMING 0xc5 +#define LMS397_RGB_SYNC_OPTION 0xc6 +#define LMS397_GAMMA_SET_RED 0xc8 +#define LMS397_GAMMA_SET_GREEN 0xc9 +#define LMS397_GAMMA_SET_BLUE 0xca +#define LMS397_BIAS_CURRENT_CTRL 0xd1 +#define LMS397_DDV_CTRL 0xd2 +#define LMS397_GAMMA_CTRL_REF 0xd3 +#define LMS397_UNKNOWN_D4 0xd4 +#define LMS397_DCDC_CTRL 0xd5 +#define LMS397_VCL_CTRL 0xd6 +#define LMS397_UNKNOWN_F8 0xf8 +#define LMS397_UNKNOWN_FC 0xfc + +#define DATA_MASK 0x100 + +/** + * struct lms397kf04 - state container for the LMS397kf04 panel + */ +struct lms397kf04 { + /** + * @dev: the container device + */ + struct device *dev; + /** + * @spi: the corresponding SPI device + */ + struct spi_device *spi; + /** + * @panel: the DRM panel instance for this device + */ + struct drm_panel panel; + /** + * @width: the width of this panel in mm + */ + u32 width; + /** + * @height: the height of this panel in mm + */ + u32 height; + /** + * @reset: reset GPIO line + */ + struct gpio_desc *reset; + /** + * @regulators: VCCIO and VIO supply regulators + */ + struct regulator_bulk_data regulators[2]; +}; + +static const struct drm_display_mode lms397kf04_mode = { + /* + * 31 ns period min (htotal*vtotal*vrefresh)/1000 + * gives a Vrefresh of ~71 Hz. + */ + .clock = 32258, + .hdisplay = 480, + .hsync_start = 480 + 10, + .hsync_end = 480 + 10 + 4, + .htotal = 480 + 10 + 4 + 40, + .vdisplay = 800, + .vsync_start = 800 + 6, + .vsync_end = 800 + 6 + 1, + .vtotal = 800 + 6 + 1 + 7, + .width_mm = 53, + .height_mm = 87, + .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, +}; + +static inline struct lms397kf04 *to_lms397kf04(struct drm_panel *panel) +{ + return container_of(panel, struct lms397kf04, panel); +} + +static int lms397kf04_write_word(struct lms397kf04 *lms, u16 data) +{ + /* SPI buffers are always in CPU order */ + return spi_write(lms->spi, &data, 2); +} + +static int lms397kf04_dcs_write(struct lms397kf04 *lms, const u8 *data, size_t len) +{ + int ret; + + dev_dbg(lms->dev, "SPI writing dcs seq: %*ph\n", (int)len, data); + + /* + * This sends 9 bits with the first bit (bit 8) set to 0 + * This indicates that this is a command. Anything after the + * command is data. + */ + ret = lms397kf04_write_word(lms, *data); + + while (!ret && --len) { + ++data; + /* This sends 9 bits with the first bit (bit 8) set to 1 */ + ret = lms397kf04_write_word(lms, *data | DATA_MASK); + } + + if (ret) { + dev_err(lms->dev, "SPI error %d writing dcs seq: %*ph\n", ret, + (int)len, data); + } + + return ret; +} + +#define lms397kf04_dcs_write_seq_static(ctx, seq ...) \ + ({ \ + static const u8 d[] = { seq }; \ + lms397kf04_dcs_write(ctx, d, ARRAY_SIZE(d)); \ + }) + +static int lms397kf04_power_on(struct lms397kf04 *lms) +{ + int ret; + + /* Power up */ + ret = regulator_bulk_enable(ARRAY_SIZE(lms->regulators), + lms->regulators); + if (ret) { + dev_err(lms->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + msleep(50); + + /* Assert reset >=1 ms */ + gpiod_set_value_cansleep(lms->reset, 1); + msleep(1); + /* De-assert reset */ + gpiod_set_value_cansleep(lms->reset, 0); + /* Wait >= 10 ms */ + msleep(10); + dev_info(lms->dev, "de-asserted RESET\n"); + + /* + * This is set to 0x0a (RGB/BGR order + horizontal flip) in order + * to make the display behave normally. If this is not set the displays + * normal output behaviour is horizontally flipped and BGR ordered. Do + * it twice because the first message doesn't always "take". + */ + lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_SET_ADDRESS_MODE, 0x0a); + lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_SET_ADDRESS_MODE, 0x0a); + /* Called "Access protection off" in vendor code */ + lms397kf04_dcs_write_seq_static(lms, LMS397_MANUFACTURER_CMD, 0x00); + lms397kf04_dcs_write_seq_static(lms, LMS397_PANEL_DRIVING, 0x28, 0x08); + lms397kf04_dcs_write_seq_static(lms, LMS397_SOURCE_CONTROL, + 0x01, 0x30, 0x15, 0x05, 0x22); + lms397kf04_dcs_write_seq_static(lms, LMS397_GATE_INTERFACE, + 0x10, 0x01, 0x00); + lms397kf04_dcs_write_seq_static(lms, LMS397_DISPLAY_H_TIMING, + 0x06, 0x55, 0x03, 0x07, 0x0b, + 0x33, 0x00, 0x01, 0x03); + /* + * 0x00 in datasheet 0x01 in vendor code 0x00, it seems 0x01 means + * DE active high and 0x00 means DE active low. + */ + lms397kf04_dcs_write_seq_static(lms, LMS397_RGB_SYNC_OPTION, 0x01); + lms397kf04_dcs_write_seq_static(lms, LMS397_GAMMA_SET_RED, + /* R positive gamma */ 0x00, + 0x0A, 0x31, 0x3B, 0x4E, 0x58, 0x59, 0x5B, 0x58, 0x5E, 0x62, + 0x60, 0x61, 0x5E, 0x62, 0x55, 0x55, 0x7F, 0x08, + /* R negative gamma */ 0x00, + 0x0A, 0x31, 0x3B, 0x4E, 0x58, 0x59, 0x5B, 0x58, 0x5E, 0x62, + 0x60, 0x61, 0x5E, 0x62, 0x55, 0x55, 0x7F, 0x08); + lms397kf04_dcs_write_seq_static(lms, LMS397_GAMMA_SET_GREEN, + /* G positive gamma */ 0x00, + 0x25, 0x15, 0x28, 0x3D, 0x4A, 0x48, 0x4C, 0x4A, 0x52, 0x59, + 0x59, 0x5B, 0x56, 0x60, 0x5D, 0x55, 0x7F, 0x0A, + /* G negative gamma */ 0x00, + 0x25, 0x15, 0x28, 0x3D, 0x4A, 0x48, 0x4C, 0x4A, 0x52, 0x59, + 0x59, 0x5B, 0x56, 0x60, 0x5D, 0x55, 0x7F, 0x0A); + lms397kf04_dcs_write_seq_static(lms, LMS397_GAMMA_SET_BLUE, + /* B positive gamma */ 0x00, + 0x48, 0x10, 0x1F, 0x2F, 0x35, 0x38, 0x3D, 0x3C, 0x45, 0x4D, + 0x4E, 0x52, 0x51, 0x60, 0x7F, 0x7E, 0x7F, 0x0C, + /* B negative gamma */ 0x00, + 0x48, 0x10, 0x1F, 0x2F, 0x35, 0x38, 0x3D, 0x3C, 0x45, 0x4D, + 0x4E, 0x52, 0x51, 0x60, 0x7F, 0x7E, 0x7F, 0x0C); + lms397kf04_dcs_write_seq_static(lms, LMS397_BIAS_CURRENT_CTRL, + 0x33, 0x13); + lms397kf04_dcs_write_seq_static(lms, LMS397_DDV_CTRL, + 0x11, 0x00, 0x00); + lms397kf04_dcs_write_seq_static(lms, LMS397_GAMMA_CTRL_REF, + 0x50, 0x50); + lms397kf04_dcs_write_seq_static(lms, LMS397_DCDC_CTRL, + 0x2f, 0x11, 0x1e, 0x46); + lms397kf04_dcs_write_seq_static(lms, LMS397_VCL_CTRL, + 0x11, 0x0a); + + return 0; +} + +static void lms397kf04_power_off(struct lms397kf04 *lms) +{ + /* Go into RESET and disable regulators */ + gpiod_set_value_cansleep(lms->reset, 1); + regulator_bulk_disable(ARRAY_SIZE(lms->regulators), + lms->regulators); +} + +static int lms397kf04_unprepare(struct drm_panel *panel) +{ + struct lms397kf04 *lms = to_lms397kf04(panel); + + lms397kf04_power_off(lms); + + return 0; +} + +static int lms397kf04_disable(struct drm_panel *panel) +{ + struct lms397kf04 *lms = to_lms397kf04(panel); + + lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_SET_DISPLAY_OFF); + msleep(25); + lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_ENTER_SLEEP_MODE); + msleep(120); + + return 0; +} + +static int lms397kf04_prepare(struct drm_panel *panel) +{ + struct lms397kf04 *lms = to_lms397kf04(panel); + int ret; + + ret = lms397kf04_power_on(lms); + if (ret) + return ret; + + return 0; +} + +static int lms397kf04_enable(struct drm_panel *panel) +{ + struct lms397kf04 *lms = to_lms397kf04(panel); + + /* Exit sleep mode */ + lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(20); + + /* NVM (non-volatile memory) load sequence */ + lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_D4, + 0x52, 0x5e); + lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_F8, + 0x01, 0xf5, 0xf2, 0x71, 0x44); + lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_FC, + 0x00, 0x08); + msleep(150); + + /* CABC turn on sequence (BC = backlight control) */ + lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_B4, + 0x0f, 0x00, 0x50); + lms397kf04_dcs_write_seq_static(lms, LMS397_USER_SELECT, 0x80); + lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_B7, 0x24); + lms397kf04_dcs_write_seq_static(lms, LMS397_UNKNOWN_B8, 0x01); + + /* Turn on display */ + lms397kf04_dcs_write_seq_static(lms, MIPI_DCS_SET_DISPLAY_ON); + + /* Update brightness */ + + return 0; +} + +/** + * lms397kf04_get_modes() - return the mode + * @panel: the panel to get the mode for + * @connector: reference to the central DRM connector control structure + */ +static int lms397kf04_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct lms397kf04 *lms = to_lms397kf04(panel); + struct drm_display_mode *mode; + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + mode = drm_mode_duplicate(connector->dev, &lms397kf04_mode); + if (!mode) { + dev_err(lms->dev, "failed to add mode\n"); + return -ENOMEM; + } + + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + connector->display_info.bus_flags = + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs lms397kf04_drm_funcs = { + .disable = lms397kf04_disable, + .unprepare = lms397kf04_unprepare, + .prepare = lms397kf04_prepare, + .enable = lms397kf04_enable, + .get_modes = lms397kf04_get_modes, +}; + +static int lms397kf04_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct lms397kf04 *lms; + int ret; + + lms = devm_kzalloc(dev, sizeof(*lms), GFP_KERNEL); + if (!lms) + return -ENOMEM; + lms->dev = dev; + + /* + * VCI is the analog voltage supply + * VCCIO is the digital I/O voltage supply + */ + lms->regulators[0].supply = "vci"; + lms->regulators[1].supply = "vccio"; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(lms->regulators), + lms->regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + /* This asserts the RESET signal, putting the display into reset */ + lms->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lms->reset)) { + dev_err(dev, "no RESET GPIO\n"); + return -ENODEV; + } + + spi->bits_per_word = 9; + /* Preserve e.g. SPI_3WIRE setting */ + spi->mode |= SPI_MODE_3; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(dev, "spi setup failed.\n"); + return ret; + } + lms->spi = spi; + + drm_panel_init(&lms->panel, dev, &lms397kf04_drm_funcs, + DRM_MODE_CONNECTOR_DPI); + + /* FIXME: if no external backlight, use internal backlight */ + ret = drm_panel_of_backlight(&lms->panel); + if (ret) { + dev_info(dev, "failed to add backlight\n"); + return ret; + } + + spi_set_drvdata(spi, lms); + + drm_panel_add(&lms->panel); + dev_info(dev, "added panel\n"); + + return 0; +} + +static int lms397kf04_remove(struct spi_device *spi) +{ + struct lms397kf04 *lms = spi_get_drvdata(spi); + + drm_panel_remove(&lms->panel); + return 0; +} + +static const struct of_device_id lms397kf04_match[] = { + { .compatible = "samsung,lms397kf04", }, + {}, +}; +MODULE_DEVICE_TABLE(of, lms397kf04_match); + +static struct spi_driver lms397kf04_driver = { + .probe = lms397kf04_probe, + .remove = lms397kf04_remove, + .driver = { + .name = "lms397kf04-panel", + .of_match_table = lms397kf04_match, + }, +}; +module_spi_driver(lms397kf04_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Samsung LMS397KF04 panel driver"); +MODULE_LICENSE("GPL v2"); -- 2.29.2 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel