On 25/07/2024 02:47, Alex Lanzano wrote: > Add support for the monochrome Sharp Memory LCDs. > > Signed-off-by: Alex Lanzano <lanzano.alex@xxxxxxxxx> > --- > MAINTAINERS | 8 + > drivers/gpu/drm/tiny/Kconfig | 20 + > drivers/gpu/drm/tiny/Makefile | 1 + > drivers/gpu/drm/tiny/sharp-memory.c | 742 ++++++++++++++++++++++++++++ > 4 files changed, 771 insertions(+) > create mode 100644 drivers/gpu/drm/tiny/sharp-memory.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 71b739b40921..d19893d25913 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -7123,6 +7123,14 @@ S: Maintained > F: Documentation/devicetree/bindings/display/panel/samsung,s6d7aa0.yaml > F: drivers/gpu/drm/panel/panel-samsung-s6d7aa0.c > > +DRM DRIVER FOR SHARP MEMORY LCD > +M: Alex Lanzano <lanzano.alex@xxxxxxxxx> > +S: Maintained > +T: git git://anongit.freedesktop.org/drm/drm-misc > +F: Documentation/devicetree/bindings/display/sharp,sharp-memory.yaml > +F: drivers/gpu/drm/tiny/sharp-memory.c > +F: include/dt-bindings/display/sharp-memory.h > + > DRM DRIVER FOR SITRONIX ST7586 PANELS > M: David Lechner <david@xxxxxxxxxxxxxx> > S: Maintained > diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig > index f6889f649bc1..bc386954faa2 100644 > --- a/drivers/gpu/drm/tiny/Kconfig > +++ b/drivers/gpu/drm/tiny/Kconfig > @@ -186,6 +186,26 @@ config TINYDRM_REPAPER > > If M is selected the module will be called repaper. > > +config TINYDRM_SHARP_MEMORY > + tristate "DRM support for Sharp Memory LCD panels" > + depends on DRM && SPI > + select DRM_GEM_DMA_HELPER > + select DRM_KMS_HELPER > + help > + DRM Driver for the following Sharp Memory Panels: > + * 1.00" Sharp Memory LCD (LS010B7DH04) > + * 1.10" Sharp Memory LCD (LS011B7DH03) > + * 1.20" Sharp Memory LCD (LS012B7DD01) > + * 1.28" Sharp Memory LCD (LS013B7DH03) > + * 1.26" Sharp Memory LCD (LS013B7DH05) > + * 1.80" Sharp Memory LCD (LS018B7DH02) > + * 2.70" Sharp Memory LCD (LS027B7DH01) > + * 2.70" Sharp Memory LCD (LS027B7DH01A) > + * 3.20" Sharp Memory LCD (LS032B7DD02) > + * 4.40" Sharp Memory LCD (LS044Q7DH01) > + > + If M is selected the module will be called sharp_memory. > + > config TINYDRM_ST7586 > tristate "DRM support for Sitronix ST7586 display panels" > depends on DRM && SPI > diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile > index 76dde89a044b..4aaf56f8707d 100644 > --- a/drivers/gpu/drm/tiny/Makefile > +++ b/drivers/gpu/drm/tiny/Makefile > @@ -14,5 +14,6 @@ obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o > obj-$(CONFIG_TINYDRM_ILI9486) += ili9486.o > obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o > obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o > +obj-$(CONFIG_TINYDRM_SHARP_MEMORY) += sharp-memory.o > obj-$(CONFIG_TINYDRM_ST7586) += st7586.o > obj-$(CONFIG_TINYDRM_ST7735R) += st7735r.o > diff --git a/drivers/gpu/drm/tiny/sharp-memory.c b/drivers/gpu/drm/tiny/sharp-memory.c > new file mode 100644 > index 000000000000..5e61e348ce3a > --- /dev/null > +++ b/drivers/gpu/drm/tiny/sharp-memory.c > @@ -0,0 +1,742 @@ > +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/spi/spi.h> > +#include <linux/gpio/consumer.h> > +#include <linux/delay.h> > +#include <linux/kthread.h> > +#include <linux/bitrev.h> > +#include <linux/pwm.h> > +#include <linux/mutex.h> > + > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_connector.h> > +#include <drm/drm_damage_helper.h> > +#include <drm/drm_drv.h> > +#include <drm/drm_fb_dma_helper.h> > +#include <drm/drm_fbdev_dma.h> > +#include <drm/drm_format_helper.h> > +#include <drm/drm_framebuffer.h> > +#include <drm/drm_gem_atomic_helper.h> > +#include <drm/drm_gem_dma_helper.h> > +#include <drm/drm_gem_framebuffer_helper.h> > +#include <drm/drm_managed.h> > +#include <drm/drm_modes.h> > +#include <drm/drm_rect.h> > +#include <drm/drm_probe_helper.h> > + > +#include <dt-bindings/display/sharp-memory.h> > + > +#define SHARP_MODE_PERIOD 8 > +#define SHARP_ADDR_PERIOD 8 > +#define SHARP_DUMMY_PERIOD 8 > + > +#define SHARP_MEMORY_DISPLAY_MAINTAIN_MODE 0 > +#define SHARP_MEMORY_DISPLAY_UPDATE_MODE 1 > +#define SHARP_MEMORY_DISPLAY_CLEAR_MODE 4 > + > +enum sharp_memory_model { > + LS010B7DH04 = 1, > + LS011B7DH03, > + LS012B7DD01, > + LS013B7DH03, > + LS013B7DH05, > + LS018B7DH02, > + LS027B7DH01, > + LS027B7DH01A, > + LS032B7DD02, > + LS044Q7DH01, > +}; > + > +struct sharp_memory_device { > + struct drm_device drm; > + struct spi_device *spi; > + > + const struct drm_display_mode *mode; > + > + struct drm_crtc crtc; > + struct drm_plane plane; > + struct drm_encoder encoder; > + struct drm_connector connector; > + > + struct gpio_desc *enable_gpio; > + > + struct task_struct *sw_vcom_signal; > + struct pwm_device *pwm_vcom_signal; > + > + uint32_t vcom_mode; > + uint8_t vcom; > + > + uint32_t pitch; > + uint32_t tx_buffer_size; > + uint8_t *tx_buffer; > + struct mutex tx_mutex; > +}; > + > +static inline int sharp_memory_spi_write(struct spi_device *spi, void *buf, size_t len) > +{ > + /* Reverse the bit order */ > + for (uint8_t *b = buf; b < ((uint8_t *)buf) + len; ++b) > + *b = bitrev8(*b); > + > + return spi_write(spi, buf, len); > +} > + > +static inline struct sharp_memory_device *drm_to_sharp_memory_device(struct drm_device *drm) > +{ > + return container_of(drm, struct sharp_memory_device, drm); > +} > + > +DEFINE_DRM_GEM_DMA_FOPS(sharp_memory_fops); > + > +static const struct drm_driver sharp_memory_drm_driver = { > + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, > + .fops = &sharp_memory_fops, > + DRM_GEM_DMA_DRIVER_OPS_VMAP, > + .name = "sharp_memory_display", > + .desc = "Sharp Display Memory LCD", > + .date = "20231129", > + .major = 1, > + .minor = 0, > +}; > + > +static inline void sharp_memory_set_tx_buffer_mode(uint8_t *buffer, uint8_t mode, uint8_t vcom) > +{ > + > + *buffer = mode | (vcom << 1); > +} > + > +static inline void sharp_memory_set_tx_buffer_addresses(uint8_t *buffer, > + struct drm_rect clip, > + uint32_t pitch) > +{ > + for (uint32_t line = 0; line < clip.y2; ++line) > + buffer[line * pitch] = line + 1; > + > +} > + > +static void sharp_memory_set_tx_buffer_data(uint8_t *buffer, > + struct drm_framebuffer *fb, > + struct drm_rect clip, > + uint32_t pitch, > + struct drm_format_conv_state *fmtcnv_state) > +{ > + int ret; > + struct iosys_map dst, vmap; > + struct drm_gem_dma_object *dma_obj = drm_fb_dma_get_gem_obj(fb, 0); > + > + ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); > + if (ret) > + return; > + > + > + iosys_map_set_vaddr(&dst, buffer); > + iosys_map_set_vaddr(&vmap, dma_obj->vaddr); > + > + drm_fb_xrgb8888_to_mono(&dst, &pitch, &vmap, fb, &clip, fmtcnv_state); > + > + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); > +} > + > +static int sharp_memory_update_display(struct sharp_memory_device *smd, > + struct drm_framebuffer *fb, > + struct drm_rect clip, > + struct drm_format_conv_state *fmtcnv_state) > +{ > + int ret; > + uint32_t pitch = smd->pitch; > + uint8_t vcom = smd->vcom; > + uint8_t *tx_buffer = smd->tx_buffer; > + uint32_t tx_buffer_size = smd->tx_buffer_size; > + > + mutex_lock(&smd->tx_mutex); > + > + /* Populate the transmit buffer with frame data */ > + sharp_memory_set_tx_buffer_mode(&tx_buffer[0], > + SHARP_MEMORY_DISPLAY_UPDATE_MODE, vcom); > + sharp_memory_set_tx_buffer_addresses(&tx_buffer[1], clip, pitch); > + sharp_memory_set_tx_buffer_data(&tx_buffer[2], fb, clip, pitch, fmtcnv_state); > + > + ret = sharp_memory_spi_write(smd->spi, tx_buffer, tx_buffer_size); > + > + mutex_unlock(&smd->tx_mutex); > + > + return ret; > +} > + > +static int sharp_memory_maintain_display(struct sharp_memory_device *smd) > +{ > + int ret; > + uint8_t vcom = smd->vcom; > + uint8_t *tx_buffer = smd->tx_buffer; > + > + mutex_lock(&smd->tx_mutex); > + > + sharp_memory_set_tx_buffer_mode(&tx_buffer[0], SHARP_MEMORY_DISPLAY_MAINTAIN_MODE, vcom); > + tx_buffer[1] = 0; // Write dummy data > + ret = sharp_memory_spi_write(smd->spi, tx_buffer, 2); > + > + mutex_unlock(&smd->tx_mutex); > + > + return ret; > +} > + > +static int sharp_memory_clear_display(struct sharp_memory_device *smd) > +{ > + int ret; > + uint8_t vcom = smd->vcom; > + uint8_t *tx_buffer = smd->tx_buffer; > + > + mutex_lock(&smd->tx_mutex); > + > + sharp_memory_set_tx_buffer_mode(&tx_buffer[0], SHARP_MEMORY_DISPLAY_CLEAR_MODE, vcom); > + tx_buffer[1] = 0; // write dummy data > + ret = sharp_memory_spi_write(smd->spi, tx_buffer, 2); > + > + mutex_unlock(&smd->tx_mutex); > + > + return ret; > +} > + > +static void sharp_memory_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect, > + struct drm_format_conv_state *fmtconv_state) > +{ > + struct drm_rect clip; > + struct sharp_memory_device *smd = drm_to_sharp_memory_device(fb->dev); > + > + /* Always update a full line regardless of what is dirty */ > + clip.x1 = 0; > + clip.x2 = fb->width; > + clip.y1 = rect->y1; > + clip.y2 = rect->y2; > + > + sharp_memory_update_display(smd, fb, clip, fmtconv_state); > +} > + > +static int sharp_memory_plane_atomic_check(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct sharp_memory_device *smd; > + struct drm_crtc_state *crtc_state; > + > + smd = container_of(plane, struct sharp_memory_device, plane); > + crtc_state = drm_atomic_get_new_crtc_state(state, &smd->crtc); > + > + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, > + DRM_PLANE_NO_SCALING, > + DRM_PLANE_NO_SCALING, > + false, false); > +} > + > +static void sharp_memory_plane_atomic_update(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + > + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); > + struct drm_plane_state *plane_state = plane->state; > + struct drm_format_conv_state fmtcnv_state = DRM_FORMAT_CONV_STATE_INIT; > + struct sharp_memory_device *smd; > + struct drm_rect rect; > + > + smd = container_of(plane, struct sharp_memory_device, plane); > + if (!smd->crtc.state->active) > + return; > + > + > + if (drm_atomic_helper_damage_merged(old_state, plane_state, &rect)) > + sharp_memory_fb_dirty(plane_state->fb, &rect, &fmtcnv_state); > + > + drm_format_conv_state_release(&fmtcnv_state); > +} > + > +static const struct drm_plane_helper_funcs sharp_memory_plane_helper_funcs = { > + .prepare_fb = drm_gem_plane_helper_prepare_fb, > + .atomic_check = sharp_memory_plane_atomic_check, > + .atomic_update = sharp_memory_plane_atomic_update, > +}; > + > +static bool sharp_memory_format_mod_supported(struct drm_plane *plane, > + uint32_t format, > + uint64_t modifier) > +{ > + return modifier == DRM_FORMAT_MOD_LINEAR; > +} > + > +static const struct drm_plane_funcs sharp_memory_plane_funcs = { > + .update_plane = drm_atomic_helper_update_plane, > + .disable_plane = drm_atomic_helper_disable_plane, > + .destroy = drm_plane_cleanup, > + .reset = drm_atomic_helper_plane_reset, > + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, > + .format_mod_supported = sharp_memory_format_mod_supported, > +}; > + > +static enum drm_mode_status sharp_memory_crtc_mode_valid(struct drm_crtc *crtc, > + const struct drm_display_mode *mode) > +{ > + struct sharp_memory_device *smd = drm_to_sharp_memory_device(crtc->dev); > + > + return drm_crtc_helper_mode_valid_fixed(crtc, mode, smd->mode); > +} > + > +static int sharp_memory_crtc_check(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); > + int ret; > + > + if (!crtc_state->enable) > + goto out; > + > + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); > + if (ret) > + return ret; > + > +out: > + return drm_atomic_add_affected_planes(state, crtc); > +} > + > +static int sharp_memory_sw_vcom_signal_thread(void *data) > +{ > + struct sharp_memory_device *smd = data; > + > + while (!kthread_should_stop()) { > + smd->vcom ^= 1; // Toggle vcom > + sharp_memory_maintain_display(smd); > + msleep(1000); > + } > + > + return 0; > +} > + > +static void sharp_memory_crtc_enable(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct pwm_state pwm_state; > + struct sharp_memory_device *smd = drm_to_sharp_memory_device(crtc->dev); > + > + sharp_memory_clear_display(smd); > + > + if (smd->enable_gpio) > + gpiod_set_value(smd->enable_gpio, 1); > + > + > + switch (smd->vcom_mode) { > + case SHARP_MEMORY_SOFTWARE_VCOM: > + smd->sw_vcom_signal = kthread_run(sharp_memory_sw_vcom_signal_thread, > + smd, "sw_vcom_signal"); > + break; > + > + case SHARP_MEMORY_EXTERNAL_VCOM: > + break; > + > + case SHARP_MEMORY_PWM_VCOM: > + pwm_get_state(smd->pwm_vcom_signal, &pwm_state); > + pwm_state.period = 1000000000; > + pwm_state.duty_cycle = 100000000; > + pwm_state.enabled = true; > + pwm_apply_state(smd->pwm_vcom_signal, &pwm_state); > + break; > + } > +} > + > +static void sharp_memory_crtc_disable(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct sharp_memory_device *smd = drm_to_sharp_memory_device(crtc->dev); > + > + sharp_memory_clear_display(smd); > + > + if (smd->enable_gpio) > + gpiod_set_value(smd->enable_gpio, 0); > + > + > + switch (smd->vcom_mode) { > + case SHARP_MEMORY_SOFTWARE_VCOM: > + kthread_stop(smd->sw_vcom_signal); > + break; > + > + case SHARP_MEMORY_EXTERNAL_VCOM: > + break; > + > + case SHARP_MEMORY_PWM_VCOM: > + pwm_disable(smd->pwm_vcom_signal); > + break; > + } > +} > + > +static const struct drm_crtc_helper_funcs sharp_memory_crtc_helper_funcs = { > + .mode_valid = sharp_memory_crtc_mode_valid, > + .atomic_check = sharp_memory_crtc_check, > + .atomic_enable = sharp_memory_crtc_enable, > + .atomic_disable = sharp_memory_crtc_disable, > +}; > + > +static const struct drm_crtc_funcs sharp_memory_crtc_funcs = { > + .reset = drm_atomic_helper_crtc_reset, > + .destroy = drm_crtc_cleanup, > + .set_config = drm_atomic_helper_set_config, > + .page_flip = drm_atomic_helper_page_flip, > + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, > +}; > + > +static const struct drm_encoder_funcs sharp_memory_encoder_funcs = { > + .destroy = drm_encoder_cleanup, > +}; > + > +static int sharp_memory_connector_get_modes(struct drm_connector *connector) > +{ > + struct sharp_memory_device *smd = drm_to_sharp_memory_device(connector->dev); > + > + return drm_connector_helper_get_modes_fixed(connector, smd->mode); > +} > + > +static const struct drm_connector_helper_funcs sharp_memory_connector_hfuncs = { > + .get_modes = sharp_memory_connector_get_modes, > +}; > + > +static const struct drm_connector_funcs sharp_memory_connector_funcs = { > + .reset = drm_atomic_helper_connector_reset, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = drm_connector_cleanup, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > + > +}; > + > +static const struct drm_mode_config_funcs sharp_memory_mode_config_funcs = { > + .fb_create = drm_gem_fb_create_with_dirty, > + .atomic_check = drm_atomic_helper_check, > + .atomic_commit = drm_atomic_helper_commit, > +}; > + > +static const struct spi_device_id sharp_memory_ids[] = { > + {"ls010b7dh04", LS010B7DH04}, > + {"ls011b7dh03", LS011B7DH03}, > + {"ls012b7dd01", LS012B7DD01}, > + {"ls013b7dh03", LS013B7DH03}, > + {"ls013b7dh05", LS013B7DH05}, > + {"ls018b7dh02", LS018B7DH02}, > + {"ls027b7dh01", LS027B7DH01}, > + {"ls027b7dh01a", LS027B7DH01A}, > + {"ls032b7dd02", LS032B7DD02}, > + {"ls044q7dh01", LS044Q7DH01}, > + {}, > +}; > +MODULE_DEVICE_TABLE(spi, sharp_memory_ids); > + > +static const struct of_device_id sharp_memory_of_match[] = { > + {.compatible = "sharp,ls010b7dh04"}, Both ID tables should be in sync. See not-so-recent IIO discussions and commits. > + {.compatible = "sharp,ls011b7dh03"}, > + {.compatible = "sharp,ls012b7dd01"}, > + {.compatible = "sharp,ls013b7dh03"}, > + {.compatible = "sharp,ls013b7dh05"}, > + {.compatible = "sharp,ls018b7dh02"}, > + {.compatible = "sharp,ls027b7dh01"}, > + {.compatible = "sharp,ls027b7dh01a"}, > + {.compatible = "sharp,ls032b7dd02"}, > + {.compatible = "sharp,ls044q7dh01"}, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, sharp_memory_of_match); > + > +static const struct drm_display_mode sharp_memory_ls010b7dh04_mode = { > + DRM_SIMPLE_MODE(128, 128, 18, 18), > +}; > + > +static const struct drm_display_mode sharp_memory_ls011b7dh03_mode = { > + DRM_SIMPLE_MODE(160, 68, 25, 10), > +}; > + > +static const struct drm_display_mode sharp_memory_ls012b7dd01_mode = { > + DRM_SIMPLE_MODE(184, 38, 29, 6), > +}; > + > +static const struct drm_display_mode sharp_memory_ls013b7dh03_mode = { > + DRM_SIMPLE_MODE(128, 128, 23, 23), > +}; > + > +static const struct drm_display_mode sharp_memory_ls013b7dh05_mode = { > + DRM_SIMPLE_MODE(144, 168, 20, 24), > +}; > + > +static const struct drm_display_mode sharp_memory_ls018b7dh02_mode = { > + DRM_SIMPLE_MODE(230, 303, 27, 36), > +}; > + > +static const struct drm_display_mode sharp_memory_ls027b7dh01_mode = { > + DRM_SIMPLE_MODE(400, 240, 58, 35), > +}; > + > +static const struct drm_display_mode sharp_memory_ls032b7dd02_mode = { > + DRM_SIMPLE_MODE(336, 536, 42, 68), > +}; > + > +static const struct drm_display_mode sharp_memory_ls044q7dh01_mode = { > + DRM_SIMPLE_MODE(320, 240, 89, 67), > +}; > + > +static const uint32_t sharp_memory_formats[] = { > + DRM_FORMAT_XRGB8888, > +}; > + > +static inline enum sharp_memory_model sharp_memory_model_from_device_id(struct spi_device *spi) > +{ > + const struct spi_device_id *spi_id = spi_get_device_id(spi); > + > + return (enum sharp_memory_model)spi_id->driver_data; No, use appropriate wrapper, do not re-implement spi_get_device_match_data. > +} > + > +static int sharp_memory_pipe_init(struct drm_device *dev, > + struct sharp_memory_device *smd, > + const uint32_t *formats, unsigned int format_count, > + const uint64_t *format_modifiers) > +{ Best regards, Krzysztof