315 lines
8.5 KiB
C
315 lines
8.5 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* DRM driver for Sitronix ST7735S display panels
|
|
*
|
|
* Based on st7735r driver by David Lechner <david@lechnology.com>
|
|
* Android DTS sequence implementation
|
|
*/
|
|
|
|
#include <linux/backlight.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-buf.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/module.h>
|
|
#include <linux/property.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <video/mipi_display.h>
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_fbdev_dma.h>
|
|
#include <drm/drm_gem_atomic_helper.h>
|
|
#include <drm/drm_gem_dma_helper.h>
|
|
#include <drm/drm_managed.h>
|
|
#include <drm/drm_mipi_dbi.h>
|
|
|
|
#define ST7735S_FRMCTR1 0xb1
|
|
#define ST7735S_FRMCTR2 0xb2
|
|
#define ST7735S_FRMCTR3 0xb3
|
|
#define ST7735S_INVCTR 0xb4
|
|
#define ST7735S_PWCTR1 0xc0
|
|
#define ST7735S_PWCTR2 0xc1
|
|
#define ST7735S_PWCTR3 0xc2
|
|
#define ST7735S_PWCTR4 0xc3
|
|
#define ST7735S_PWCTR5 0xc4
|
|
#define ST7735S_VMCTR1 0xc5
|
|
#define ST7735S_GAMCTRP1 0xe0
|
|
#define ST7735S_GAMCTRN1 0xe1
|
|
|
|
#define ST7735S_MY BIT(7)
|
|
#define ST7735S_MX BIT(6)
|
|
#define ST7735S_MV BIT(5)
|
|
#define ST7735S_RGB BIT(3)
|
|
|
|
struct st7735s_cfg {
|
|
const struct drm_display_mode mode;
|
|
unsigned int left_offset;
|
|
unsigned int top_offset;
|
|
unsigned int write_only:1;
|
|
unsigned int rgb:1;
|
|
};
|
|
|
|
struct st7735s_priv {
|
|
struct mipi_dbi_dev dbidev;
|
|
const struct st7735s_cfg *cfg;
|
|
};
|
|
|
|
/*
|
|
* Android DTS initialization sequence from MSM8916
|
|
* DO NOT modify order or add extra commands
|
|
*/
|
|
static void st7735s_pipe_enable(struct drm_simple_display_pipe *pipe,
|
|
struct drm_crtc_state *crtc_state,
|
|
struct drm_plane_state *plane_state)
|
|
{
|
|
struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
|
|
struct st7735s_priv *priv = container_of(dbidev, struct st7735s_priv,
|
|
dbidev);
|
|
struct mipi_dbi *dbi = &dbidev->dbi;
|
|
int ret, idx;
|
|
|
|
if (!drm_dev_enter(pipe->crtc.dev, &idx))
|
|
return;
|
|
|
|
pr_info("st7735s: === BEGIN ANDROID SEQUENCE ===\n");
|
|
|
|
ret = mipi_dbi_poweron_reset(dbidev);
|
|
if (ret) {
|
|
pr_err("st7735s: poweron_reset failed: %d\n", ret);
|
|
goto out_exit;
|
|
}
|
|
|
|
/* 1. EXIT_SLEEP_MODE (0x11) - delay 150ms (0x96) */
|
|
pr_info("st7735s: [1/19] EXIT_SLEEP_MODE (0x11)\n");
|
|
mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
|
|
msleep(150);
|
|
|
|
/* 2. SET_ADDRESS_MODE (0x36) - 0x00 */
|
|
pr_info("st7735s: [2/19] SET_ADDRESS_MODE (0x36) = 0x00\n");
|
|
mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
|
|
|
|
/* 3. SET_COLUMN_ADDRESS (0x2a) - 0,0,0,131 */
|
|
pr_info("st7735s: [3/19] SET_COLUMN_ADDRESS (0x2a) = 0,0,0,131\n");
|
|
mipi_dbi_command(dbi, MIPI_DCS_SET_COLUMN_ADDRESS,
|
|
0x00, 0x00, 0x00, 0x83);
|
|
|
|
/* 4. SET_PAGE_ADDRESS (0x2b) - 0,0,0,131 */
|
|
pr_info("st7735s: [4/19] SET_PAGE_ADDRESS (0x2b) = 0,0,0,131\n");
|
|
mipi_dbi_command(dbi, MIPI_DCS_SET_PAGE_ADDRESS,
|
|
0x00, 0x00, 0x00, 0x83);
|
|
|
|
/* 5. SET_PIXEL_FORMAT (0x3a) - 0x05 (16-bit RGB565) */
|
|
pr_info("st7735s: [5/19] SET_PIXEL_FORMAT (0x3a) = 0x05\n");
|
|
mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 0x05);
|
|
|
|
/* 6. FRMCTR1 (0xb1) - 0x05,0x3c,0x3c */
|
|
pr_info("st7735s: [6/19] FRMCTR1 (0xb1)\n");
|
|
mipi_dbi_command(dbi, ST7735S_FRMCTR1, 0x05, 0x3c, 0x3c);
|
|
|
|
/* 7. FRMCTR2 (0xb2) - 0x05,0x3c,0x3c */
|
|
pr_info("st7735s: [7/19] FRMCTR2 (0xb2)\n");
|
|
mipi_dbi_command(dbi, ST7735S_FRMCTR2, 0x05, 0x3c, 0x3c);
|
|
|
|
/* 8. FRMCTR3 (0xb3) - 0x05,0x3c,0x3c,0x05,0x3c,0x3c */
|
|
pr_info("st7735s: [8/19] FRMCTR3 (0xb3)\n");
|
|
mipi_dbi_command(dbi, ST7735S_FRMCTR3, 0x05, 0x3c, 0x3c,
|
|
0x05, 0x3c, 0x3c);
|
|
|
|
/* 9. INVCTR (0xb4) - 0x03 */
|
|
pr_info("st7735s: [9/19] INVCTR (0xb4) = 0x03\n");
|
|
mipi_dbi_command(dbi, ST7735S_INVCTR, 0x03);
|
|
|
|
/* 10. PWCTR1 (0xc0) - 0x0e,0x0e,0x04 */
|
|
pr_info("st7735s: [10/19] PWCTR1 (0xc0)\n");
|
|
mipi_dbi_command(dbi, ST7735S_PWCTR1, 0x0e, 0x0e, 0x04);
|
|
|
|
/* 11. PWCTR2 (0xc1) - 0xc0 */
|
|
pr_info("st7735s: [11/19] PWCTR2 (0xc1) = 0xc0\n");
|
|
mipi_dbi_command(dbi, ST7735S_PWCTR2, 0xc0);
|
|
|
|
/* 12. PWCTR3 (0xc2) - 0x0d,0x00 */
|
|
pr_info("st7735s: [12/19] PWCTR3 (0xc2)\n");
|
|
mipi_dbi_command(dbi, ST7735S_PWCTR3, 0x0d, 0x00);
|
|
|
|
/* 13. PWCTR4 (0xc3) - 0x8d,0x2a */
|
|
pr_info("st7735s: [13/19] PWCTR4 (0xc3)\n");
|
|
mipi_dbi_command(dbi, ST7735S_PWCTR4, 0x8d, 0x2a);
|
|
|
|
/* 14. PWCTR5 (0xc4) - 0x8d,0xee */
|
|
pr_info("st7735s: [14/19] PWCTR5 (0xc4)\n");
|
|
mipi_dbi_command(dbi, ST7735S_PWCTR5, 0x8d, 0xee);
|
|
|
|
/* 15. VMCTR1 (0xc5) - 0x0c */
|
|
pr_info("st7735s: [15/19] VMCTR1 (0xc5) = 0x0c\n");
|
|
mipi_dbi_command(dbi, ST7735S_VMCTR1, 0x0c);
|
|
|
|
/* 16. GAMCTRP1 (0xe0) - Positive Gamma */
|
|
pr_info("st7735s: [16/19] GAMCTRP1 (0xe0)\n");
|
|
mipi_dbi_command(dbi, ST7735S_GAMCTRP1,
|
|
0x0c, 0x1c, 0x0f, 0x18, 0x36, 0x2f, 0x27, 0x2a,
|
|
0x27, 0x25, 0x2d, 0x3c, 0x00, 0x05, 0x03, 0x10);
|
|
|
|
/* 17. GAMCTRN1 (0xe1) - Negative Gamma */
|
|
pr_info("st7735s: [17/19] GAMCTRN1 (0xe1)\n");
|
|
mipi_dbi_command(dbi, ST7735S_GAMCTRN1,
|
|
0x0c, 0x1a, 0x09, 0x09, 0x26, 0x22, 0x1e, 0x25,
|
|
0x25, 0x25, 0x2e, 0x3b, 0x00, 0x05, 0x03, 0x10);
|
|
|
|
/* 18. ENTER_INVERT_MODE (0x21) */
|
|
pr_info("st7735s: [18/19] ENTER_INVERT_MODE (0x21)\n");
|
|
mipi_dbi_command(dbi, MIPI_DCS_ENTER_INVERT_MODE);
|
|
|
|
/* 19. SET_DISPLAY_ON (0x29) - delay 120ms (0x78) */
|
|
pr_info("st7735s: [19/19] SET_DISPLAY_ON (0x29)\n");
|
|
mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
|
|
msleep(120);
|
|
|
|
pr_info("st7735s: === SEQUENCE COMPLETE, calling flush ===\n");
|
|
mipi_dbi_enable_flush(dbidev, crtc_state, plane_state);
|
|
pr_info("st7735s: === FLUSH COMPLETE ===\n");
|
|
|
|
out_exit:
|
|
drm_dev_exit(idx);
|
|
}
|
|
|
|
static const struct drm_simple_display_pipe_funcs st7735s_pipe_funcs = {
|
|
DRM_MIPI_DBI_SIMPLE_DISPLAY_PIPE_FUNCS(st7735s_pipe_enable),
|
|
};
|
|
|
|
static const struct st7735s_cfg st7735s_128x128_cfg = {
|
|
.mode = { DRM_SIMPLE_MODE(128, 128, 22, 22) },
|
|
.left_offset = 2,
|
|
.top_offset = 1,
|
|
.write_only = false,
|
|
.rgb = false,
|
|
};
|
|
|
|
/* ... resto del driver igual ... */
|
|
|
|
|
|
DEFINE_DRM_GEM_DMA_FOPS(st7735s_fops);
|
|
|
|
static const struct drm_driver st7735s_driver = {
|
|
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
|
|
.fops = &st7735s_fops,
|
|
DRM_GEM_DMA_DRIVER_OPS_VMAP,
|
|
.debugfs_init = mipi_dbi_debugfs_init,
|
|
.name = "st7735s",
|
|
.desc = "Sitronix ST7735S",
|
|
.date = "20251013",
|
|
.major = 1,
|
|
.minor = 0,
|
|
};
|
|
|
|
static const struct of_device_id st7735s_of_match[] = {
|
|
{ .compatible = "sitronix,st7735s", .data = &st7735s_128x128_cfg },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, st7735s_of_match);
|
|
|
|
static const struct spi_device_id st7735s_id[] = {
|
|
{ "st7735s", (uintptr_t)&st7735s_128x128_cfg },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(spi, st7735s_id);
|
|
|
|
static int st7735s_probe(struct spi_device *spi)
|
|
{
|
|
struct device *dev = &spi->dev;
|
|
const struct st7735s_cfg *cfg;
|
|
struct mipi_dbi_dev *dbidev;
|
|
struct st7735s_priv *priv;
|
|
struct drm_device *drm;
|
|
struct mipi_dbi *dbi;
|
|
struct gpio_desc *dc;
|
|
u32 rotation = 0;
|
|
int ret;
|
|
|
|
cfg = device_get_match_data(&spi->dev);
|
|
if (!cfg)
|
|
cfg = (void *)spi_get_device_id(spi)->driver_data;
|
|
|
|
priv = devm_drm_dev_alloc(dev, &st7735s_driver,
|
|
struct st7735s_priv, dbidev.drm);
|
|
if (IS_ERR(priv))
|
|
return PTR_ERR(priv);
|
|
|
|
dbidev = &priv->dbidev;
|
|
priv->cfg = cfg;
|
|
|
|
dbi = &dbidev->dbi;
|
|
drm = &dbidev->drm;
|
|
|
|
dbi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
|
|
if (IS_ERR(dbi->reset))
|
|
return dev_err_probe(dev, PTR_ERR(dbi->reset),
|
|
"Failed to get GPIO 'reset'\n");
|
|
|
|
dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
|
|
if (IS_ERR(dc))
|
|
return dev_err_probe(dev, PTR_ERR(dc),
|
|
"Failed to get GPIO 'dc'\n");
|
|
|
|
dbidev->backlight = devm_of_find_backlight(dev);
|
|
if (IS_ERR(dbidev->backlight))
|
|
return PTR_ERR(dbidev->backlight);
|
|
|
|
device_property_read_u32(dev, "rotation", &rotation);
|
|
|
|
ret = mipi_dbi_spi_init(spi, dbi, dc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (cfg->write_only)
|
|
dbi->read_commands = NULL;
|
|
|
|
dbidev->left_offset = cfg->left_offset;
|
|
dbidev->top_offset = cfg->top_offset;
|
|
|
|
ret = mipi_dbi_dev_init(dbidev, &st7735s_pipe_funcs, &cfg->mode,
|
|
rotation);
|
|
if (ret)
|
|
return ret;
|
|
|
|
drm_mode_config_reset(drm);
|
|
|
|
ret = drm_dev_register(drm, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
spi_set_drvdata(spi, drm);
|
|
|
|
drm_fbdev_dma_setup(drm, 16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void st7735s_remove(struct spi_device *spi)
|
|
{
|
|
struct drm_device *drm = spi_get_drvdata(spi);
|
|
|
|
drm_dev_unplug(drm);
|
|
drm_atomic_helper_shutdown(drm);
|
|
}
|
|
|
|
static void st7735s_shutdown(struct spi_device *spi)
|
|
{
|
|
drm_atomic_helper_shutdown(spi_get_drvdata(spi));
|
|
}
|
|
|
|
static struct spi_driver st7735s_spi_driver = {
|
|
.driver = {
|
|
.name = "st7735s",
|
|
.of_match_table = st7735s_of_match,
|
|
},
|
|
.id_table = st7735s_id,
|
|
.probe = st7735s_probe,
|
|
.remove = st7735s_remove,
|
|
.shutdown = st7735s_shutdown,
|
|
};
|
|
module_spi_driver(st7735s_spi_driver);
|
|
|
|
MODULE_DESCRIPTION("Sitronix ST7735S DRM driver");
|
|
MODULE_AUTHOR("Based on st7735r by David Lechner");
|
|
MODULE_LICENSE("GPL");
|