#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 <linux/iosys-map.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_generic.h>
#include <drm/drm_format_helper.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_mipi_dbi.h>
#include <drm/drm_modes.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_rect.h>
#include <drm/drm_fourcc.h>

#define ST7567_PAGES 8
#define ST7567_WIDTH 128

struct st7567_device {
	struct mipi_dbi_dev dbidev;
	struct gpio_desc *backlight;
};

static inline struct st7567_device *to_st7567_device(struct drm_device *drm)
{
	return container_of(drm_to_mipi_dbi_dev(drm), struct st7567_device, dbidev);
}

static const uint32_t st7567_formats[] = {
	DRM_FORMAT_XRGB8888,
};

static void st7567_xrgb8888_to_bw(u8 *dst, struct iosys_map *src, 
				 struct drm_framebuffer *fb, struct drm_rect *rect)
{
	unsigned int x, y;
	u32 pixel;

	memset(dst, 0, ST7567_PAGES * ST7567_WIDTH);

	for (y = rect->y1; y < rect->y2; y++) {
		for (x = rect->x1; x < rect->x2; x++) {
			pixel = iosys_map_rd(src, y * fb->pitches[0] + x * 4, u32);
			if (pixel & 0x00FFFFFF)
				dst[(y / 8) * ST7567_WIDTH + x] |= BIT(y % 8);
		}
	}
}

static void st7567_fb_dirty(struct iosys_map *src, struct drm_framebuffer *fb, 
			    struct drm_rect *rect)
{
	struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(fb->dev);
	struct mipi_dbi *dbi = &dbidev->dbi;
	u8 *dst = (u8 *)dbidev->tx_buf;
	int i;

	if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE))
		return;

	st7567_xrgb8888_to_bw(dst, src, fb, rect);

	for (i = 0; i < ST7567_PAGES; i++) {
		mipi_dbi_command(dbi, 0xB0 + i); // Set Page Address
		mipi_dbi_command(dbi, 0x00);     // Set Column Address LSB
		mipi_dbi_command(dbi, 0x10);     // Set Column Address MSB
		mipi_dbi_command_buf(dbi, 0, dst + (i * ST7567_WIDTH), ST7567_WIDTH);
	}

	drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
}

static void st7567_pipe_update(struct drm_simple_display_pipe *pipe,
			       struct drm_plane_state *old_state)
{
	struct drm_plane_state *state = pipe->plane.state;
	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state);
	struct drm_framebuffer *fb = state->fb;
	struct drm_rect rect;
	int idx;

	if (!fb || !pipe->crtc.state->active)
		return;

	if (!drm_dev_enter(fb->dev, &idx))
		return;

	if (drm_atomic_helper_damage_merged(old_state, state, &rect))
		st7567_fb_dirty(&shadow_plane_state->data[0], fb, &rect);

	drm_dev_exit(idx);
}

static void st7567_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 st7567_device *stdev = to_st7567_device(pipe->crtc.dev);
	struct mipi_dbi *dbi = &dbidev->dbi;
	bool invert = device_property_read_bool(dbidev->drm.dev, "invert");
	int idx;

	if (!drm_dev_enter(pipe->crtc.dev, &idx)) return;

	mipi_dbi_poweron_reset(dbidev);
	msleep(50);

	mipi_dbi_command(dbi, 0xE2);	/* Soft Reset */
	mipi_dbi_command(dbi, 0xAE);	/* Display OFF */

	mipi_dbi_command(dbi, 0xA2);	/* LCD Bias 1/9 */
	mipi_dbi_command(dbi, 0xA0);	/* ADC Normal */
	mipi_dbi_command(dbi, 0xC8);	/* COM Reverse */
	mipi_dbi_command(dbi, (invert) ? 0xA7 : 0xA6); /* Inverse colors or not */

	mipi_dbi_command(dbi, 0x2C); msleep(20);	/* Booster ON */
	mipi_dbi_command(dbi, 0x2E); msleep(20);	/* Regulator ON */
	mipi_dbi_command(dbi, 0x2F); msleep(20);	/* Follower ON */

	mipi_dbi_command(dbi, 0x24);	/* V0 Voltage Resistor Ratio (0x20..0x27) */
	mipi_dbi_command(dbi, 0x81);	/* Set Contrast */
	mipi_dbi_command(dbi, 0x28);	/* Contrast value */

	mipi_dbi_command(dbi, 0x40);	/* Start Line 0 */
	mipi_dbi_command(dbi, 0xA4);	/* Normal Pixels (не A5!) */
	mipi_dbi_command(dbi, 0xAF);	/* Display ON */
	msleep(100);

	if (stdev->backlight) gpiod_set_value(stdev->backlight, 1);

	if (plane_state->fb) {
		struct drm_shadow_plane_state *shadow = to_drm_shadow_plane_state(plane_state);
		struct drm_rect rect = { 0, 0, plane_state->fb->width, plane_state->fb->height };
		st7567_fb_dirty(&shadow->data[0], plane_state->fb, &rect);
	}

	drm_dev_exit(idx);
}

