| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * DRM driver for the HX8357D LCD controller |
| * |
| * Copyright 2018 Broadcom |
| * Copyright 2018 David Lechner <david@lechnology.com> |
| * Copyright 2016 Noralf Trønnes |
| * Copyright (C) 2015 Adafruit Industries |
| * Copyright (C) 2013 Christian Vogelgsang |
| */ |
| |
| #include <linux/backlight.h> |
| #include <linux/delay.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/module.h> |
| #include <linux/property.h> |
| #include <linux/spi/spi.h> |
| |
| #include <drm/clients/drm_client_setup.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> |
| #include <drm/drm_modeset_helper.h> |
| #include <drm/drm_print.h> |
| #include <video/mipi_display.h> |
| |
| #define HX8357D_SETOSC 0xb0 |
| #define HX8357D_SETPOWER 0xb1 |
| #define HX8357D_SETRGB 0xb3 |
| #define HX8357D_SETCYC 0xb3 |
| #define HX8357D_SETCOM 0xb6 |
| #define HX8357D_SETEXTC 0xb9 |
| #define HX8357D_SETSTBA 0xc0 |
| #define HX8357D_SETPANEL 0xcc |
| #define HX8357D_SETGAMMA 0xe0 |
| |
| #define HX8357D_MADCTL_MY 0x80 |
| #define HX8357D_MADCTL_MX 0x40 |
| #define HX8357D_MADCTL_MV 0x20 |
| #define HX8357D_MADCTL_ML 0x10 |
| #define HX8357D_MADCTL_RGB 0x00 |
| #define HX8357D_MADCTL_BGR 0x08 |
| #define HX8357D_MADCTL_MH 0x04 |
| |
| struct hx8357d_device { |
| struct mipi_dbi_dev dbidev; |
| |
| struct drm_plane plane; |
| struct drm_crtc crtc; |
| struct drm_encoder encoder; |
| struct drm_connector connector; |
| }; |
| |
| static struct hx8357d_device *to_hx8357d_device(struct drm_device *dev) |
| { |
| return container_of(drm_to_mipi_dbi_dev(dev), struct hx8357d_device, dbidev); |
| } |
| |
| static const u32 hx8357d_plane_formats[] = { |
| DRM_MIPI_DBI_PLANE_FORMATS, |
| }; |
| |
| static const u64 hx8357d_plane_format_modifiers[] = { |
| DRM_MIPI_DBI_PLANE_FORMAT_MODIFIERS, |
| }; |
| |
| static const struct drm_plane_helper_funcs hx8357d_plane_helper_funcs = { |
| DRM_MIPI_DBI_PLANE_HELPER_FUNCS, |
| }; |
| |
| static const struct drm_plane_funcs hx8357d_plane_funcs = { |
| DRM_MIPI_DBI_PLANE_FUNCS, |
| .destroy = drm_plane_cleanup, |
| }; |
| |
| static void hx8357d_crtc_helper_atomic_enable(struct drm_crtc *crtc, |
| struct drm_atomic_state *state) |
| { |
| struct drm_device *drm = crtc->dev; |
| struct hx8357d_device *hx8357d = to_hx8357d_device(drm); |
| struct mipi_dbi_dev *dbidev = &hx8357d->dbidev; |
| struct mipi_dbi *dbi = &dbidev->dbi; |
| u8 addr_mode; |
| int ret, idx; |
| |
| if (!drm_dev_enter(drm, &idx)) |
| return; |
| |
| DRM_DEBUG_KMS("\n"); |
| |
| ret = mipi_dbi_poweron_conditional_reset(dbidev); |
| if (ret < 0) |
| goto out_exit; |
| if (ret == 1) |
| goto out_enable; |
| |
| /* setextc */ |
| mipi_dbi_command(dbi, HX8357D_SETEXTC, 0xFF, 0x83, 0x57); |
| msleep(150); |
| |
| /* setRGB which also enables SDO */ |
| mipi_dbi_command(dbi, HX8357D_SETRGB, 0x00, 0x00, 0x06, 0x06); |
| |
| /* -1.52V */ |
| mipi_dbi_command(dbi, HX8357D_SETCOM, 0x25); |
| |
| /* Normal mode 70Hz, Idle mode 55 Hz */ |
| mipi_dbi_command(dbi, HX8357D_SETOSC, 0x68); |
| |
| /* Set Panel - BGR, Gate direction swapped */ |
| mipi_dbi_command(dbi, HX8357D_SETPANEL, 0x05); |
| |
| mipi_dbi_command(dbi, HX8357D_SETPOWER, |
| 0x00, /* Not deep standby */ |
| 0x15, /* BT */ |
| 0x1C, /* VSPR */ |
| 0x1C, /* VSNR */ |
| 0x83, /* AP */ |
| 0xAA); /* FS */ |
| |
| mipi_dbi_command(dbi, HX8357D_SETSTBA, |
| 0x50, /* OPON normal */ |
| 0x50, /* OPON idle */ |
| 0x01, /* STBA */ |
| 0x3C, /* STBA */ |
| 0x1E, /* STBA */ |
| 0x08); /* GEN */ |
| |
| mipi_dbi_command(dbi, HX8357D_SETCYC, |
| 0x02, /* NW 0x02 */ |
| 0x40, /* RTN */ |
| 0x00, /* DIV */ |
| 0x2A, /* DUM */ |
| 0x2A, /* DUM */ |
| 0x0D, /* GDON */ |
| 0x78); /* GDOFF */ |
| |
| mipi_dbi_command(dbi, HX8357D_SETGAMMA, |
| 0x02, |
| 0x0A, |
| 0x11, |
| 0x1d, |
| 0x23, |
| 0x35, |
| 0x41, |
| 0x4b, |
| 0x4b, |
| 0x42, |
| 0x3A, |
| 0x27, |
| 0x1B, |
| 0x08, |
| 0x09, |
| 0x03, |
| 0x02, |
| 0x0A, |
| 0x11, |
| 0x1d, |
| 0x23, |
| 0x35, |
| 0x41, |
| 0x4b, |
| 0x4b, |
| 0x42, |
| 0x3A, |
| 0x27, |
| 0x1B, |
| 0x08, |
| 0x09, |
| 0x03, |
| 0x00, |
| 0x01); |
| |
| /* 16 bit */ |
| mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, |
| MIPI_DCS_PIXEL_FMT_16BIT); |
| |
| /* TE off */ |
| mipi_dbi_command(dbi, MIPI_DCS_SET_TEAR_ON, 0x00); |
| |
| /* tear line */ |
| mipi_dbi_command(dbi, MIPI_DCS_SET_TEAR_SCANLINE, 0x00, 0x02); |
| |
| /* Exit Sleep */ |
| mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); |
| msleep(150); |
| |
| /* display on */ |
| mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); |
| usleep_range(5000, 7000); |
| |
| out_enable: |
| switch (dbidev->rotation) { |
| default: |
| addr_mode = HX8357D_MADCTL_MX | HX8357D_MADCTL_MY; |
| break; |
| case 90: |
| addr_mode = HX8357D_MADCTL_MV | HX8357D_MADCTL_MY; |
| break; |
| case 180: |
| addr_mode = 0; |
| break; |
| case 270: |
| addr_mode = HX8357D_MADCTL_MV | HX8357D_MADCTL_MX; |
| break; |
| } |
| mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); |
| |
| backlight_enable(dbidev->backlight); |
| out_exit: |
| drm_dev_exit(idx); |
| } |
| |
| static const struct drm_crtc_helper_funcs hx8357d_crtc_helper_funcs = { |
| DRM_MIPI_DBI_CRTC_HELPER_FUNCS, |
| .atomic_enable = hx8357d_crtc_helper_atomic_enable, |
| }; |
| |
| static const struct drm_crtc_funcs hx8357d_crtc_funcs = { |
| DRM_MIPI_DBI_CRTC_FUNCS, |
| .destroy = drm_crtc_cleanup, |
| }; |
| |
| static const struct drm_encoder_funcs hx8357d_encoder_funcs = { |
| .destroy = drm_encoder_cleanup, |
| }; |
| |
| static const struct drm_connector_helper_funcs hx8357d_connector_helper_funcs = { |
| DRM_MIPI_DBI_CONNECTOR_HELPER_FUNCS, |
| }; |
| |
| static const struct drm_connector_funcs hx8357d_connector_funcs = { |
| DRM_MIPI_DBI_CONNECTOR_FUNCS, |
| .destroy = drm_connector_cleanup, |
| }; |
| |
| static const struct drm_mode_config_helper_funcs hx8357d_mode_config_helper_funcs = { |
| DRM_MIPI_DBI_MODE_CONFIG_HELPER_FUNCS, |
| }; |
| |
| static const struct drm_mode_config_funcs hx8357d_mode_config_funcs = { |
| DRM_MIPI_DBI_MODE_CONFIG_FUNCS, |
| }; |
| |
| static const struct drm_display_mode yx350hv15_mode = { |
| DRM_SIMPLE_MODE(320, 480, 60, 75), |
| }; |
| |
| DEFINE_DRM_GEM_DMA_FOPS(hx8357d_fops); |
| |
| static const struct drm_driver hx8357d_driver = { |
| .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, |
| .fops = &hx8357d_fops, |
| DRM_GEM_DMA_DRIVER_OPS_VMAP, |
| DRM_FBDEV_DMA_DRIVER_OPS, |
| .debugfs_init = mipi_dbi_debugfs_init, |
| .name = "hx8357d", |
| .desc = "HX8357D", |
| .major = 1, |
| .minor = 0, |
| }; |
| |
| static const struct of_device_id hx8357d_of_match[] = { |
| { .compatible = "adafruit,yx350hv15" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, hx8357d_of_match); |
| |
| static const struct spi_device_id hx8357d_id[] = { |
| { "yx350hv15", 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(spi, hx8357d_id); |
| |
| static int hx8357d_probe(struct spi_device *spi) |
| { |
| struct device *dev = &spi->dev; |
| struct hx8357d_device *hx8357d; |
| struct mipi_dbi_dev *dbidev; |
| struct drm_device *drm; |
| struct gpio_desc *dc; |
| struct drm_plane *plane; |
| struct drm_crtc *crtc; |
| struct drm_encoder *encoder; |
| struct drm_connector *connector; |
| u32 rotation = 0; |
| int ret; |
| |
| hx8357d = devm_drm_dev_alloc(dev, &hx8357d_driver, struct hx8357d_device, dbidev.drm); |
| if (IS_ERR(hx8357d)) |
| return PTR_ERR(hx8357d); |
| dbidev = &hx8357d->dbidev; |
| drm = &dbidev->drm; |
| |
| 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, &dbidev->dbi, dc); |
| if (ret) |
| return ret; |
| |
| ret = drm_mipi_dbi_dev_init(dbidev, &yx350hv15_mode, hx8357d_plane_formats[0], |
| rotation, 0); |
| if (ret) |
| return ret; |
| |
| ret = drmm_mode_config_init(drm); |
| if (ret) |
| return ret; |
| |
| drm->mode_config.min_width = dbidev->mode.hdisplay; |
| drm->mode_config.max_width = dbidev->mode.hdisplay; |
| drm->mode_config.min_height = dbidev->mode.vdisplay; |
| drm->mode_config.max_height = dbidev->mode.vdisplay; |
| drm->mode_config.funcs = &hx8357d_mode_config_funcs; |
| drm->mode_config.preferred_depth = 16; |
| drm->mode_config.helper_private = &hx8357d_mode_config_helper_funcs; |
| |
| plane = &hx8357d->plane; |
| ret = drm_universal_plane_init(drm, plane, 0, &hx8357d_plane_funcs, |
| hx8357d_plane_formats, ARRAY_SIZE(hx8357d_plane_formats), |
| hx8357d_plane_format_modifiers, |
| DRM_PLANE_TYPE_PRIMARY, NULL); |
| if (ret) |
| return ret; |
| drm_plane_helper_add(plane, &hx8357d_plane_helper_funcs); |
| drm_plane_enable_fb_damage_clips(plane); |
| |
| crtc = &hx8357d->crtc; |
| ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL, &hx8357d_crtc_funcs, NULL); |
| if (ret) |
| return ret; |
| drm_crtc_helper_add(crtc, &hx8357d_crtc_helper_funcs); |
| |
| encoder = &hx8357d->encoder; |
| ret = drm_encoder_init(drm, encoder, &hx8357d_encoder_funcs, DRM_MODE_ENCODER_NONE, NULL); |
| if (ret) |
| return ret; |
| encoder->possible_crtcs = drm_crtc_mask(crtc); |
| |
| connector = &hx8357d->connector; |
| ret = drm_connector_init(drm, connector, &hx8357d_connector_funcs, |
| DRM_MODE_CONNECTOR_SPI); |
| if (ret) |
| return ret; |
| drm_connector_helper_add(connector, &hx8357d_connector_helper_funcs); |
| |
| ret = drm_connector_attach_encoder(connector, encoder); |
| 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_client_setup(drm, NULL); |
| |
| return 0; |
| } |
| |
| static void hx8357d_remove(struct spi_device *spi) |
| { |
| struct drm_device *drm = spi_get_drvdata(spi); |
| |
| drm_dev_unplug(drm); |
| drm_atomic_helper_shutdown(drm); |
| } |
| |
| static void hx8357d_shutdown(struct spi_device *spi) |
| { |
| drm_atomic_helper_shutdown(spi_get_drvdata(spi)); |
| } |
| |
| static struct spi_driver hx8357d_spi_driver = { |
| .driver = { |
| .name = "hx8357d", |
| .of_match_table = hx8357d_of_match, |
| }, |
| .id_table = hx8357d_id, |
| .probe = hx8357d_probe, |
| .remove = hx8357d_remove, |
| .shutdown = hx8357d_shutdown, |
| }; |
| module_spi_driver(hx8357d_spi_driver); |
| |
| MODULE_DESCRIPTION("HX8357D DRM driver"); |
| MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); |
| MODULE_LICENSE("GPL"); |