Re: [PATCH 2/2] drm/tiny: Add driver for Sharp Memory LCD

From: Krzysztof Kozlowski
Date: Thu Jul 25 2024 - 02:09:11 EST


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