static void st7567_pipe_disable(struct drm_simple_display_pipe *pipe)
{
	struct st7567_device *stdev = to_st7567_device(pipe->crtc.dev);
	mipi_dbi_command(&stdev->dbidev.dbi, 0xAE);
	/* Гасим подсветку при засыпании */
	if (stdev->backlight) gpiod_set_value(stdev->backlight, 0);
}

/* Атрибут Sysfs для ручного управления подсветкой */
static ssize_t backlight_pwr_store(struct device *dev, struct device_attribute *attr, 
				  const char *buf, size_t count)
{
	struct drm_device *drm = dev_get_drvdata(dev);
	struct st7567_device *stdev = to_st7567_device(drm);
	unsigned long val;

	if (kstrtoul(buf, 10, &val)) return -EINVAL;
	if (stdev->backlight) gpiod_set_value(stdev->backlight, !!val);
	return count;
}
static DEVICE_ATTR_WO(backlight_pwr);

static const struct drm_simple_display_pipe_funcs st7567_pipe_funcs = {
	.enable = st7567_pipe_enable,
	.disable = st7567_pipe_disable,
	.update = st7567_pipe_update,
	.begin_fb_access = mipi_dbi_pipe_begin_fb_access,
	.end_fb_access   = mipi_dbi_pipe_end_fb_access,
	.reset_plane     = mipi_dbi_pipe_reset_plane,
	.duplicate_plane_state = mipi_dbi_pipe_duplicate_plane_state,
	.destroy_plane_state   = mipi_dbi_pipe_destroy_plane_state,
};

static const struct drm_display_mode st7567_mode = {
	DRM_SIMPLE_MODE(128, 64, 34, 24),
};

DEFINE_DRM_GEM_DMA_FOPS(st7567_fops);

static const struct drm_driver st7567_driver = {
	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
	.fops			= &st7567_fops,
	DRM_GEM_DMA_DRIVER_OPS_VMAP,
	.debugfs_init		= mipi_dbi_debugfs_init,
	.name			= "st7567",
	.desc			= "ST7567",
	.date			= "20240501",
	.major			= 1,
	.minor			= 0,
};

static int st7567_probe(struct spi_device *spi)
{
	struct device *dev = &spi->dev;
	struct st7567_device *stdev;
	struct mipi_dbi *dbi;
	struct gpio_desc *dc;
	int ret;

	stdev = devm_drm_dev_alloc(dev, &st7567_driver, struct st7567_device, dbidev.drm);
	if (IS_ERR(stdev)) return PTR_ERR(stdev);

	dbi = &stdev->dbidev.dbi;
	dbi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
	dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
	/* Наш новый пин подсветки */
	stdev->backlight = devm_gpiod_get_optional(dev, "bl", GPIOD_OUT_HIGH);

	ret = mipi_dbi_spi_init(spi, dbi, dc);
	if (ret) return ret;

	ret = mipi_dbi_dev_init_with_formats(&stdev->dbidev, &st7567_pipe_funcs, 
					     st7567_formats, ARRAY_SIZE(st7567_formats),
					     &st7567_mode, 0, 1024);
	if (ret) return ret;

	drm_mode_config_reset(&stdev->dbidev.drm);
	ret = drm_dev_register(&stdev->dbidev.drm, 0);
	if (ret) return ret;

	/* Создаем файл управления в sysfs */
	device_create_file(dev, &dev_attr_backlight_pwr);

	spi_set_drvdata(spi, &stdev->dbidev.drm);
	drm_fbdev_generic_setup(&stdev->dbidev.drm, 0);

	return 0;
}

static void st7567_remove(struct spi_device *spi)
{
	struct drm_device *drm = spi_get_drvdata(spi);
	device_remove_file(drm->dev, &dev_attr_backlight_pwr);
	drm_dev_unplug(drm);
	drm_atomic_helper_shutdown(drm);
}

static const struct of_device_id st7567_of_match[] = {
	{ .compatible = "sitronix,st7567" },
	{ }
};
MODULE_DEVICE_TABLE(of, st7567_of_match);

static const struct spi_device_id st7567_id[] = {
	{ "st7567", 0 },
	{ }
};
MODULE_DEVICE_TABLE(spi, st7567_id);

static struct spi_driver st7567_spi_driver = {
	.driver = {
		.name = "st7567",
		.of_match_table = st7567_of_match,
	},
	.id_table = st7567_id,
	.probe = st7567_probe,
	.remove = st7567_remove,
};

module_spi_driver(st7567_spi_driver);

MODULE_DESCRIPTION("ST7567 DRM Driver");
MODULE_AUTHOR("Fox_exe");
MODULE_LICENSE("GPL");
