diff options
Diffstat (limited to 'drivers/gpu/drm/panel/panel-innolux-ej030na.c')
-rw-r--r-- | drivers/gpu/drm/panel/panel-innolux-ej030na.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/drivers/gpu/drm/panel/panel-innolux-ej030na.c b/drivers/gpu/drm/panel/panel-innolux-ej030na.c new file mode 100644 index 000000000..b2b0ebc9e --- /dev/null +++ b/drivers/gpu/drm/panel/panel-innolux-ej030na.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Innolux/Chimei EJ030NA TFT LCD panel driver + * + * Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net> + * Copyright (C) 2020, Christophe Branchereau <cbranchereau@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +struct ej030na_info { + const struct drm_display_mode *display_modes; + unsigned int num_modes; + u16 width_mm, height_mm; + u32 bus_format, bus_flags; +}; + +struct ej030na { + struct drm_panel panel; + struct spi_device *spi; + struct regmap *map; + + const struct ej030na_info *panel_info; + + struct regulator *supply; + struct gpio_desc *reset_gpio; +}; + +static inline struct ej030na *to_ej030na(struct drm_panel *panel) +{ + return container_of(panel, struct ej030na, panel); +} + +static const struct reg_sequence ej030na_init_sequence[] = { + { 0x05, 0x1e }, + { 0x05, 0x5c }, + { 0x02, 0x14 }, + { 0x03, 0x40 }, + { 0x04, 0x07 }, + { 0x06, 0x12 }, + { 0x07, 0xd2 }, + { 0x0c, 0x06 }, + { 0x0d, 0x40 }, + { 0x0e, 0x40 }, + { 0x0f, 0x40 }, + { 0x10, 0x40 }, + { 0x11, 0x40 }, + { 0x2f, 0x40 }, + { 0x5a, 0x02 }, + + { 0x30, 0x07 }, + { 0x31, 0x57 }, + { 0x32, 0x53 }, + { 0x33, 0x77 }, + { 0x34, 0xb8 }, + { 0x35, 0xbd }, + { 0x36, 0xb8 }, + { 0x37, 0xe7 }, + { 0x38, 0x04 }, + { 0x39, 0xff }, + + { 0x40, 0x0b }, + { 0x41, 0xb8 }, + { 0x42, 0xab }, + { 0x43, 0xb9 }, + { 0x44, 0x6a }, + { 0x45, 0x56 }, + { 0x46, 0x61 }, + { 0x47, 0x08 }, + { 0x48, 0x0f }, + { 0x49, 0x0f }, +}; + +static int ej030na_prepare(struct drm_panel *panel) +{ + struct ej030na *priv = to_ej030na(panel); + struct device *dev = &priv->spi->dev; + int err; + + err = regulator_enable(priv->supply); + if (err) { + dev_err(dev, "Failed to enable power supply: %d\n", err); + return err; + } + + /* Reset the chip */ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(50, 150); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(50, 150); + + err = regmap_multi_reg_write(priv->map, ej030na_init_sequence, + ARRAY_SIZE(ej030na_init_sequence)); + if (err) { + dev_err(dev, "Failed to init registers: %d\n", err); + goto err_disable_regulator; + } + + return 0; + +err_disable_regulator: + regulator_disable(priv->supply); + return err; +} + +static int ej030na_unprepare(struct drm_panel *panel) +{ + struct ej030na *priv = to_ej030na(panel); + + gpiod_set_value_cansleep(priv->reset_gpio, 1); + regulator_disable(priv->supply); + + return 0; +} + +static int ej030na_enable(struct drm_panel *panel) +{ + struct ej030na *priv = to_ej030na(panel); + + /* standby off */ + regmap_write(priv->map, 0x2b, 0x01); + + if (panel->backlight) { + /* Wait for the picture to be ready before enabling backlight */ + msleep(120); + } + + return 0; +} + +static int ej030na_disable(struct drm_panel *panel) +{ + struct ej030na *priv = to_ej030na(panel); + + /* standby on */ + regmap_write(priv->map, 0x2b, 0x00); + + return 0; +} + +static int ej030na_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ej030na *priv = to_ej030na(panel); + const struct ej030na_info *panel_info = priv->panel_info; + struct drm_display_mode *mode; + unsigned int i; + + for (i = 0; i < panel_info->num_modes; i++) { + mode = drm_mode_duplicate(connector->dev, + &panel_info->display_modes[i]); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + if (panel_info->num_modes == 1) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + } + + connector->display_info.bpc = 8; + connector->display_info.width_mm = panel_info->width_mm; + connector->display_info.height_mm = panel_info->height_mm; + + drm_display_info_set_bus_formats(&connector->display_info, + &panel_info->bus_format, 1); + connector->display_info.bus_flags = panel_info->bus_flags; + + return panel_info->num_modes; +} + +static const struct drm_panel_funcs ej030na_funcs = { + .prepare = ej030na_prepare, + .unprepare = ej030na_unprepare, + .enable = ej030na_enable, + .disable = ej030na_disable, + .get_modes = ej030na_get_modes, +}; + +static const struct regmap_config ej030na_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x5a, +}; + +static int ej030na_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ej030na *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->spi = spi; + spi_set_drvdata(spi, priv); + + priv->map = devm_regmap_init_spi(spi, &ej030na_regmap_config); + if (IS_ERR(priv->map)) { + dev_err(dev, "Unable to init regmap\n"); + return PTR_ERR(priv->map); + } + + priv->panel_info = of_device_get_match_data(dev); + if (!priv->panel_info) + return -EINVAL; + + priv->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(priv->supply)) + return dev_err_probe(dev, PTR_ERR(priv->supply), + "Failed to get power supply\n"); + + priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), + "Failed to get reset GPIO\n"); + + drm_panel_init(&priv->panel, dev, &ej030na_funcs, + DRM_MODE_CONNECTOR_DPI); + + err = drm_panel_of_backlight(&priv->panel); + if (err) + return err; + + drm_panel_add(&priv->panel); + + return 0; +} + +static void ej030na_remove(struct spi_device *spi) +{ + struct ej030na *priv = spi_get_drvdata(spi); + + drm_panel_remove(&priv->panel); + drm_panel_disable(&priv->panel); + drm_panel_unprepare(&priv->panel); +} + +static const struct drm_display_mode ej030na_modes[] = { + { /* 60 Hz */ + .clock = 14400, + .hdisplay = 320, + .hsync_start = 320 + 10, + .hsync_end = 320 + 10 + 37, + .htotal = 320 + 10 + 37 + 33, + .vdisplay = 480, + .vsync_start = 480 + 102, + .vsync_end = 480 + 102 + 9 + 9, + .vtotal = 480 + 102 + 9 + 9, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, + { /* 50 Hz */ + .clock = 12000, + .hdisplay = 320, + .hsync_start = 320 + 10, + .hsync_end = 320 + 10 + 37, + .htotal = 320 + 10 + 37 + 33, + .vdisplay = 480, + .vsync_start = 480 + 102, + .vsync_end = 480 + 102 + 9, + .vtotal = 480 + 102 + 9 + 9, + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, + }, +}; + +static const struct ej030na_info ej030na_info = { + .display_modes = ej030na_modes, + .num_modes = ARRAY_SIZE(ej030na_modes), + .width_mm = 70, + .height_mm = 51, + .bus_format = MEDIA_BUS_FMT_RGB888_3X8_DELTA, + .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE | DRM_BUS_FLAG_DE_LOW, +}; + +static const struct of_device_id ej030na_of_match[] = { + { .compatible = "innolux,ej030na", .data = &ej030na_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ej030na_of_match); + +static struct spi_driver ej030na_driver = { + .driver = { + .name = "panel-innolux-ej030na", + .of_match_table = ej030na_of_match, + }, + .probe = ej030na_probe, + .remove = ej030na_remove, +}; +module_spi_driver(ej030na_driver); + +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_AUTHOR("Christophe Branchereau <cbranchereau@gmail.com>"); +MODULE_LICENSE("GPL v2"); |