On Thu, Jun 08, 2017 at 05:14:35PM +0200, Noralf Trønnes wrote: > This adds support for the Pervasive Displays RePaper branded displays. > The controller code is taken from the userspace driver available > through repaper.org. Only the V231 film is supported since the others > are EOL. > > Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> I took a look, and besides the tinydrm issues we've capture already this all looks reasonable. Acked-by: Daniel Vetter <daniel.vetter@xxxxxxxx> Yes I'm trying once more to sign you up for drm-misc commit rights, hence not pushing ... -Daniel > --- > MAINTAINERS | 6 + > drivers/gpu/drm/tinydrm/Kconfig | 12 + > drivers/gpu/drm/tinydrm/Makefile | 1 + > drivers/gpu/drm/tinydrm/repaper.c | 1095 +++++++++++++++++++++++++++++++++++++ > 4 files changed, 1114 insertions(+) > create mode 100644 drivers/gpu/drm/tinydrm/repaper.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 757d487..800afa2 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -4446,6 +4446,12 @@ M: Dave Airlie <airlied@xxxxxxxxxx> > S: Odd Fixes > F: drivers/gpu/drm/mgag200/ > > +DRM DRIVER FOR PERVASIVE DISPLAYS REPAPER PANELS > +M: Noralf Trønnes <noralf@xxxxxxxxxxx> > +S: Maintained > +F: drivers/gpu/drm/tinydrm/repaper.c > +F: Documentation/devicetree/bindings/display/repaper.txt > + > DRM DRIVER FOR RAGE 128 VIDEO CARDS > S: Orphan / Obsolete > F: drivers/gpu/drm/r128/ > diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig > index 3504c53..9596e44 100644 > --- a/drivers/gpu/drm/tinydrm/Kconfig > +++ b/drivers/gpu/drm/tinydrm/Kconfig > @@ -19,3 +19,15 @@ config TINYDRM_MI0283QT > help > DRM driver for the Multi-Inno MI0283QT display panel > If M is selected the module will be called mi0283qt. > + > +config TINYDRM_REPAPER > + tristate "DRM support for Pervasive Displays RePaper panels (V231)" > + depends on DRM_TINYDRM && SPI > + help > + DRM driver for the following Pervasive Displays panels: > + 1.44" TFT EPD Panel (E1144CS021) > + 1.90" TFT EPD Panel (E1190CS021) > + 2.00" TFT EPD Panel (E2200CS021) > + 2.71" TFT EPD Panel (E2271CS021) > + > + If M is selected the module will be called repaper. > diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile > index 7a3604c..95bb4d4 100644 > --- a/drivers/gpu/drm/tinydrm/Makefile > +++ b/drivers/gpu/drm/tinydrm/Makefile > @@ -5,3 +5,4 @@ obj-$(CONFIG_TINYDRM_MIPI_DBI) += mipi-dbi.o > > # Displays > obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o > +obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o > diff --git a/drivers/gpu/drm/tinydrm/repaper.c b/drivers/gpu/drm/tinydrm/repaper.c > new file mode 100644 > index 0000000..3343d3f > --- /dev/null > +++ b/drivers/gpu/drm/tinydrm/repaper.c > @@ -0,0 +1,1095 @@ > +/* > + * DRM driver for Pervasive Displays RePaper branded e-ink panels > + * > + * Copyright 2013-2017 Pervasive Displays, Inc. > + * Copyright 2017 Noralf Trønnes > + * > + * The driver supports: > + * Material Film: Aurora Mb (V231) > + * Driver IC: G2 (eTC) > + * > + * The controller code was taken from the userspace driver: > + * https://github.com/repaper/gratis > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + */ > + > +#include <linux/delay.h> > +#include <linux/gpio/consumer.h> > +#include <linux/module.h> > +#include <linux/of_device.h> > +#include <linux/sched/clock.h> > +#include <linux/spi/spi.h> > +#include <linux/thermal.h> > + > +#include <drm/tinydrm/tinydrm.h> > +#include <drm/tinydrm/tinydrm-helpers.h> > + > +#define REPAPER_RID_G2_COG_ID 0x12 > + > +enum repaper_model { > + E1144CS021 = 1, > + E1190CS021, > + E2200CS021, > + E2271CS021, > +}; > + > +enum repaper_stage { /* Image pixel -> Display pixel */ > + REPAPER_COMPENSATE, /* B -> W, W -> B (Current Image) */ > + REPAPER_WHITE, /* B -> N, W -> W (Current Image) */ > + REPAPER_INVERSE, /* B -> N, W -> B (New Image) */ > + REPAPER_NORMAL /* B -> B, W -> W (New Image) */ > +}; > + > +enum repaper_epd_border_byte { > + REPAPER_BORDER_BYTE_NONE, > + REPAPER_BORDER_BYTE_ZERO, > + REPAPER_BORDER_BYTE_SET, > +}; > + > +struct repaper_epd { > + struct tinydrm_device tinydrm; > + struct spi_device *spi; > + > + struct gpio_desc *panel_on; > + struct gpio_desc *border; > + struct gpio_desc *discharge; > + struct gpio_desc *reset; > + struct gpio_desc *busy; > + > + struct thermal_zone_device *thermal; > + > + unsigned int height; > + unsigned int width; > + unsigned int bytes_per_scan; > + const u8 *channel_select; > + unsigned int stage_time; > + unsigned int factored_stage_time; > + bool middle_scan; > + bool pre_border_byte; > + enum repaper_epd_border_byte border_byte; > + > + u8 *line_buffer; > + void *current_frame; > + > + bool enabled; > + bool cleared; > + bool partial; > +}; > + > +static inline struct repaper_epd * > +epd_from_tinydrm(struct tinydrm_device *tdev) > +{ > + return container_of(tdev, struct repaper_epd, tinydrm); > +} > + > +static int repaper_spi_transfer(struct spi_device *spi, u8 header, > + const void *tx, void *rx, size_t len) > +{ > + void *txbuf = NULL, *rxbuf = NULL; > + struct spi_transfer tr[2] = {}; > + u8 *headerbuf; > + int ret; > + > + headerbuf = kmalloc(1, GFP_KERNEL); > + if (!headerbuf) > + return -ENOMEM; > + > + headerbuf[0] = header; > + tr[0].tx_buf = headerbuf; > + tr[0].len = 1; > + > + /* Stack allocated tx? */ > + if (tx && len <= 32) { > + txbuf = kmalloc(len, GFP_KERNEL); > + if (!txbuf) { > + ret = -ENOMEM; > + goto out_free; > + } > + memcpy(txbuf, tx, len); > + } > + > + if (rx) { > + rxbuf = kmalloc(len, GFP_KERNEL); > + if (!rxbuf) { > + ret = -ENOMEM; > + goto out_free; > + } > + } > + > + tr[1].tx_buf = txbuf ? txbuf : tx; > + tr[1].rx_buf = rxbuf; > + tr[1].len = len; > + > + ndelay(80); > + ret = spi_sync_transfer(spi, tr, 2); > + if (rx && !ret) > + memcpy(rx, rxbuf, len); > + > +out_free: > + kfree(headerbuf); > + kfree(txbuf); > + kfree(rxbuf); > + > + return ret; > +} > + > +static int repaper_write_buf(struct spi_device *spi, u8 reg, > + const u8 *buf, size_t len) > +{ > + int ret; > + > + ret = repaper_spi_transfer(spi, 0x70, ®, NULL, 1); > + if (ret) > + return ret; > + > + return repaper_spi_transfer(spi, 0x72, buf, NULL, len); > +} > + > +static int repaper_write_val(struct spi_device *spi, u8 reg, u8 val) > +{ > + return repaper_write_buf(spi, reg, &val, 1); > +} > + > +static int repaper_read_val(struct spi_device *spi, u8 reg) > +{ > + int ret; > + u8 val; > + > + ret = repaper_spi_transfer(spi, 0x70, ®, NULL, 1); > + if (ret) > + return ret; > + > + ret = repaper_spi_transfer(spi, 0x73, NULL, &val, 1); > + > + return ret ? ret : val; > +} > + > +static int repaper_read_id(struct spi_device *spi) > +{ > + int ret; > + u8 id; > + > + ret = repaper_spi_transfer(spi, 0x71, NULL, &id, 1); > + > + return ret ? ret : id; > +} > + > +static void repaper_spi_mosi_low(struct spi_device *spi) > +{ > + const u8 buf[1] = { 0 }; > + > + spi_write(spi, buf, 1); > +} > + > +/* pixels on display are numbered from 1 so even is actually bits 1,3,5,... */ > +static void repaper_even_pixels(struct repaper_epd *epd, u8 **pp, > + const u8 *data, u8 fixed_value, const u8 *mask, > + enum repaper_stage stage) > +{ > + unsigned int b; > + > + for (b = 0; b < (epd->width / 8); b++) { > + if (data) { > + u8 pixels = data[b] & 0xaa; > + u8 pixel_mask = 0xff; > + u8 p1, p2, p3, p4; > + > + if (mask) { > + pixel_mask = (mask[b] ^ pixels) & 0xaa; > + pixel_mask |= pixel_mask >> 1; > + } > + > + switch (stage) { > + case REPAPER_COMPENSATE: /* B -> W, W -> B (Current) */ > + pixels = 0xaa | ((pixels ^ 0xaa) >> 1); > + break; > + case REPAPER_WHITE: /* B -> N, W -> W (Current) */ > + pixels = 0x55 + ((pixels ^ 0xaa) >> 1); > + break; > + case REPAPER_INVERSE: /* B -> N, W -> B (New) */ > + pixels = 0x55 | (pixels ^ 0xaa); > + break; > + case REPAPER_NORMAL: /* B -> B, W -> W (New) */ > + pixels = 0xaa | (pixels >> 1); > + break; > + } > + > + pixels = (pixels & pixel_mask) | (~pixel_mask & 0x55); > + p1 = (pixels >> 6) & 0x03; > + p2 = (pixels >> 4) & 0x03; > + p3 = (pixels >> 2) & 0x03; > + p4 = (pixels >> 0) & 0x03; > + pixels = (p1 << 0) | (p2 << 2) | (p3 << 4) | (p4 << 6); > + *(*pp)++ = pixels; > + } else { > + *(*pp)++ = fixed_value; > + } > + } > +} > + > +/* pixels on display are numbered from 1 so odd is actually bits 0,2,4,... */ > +static void repaper_odd_pixels(struct repaper_epd *epd, u8 **pp, > + const u8 *data, u8 fixed_value, const u8 *mask, > + enum repaper_stage stage) > +{ > + unsigned int b; > + > + for (b = epd->width / 8; b > 0; b--) { > + if (data) { > + u8 pixels = data[b - 1] & 0x55; > + u8 pixel_mask = 0xff; > + > + if (mask) { > + pixel_mask = (mask[b - 1] ^ pixels) & 0x55; > + pixel_mask |= pixel_mask << 1; > + } > + > + switch (stage) { > + case REPAPER_COMPENSATE: /* B -> W, W -> B (Current) */ > + pixels = 0xaa | (pixels ^ 0x55); > + break; > + case REPAPER_WHITE: /* B -> N, W -> W (Current) */ > + pixels = 0x55 + (pixels ^ 0x55); > + break; > + case REPAPER_INVERSE: /* B -> N, W -> B (New) */ > + pixels = 0x55 | ((pixels ^ 0x55) << 1); > + break; > + case REPAPER_NORMAL: /* B -> B, W -> W (New) */ > + pixels = 0xaa | pixels; > + break; > + } > + > + pixels = (pixels & pixel_mask) | (~pixel_mask & 0x55); > + *(*pp)++ = pixels; > + } else { > + *(*pp)++ = fixed_value; > + } > + } > +} > + > +/* interleave bits: (byte)76543210 -> (16 bit).7.6.5.4.3.2.1 */ > +static inline u16 repaper_interleave_bits(u16 value) > +{ > + value = (value | (value << 4)) & 0x0f0f; > + value = (value | (value << 2)) & 0x3333; > + value = (value | (value << 1)) & 0x5555; > + > + return value; > +} > + > +/* pixels on display are numbered from 1 */ > +static void repaper_all_pixels(struct repaper_epd *epd, u8 **pp, > + const u8 *data, u8 fixed_value, const u8 *mask, > + enum repaper_stage stage) > +{ > + unsigned int b; > + > + for (b = epd->width / 8; b > 0; b--) { > + if (data) { > + u16 pixels = repaper_interleave_bits(data[b - 1]); > + u16 pixel_mask = 0xffff; > + > + if (mask) { > + pixel_mask = repaper_interleave_bits(mask[b - 1]); > + > + pixel_mask = (pixel_mask ^ pixels) & 0x5555; > + pixel_mask |= pixel_mask << 1; > + } > + > + switch (stage) { > + case REPAPER_COMPENSATE: /* B -> W, W -> B (Current) */ > + pixels = 0xaaaa | (pixels ^ 0x5555); > + break; > + case REPAPER_WHITE: /* B -> N, W -> W (Current) */ > + pixels = 0x5555 + (pixels ^ 0x5555); > + break; > + case REPAPER_INVERSE: /* B -> N, W -> B (New) */ > + pixels = 0x5555 | ((pixels ^ 0x5555) << 1); > + break; > + case REPAPER_NORMAL: /* B -> B, W -> W (New) */ > + pixels = 0xaaaa | pixels; > + break; > + } > + > + pixels = (pixels & pixel_mask) | (~pixel_mask & 0x5555); > + *(*pp)++ = pixels >> 8; > + *(*pp)++ = pixels; > + } else { > + *(*pp)++ = fixed_value; > + *(*pp)++ = fixed_value; > + } > + } > +} > + > +/* output one line of scan and data bytes to the display */ > +static void repaper_one_line(struct repaper_epd *epd, unsigned int line, > + const u8 *data, u8 fixed_value, const u8 *mask, > + enum repaper_stage stage) > +{ > + u8 *p = epd->line_buffer; > + unsigned int b; > + > + repaper_spi_mosi_low(epd->spi); > + > + if (epd->pre_border_byte) > + *p++ = 0x00; > + > + if (epd->middle_scan) { > + /* data bytes */ > + repaper_odd_pixels(epd, &p, data, fixed_value, mask, stage); > + > + /* scan line */ > + for (b = epd->bytes_per_scan; b > 0; b--) { > + if (line / 4 == b - 1) > + *p++ = 0x03 << (2 * (line & 0x03)); > + else > + *p++ = 0x00; > + } > + > + /* data bytes */ > + repaper_even_pixels(epd, &p, data, fixed_value, mask, stage); > + } else { > + /* > + * even scan line, but as lines on display are numbered from 1, > + * line: 1,3,5,... > + */ > + for (b = 0; b < epd->bytes_per_scan; b++) { > + if (0 != (line & 0x01) && line / 8 == b) > + *p++ = 0xc0 >> (line & 0x06); > + else > + *p++ = 0x00; > + } > + > + /* data bytes */ > + repaper_all_pixels(epd, &p, data, fixed_value, mask, stage); > + > + /* > + * odd scan line, but as lines on display are numbered from 1, > + * line: 0,2,4,6,... > + */ > + for (b = epd->bytes_per_scan; b > 0; b--) { > + if (0 == (line & 0x01) && line / 8 == b - 1) > + *p++ = 0x03 << (line & 0x06); > + else > + *p++ = 0x00; > + } > + } > + > + switch (epd->border_byte) { > + case REPAPER_BORDER_BYTE_NONE: > + break; > + > + case REPAPER_BORDER_BYTE_ZERO: > + *p++ = 0x00; > + break; > + > + case REPAPER_BORDER_BYTE_SET: > + switch (stage) { > + case REPAPER_COMPENSATE: > + case REPAPER_WHITE: > + case REPAPER_INVERSE: > + *p++ = 0x00; > + break; > + case REPAPER_NORMAL: > + *p++ = 0xaa; > + break; > + } > + break; > + } > + > + repaper_write_buf(epd->spi, 0x0a, epd->line_buffer, > + p - epd->line_buffer); > + > + /* Output data to panel */ > + repaper_write_val(epd->spi, 0x02, 0x07); > + > + repaper_spi_mosi_low(epd->spi); > +} > + > +static void repaper_frame_fixed(struct repaper_epd *epd, u8 fixed_value, > + enum repaper_stage stage) > +{ > + unsigned int line; > + > + for (line = 0; line < epd->height; line++) > + repaper_one_line(epd, line, NULL, fixed_value, NULL, stage); > +} > + > +static void repaper_frame_data(struct repaper_epd *epd, const u8 *image, > + const u8 *mask, enum repaper_stage stage) > +{ > + unsigned int line; > + > + if (!mask) { > + for (line = 0; line < epd->height; line++) { > + repaper_one_line(epd, line, > + &image[line * (epd->width / 8)], > + 0, NULL, stage); > + } > + } else { > + for (line = 0; line < epd->height; line++) { > + size_t n = line * epd->width / 8; > + > + repaper_one_line(epd, line, &image[n], 0, &mask[n], > + stage); > + } > + } > +} > + > +static void repaper_frame_fixed_repeat(struct repaper_epd *epd, u8 fixed_value, > + enum repaper_stage stage) > +{ > + u64 start = local_clock(); > + u64 end = start + (epd->factored_stage_time * 1000 * 1000); > + > + do { > + repaper_frame_fixed(epd, fixed_value, stage); > + } while (local_clock() < end); > +} > + > +static void repaper_frame_data_repeat(struct repaper_epd *epd, const u8 *image, > + const u8 *mask, enum repaper_stage stage) > +{ > + u64 start = local_clock(); > + u64 end = start + (epd->factored_stage_time * 1000 * 1000); > + > + do { > + repaper_frame_data(epd, image, mask, stage); > + } while (local_clock() < end); > +} > + > +static void repaper_get_temperature(struct repaper_epd *epd) > +{ > + int ret, temperature = 0; > + unsigned int factor10x; > + > + if (!epd->thermal) > + return; > + > + ret = thermal_zone_get_temp(epd->thermal, &temperature); > + if (ret) { > + dev_err(&epd->spi->dev, "Failed to get temperature (%d)\n", > + ret); > + return; > + } > + > + temperature /= 1000; > + > + if (temperature <= -10) > + factor10x = 170; > + else if (temperature <= -5) > + factor10x = 120; > + else if (temperature <= 5) > + factor10x = 80; > + else if (temperature <= 10) > + factor10x = 40; > + else if (temperature <= 15) > + factor10x = 30; > + else if (temperature <= 20) > + factor10x = 20; > + else if (temperature <= 40) > + factor10x = 10; > + else > + factor10x = 7; > + > + epd->factored_stage_time = epd->stage_time * factor10x / 10; > +} > + > +static void repaper_gray8_to_mono_reversed(u8 *buf, u32 width, u32 height) > +{ > + u8 *gray8 = buf, *mono = buf; > + int y, xb, i; > + > + for (y = 0; y < height; y++) > + for (xb = 0; xb < width / 8; xb++) { > + u8 byte = 0x00; > + > + for (i = 0; i < 8; i++) { > + int x = xb * 8 + i; > + > + byte >>= 1; > + if (gray8[y * width + x] >> 7) > + byte |= BIT(7); > + } > + *mono++ = byte; > + } > +} > + > +static int repaper_fb_dirty(struct drm_framebuffer *fb, > + struct drm_file *file_priv, > + unsigned int flags, unsigned int color, > + struct drm_clip_rect *clips, > + unsigned int num_clips) > +{ > + struct tinydrm_device *tdev = fb->dev->dev_private; > + struct repaper_epd *epd = epd_from_tinydrm(tdev); > + u8 *buf = NULL; > + int ret = 0; > + > + mutex_lock(&tdev->dirty_lock); > + > + if (!epd->enabled) > + goto out_unlock; > + > + /* fbdev can flush even when we're not interested */ > + if (tdev->pipe.plane.fb != fb) > + goto out_unlock; > + > + repaper_get_temperature(epd); > + > + DRM_DEBUG("Flushing [FB:%d] st=%ums\n", fb->base.id, > + epd->factored_stage_time); > + > + buf = kmalloc(fb->width * fb->height, GFP_KERNEL); > + if (!buf) { > + ret = -ENOMEM; > + goto out_unlock; > + } > + > + ret = tinydrm_xrgb8888_to_gray8(buf, fb); > + if (ret) > + goto out_unlock; > + > + repaper_gray8_to_mono_reversed(buf, fb->width, fb->height); > + > + if (epd->partial) { > + repaper_frame_data_repeat(epd, buf, epd->current_frame, > + REPAPER_NORMAL); > + } else if (epd->cleared) { > + repaper_frame_data_repeat(epd, epd->current_frame, NULL, > + REPAPER_COMPENSATE); > + repaper_frame_data_repeat(epd, epd->current_frame, NULL, > + REPAPER_WHITE); > + repaper_frame_data_repeat(epd, buf, NULL, REPAPER_INVERSE); > + repaper_frame_data_repeat(epd, buf, NULL, REPAPER_NORMAL); > + > + epd->partial = true; > + } else { > + /* Clear display (anything -> white) */ > + repaper_frame_fixed_repeat(epd, 0xff, REPAPER_COMPENSATE); > + repaper_frame_fixed_repeat(epd, 0xff, REPAPER_WHITE); > + repaper_frame_fixed_repeat(epd, 0xaa, REPAPER_INVERSE); > + repaper_frame_fixed_repeat(epd, 0xaa, REPAPER_NORMAL); > + > + /* Assuming a clear (white) screen output an image */ > + repaper_frame_fixed_repeat(epd, 0xaa, REPAPER_COMPENSATE); > + repaper_frame_fixed_repeat(epd, 0xaa, REPAPER_WHITE); > + repaper_frame_data_repeat(epd, buf, NULL, REPAPER_INVERSE); > + repaper_frame_data_repeat(epd, buf, NULL, REPAPER_NORMAL); > + > + epd->cleared = true; > + epd->partial = true; > + } > + > + memcpy(epd->current_frame, buf, fb->width * fb->height / 8); > + > + /* > + * An extra frame write is needed if pixels are set in the bottom line, > + * or else grey lines rises up from the pixels > + */ > + if (epd->pre_border_byte) { > + unsigned int x; > + > + for (x = 0; x < (fb->width / 8); x++) > + if (buf[x + (fb->width * (fb->height - 1) / 8)]) { > + repaper_frame_data_repeat(epd, buf, > + epd->current_frame, > + REPAPER_NORMAL); > + break; > + } > + } > + > +out_unlock: > + mutex_unlock(&tdev->dirty_lock); > + > + if (ret) > + dev_err(fb->dev->dev, "Failed to update display (%d)\n", ret); > + kfree(buf); > + > + return ret; > +} > + > +static const struct drm_framebuffer_funcs repaper_fb_funcs = { > + .destroy = drm_fb_cma_destroy, > + .create_handle = drm_fb_cma_create_handle, > + .dirty = repaper_fb_dirty, > +}; > + > +static void power_off(struct repaper_epd *epd) > +{ > + /* Turn off power and all signals */ > + gpiod_set_value_cansleep(epd->reset, 0); > + gpiod_set_value_cansleep(epd->panel_on, 0); > + if (epd->border) > + gpiod_set_value_cansleep(epd->border, 0); > + > + /* Ensure SPI MOSI and CLOCK are Low before CS Low */ > + repaper_spi_mosi_low(epd->spi); > + > + /* Discharge pulse */ > + gpiod_set_value_cansleep(epd->discharge, 1); > + msleep(150); > + gpiod_set_value_cansleep(epd->discharge, 0); > +} > + > +static void repaper_pipe_enable(struct drm_simple_display_pipe *pipe, > + struct drm_crtc_state *crtc_state) > +{ > + struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); > + struct repaper_epd *epd = epd_from_tinydrm(tdev); > + struct spi_device *spi = epd->spi; > + struct device *dev = &spi->dev; > + bool dc_ok = false; > + int i, ret; > + > + DRM_DEBUG_DRIVER("\n"); > + > + /* Power up sequence */ > + gpiod_set_value_cansleep(epd->reset, 0); > + gpiod_set_value_cansleep(epd->panel_on, 0); > + gpiod_set_value_cansleep(epd->discharge, 0); > + if (epd->border) > + gpiod_set_value_cansleep(epd->border, 0); > + repaper_spi_mosi_low(spi); > + usleep_range(5000, 10000); > + > + gpiod_set_value_cansleep(epd->panel_on, 1); > + /* > + * This delay comes from the repaper.org userspace driver, it's not > + * mentioned in the datasheet. > + */ > + usleep_range(10000, 15000); > + gpiod_set_value_cansleep(epd->reset, 1); > + if (epd->border) > + gpiod_set_value_cansleep(epd->border, 1); > + usleep_range(5000, 10000); > + gpiod_set_value_cansleep(epd->reset, 0); > + usleep_range(5000, 10000); > + gpiod_set_value_cansleep(epd->reset, 1); > + usleep_range(5000, 10000); > + > + /* Wait for COG to become ready */ > + for (i = 100; i > 0; i--) { > + if (!gpiod_get_value_cansleep(epd->busy)) > + break; > + > + usleep_range(10, 100); > + } > + > + if (!i) { > + dev_err(dev, "timeout waiting for panel to become ready.\n"); > + power_off(epd); > + return; > + } > + > + repaper_read_id(spi); > + ret = repaper_read_id(spi); > + if (ret != REPAPER_RID_G2_COG_ID) { > + if (ret < 0) > + dev_err(dev, "failed to read chip (%d)\n", ret); > + else > + dev_err(dev, "wrong COG ID 0x%02x\n", ret); > + power_off(epd); > + return; > + } > + > + /* Disable OE */ > + repaper_write_val(spi, 0x02, 0x40); > + > + ret = repaper_read_val(spi, 0x0f); > + if (ret < 0 || !(ret & 0x80)) { > + if (ret < 0) > + dev_err(dev, "failed to read chip (%d)\n", ret); > + else > + dev_err(dev, "panel is reported broken\n"); > + power_off(epd); > + return; > + } > + > + /* Power saving mode */ > + repaper_write_val(spi, 0x0b, 0x02); > + /* Channel select */ > + repaper_write_buf(spi, 0x01, epd->channel_select, 8); > + /* High power mode osc */ > + repaper_write_val(spi, 0x07, 0xd1); > + /* Power setting */ > + repaper_write_val(spi, 0x08, 0x02); > + /* Vcom level */ > + repaper_write_val(spi, 0x09, 0xc2); > + /* Power setting */ > + repaper_write_val(spi, 0x04, 0x03); > + /* Driver latch on */ > + repaper_write_val(spi, 0x03, 0x01); > + /* Driver latch off */ > + repaper_write_val(spi, 0x03, 0x00); > + usleep_range(5000, 10000); > + > + /* Start chargepump */ > + for (i = 0; i < 4; ++i) { > + /* Charge pump positive voltage on - VGH/VDL on */ > + repaper_write_val(spi, 0x05, 0x01); > + msleep(240); > + > + /* Charge pump negative voltage on - VGL/VDL on */ > + repaper_write_val(spi, 0x05, 0x03); > + msleep(40); > + > + /* Charge pump Vcom on - Vcom driver on */ > + repaper_write_val(spi, 0x05, 0x0f); > + msleep(40); > + > + /* check DC/DC */ > + ret = repaper_read_val(spi, 0x0f); > + if (ret < 0) { > + dev_err(dev, "failed to read chip (%d)\n", ret); > + power_off(epd); > + return; > + } > + > + if (ret & 0x40) { > + dc_ok = true; > + break; > + } > + } > + > + if (!dc_ok) { > + dev_err(dev, "dc/dc failed\n"); > + power_off(epd); > + return; > + } > + > + /* > + * Output enable to disable > + * The userspace driver sets this to 0x04, but the datasheet says 0x06 > + */ > + repaper_write_val(spi, 0x02, 0x04); > + > + epd->enabled = true; > + epd->partial = false; > +} > + > +static void repaper_pipe_disable(struct drm_simple_display_pipe *pipe) > +{ > + struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); > + struct repaper_epd *epd = epd_from_tinydrm(tdev); > + struct spi_device *spi = epd->spi; > + unsigned int line; > + > + DRM_DEBUG_DRIVER("\n"); > + > + mutex_lock(&tdev->dirty_lock); > + epd->enabled = false; > + mutex_unlock(&tdev->dirty_lock); > + > + /* Nothing frame */ > + for (line = 0; line < epd->height; line++) > + repaper_one_line(epd, 0x7fffu, NULL, 0x00, NULL, > + REPAPER_COMPENSATE); > + > + /* 2.7" */ > + if (epd->border) { > + /* Dummy line */ > + repaper_one_line(epd, 0x7fffu, NULL, 0x00, NULL, > + REPAPER_COMPENSATE); > + msleep(25); > + gpiod_set_value_cansleep(epd->border, 0); > + msleep(200); > + gpiod_set_value_cansleep(epd->border, 1); > + } else { > + /* Border dummy line */ > + repaper_one_line(epd, 0x7fffu, NULL, 0x00, NULL, > + REPAPER_NORMAL); > + msleep(200); > + } > + > + /* not described in datasheet */ > + repaper_write_val(spi, 0x0b, 0x00); > + /* Latch reset turn on */ > + repaper_write_val(spi, 0x03, 0x01); > + /* Power off charge pump Vcom */ > + repaper_write_val(spi, 0x05, 0x03); > + /* Power off charge pump neg voltage */ > + repaper_write_val(spi, 0x05, 0x01); > + msleep(120); > + /* Discharge internal */ > + repaper_write_val(spi, 0x04, 0x80); > + /* turn off all charge pumps */ > + repaper_write_val(spi, 0x05, 0x00); > + /* Turn off osc */ > + repaper_write_val(spi, 0x07, 0x01); > + msleep(50); > + > + power_off(epd); > +} > + > +static const struct drm_simple_display_pipe_funcs repaper_pipe_funcs = { > + .enable = repaper_pipe_enable, > + .disable = repaper_pipe_disable, > + .update = tinydrm_display_pipe_update, > + .prepare_fb = tinydrm_display_pipe_prepare_fb, > +}; > + > +static const uint32_t repaper_formats[] = { > + DRM_FORMAT_XRGB8888, > +}; > + > +static const struct drm_display_mode repaper_e1144cs021_mode = { > + TINYDRM_MODE(128, 96, 29, 22), > +}; > + > +static const u8 repaper_e1144cs021_cs[] = { 0x00, 0x00, 0x00, 0x00, > + 0x00, 0x0f, 0xff, 0x00 }; > + > +static const struct drm_display_mode repaper_e1190cs021_mode = { > + TINYDRM_MODE(144, 128, 36, 32), > +}; > + > +static const u8 repaper_e1190cs021_cs[] = { 0x00, 0x00, 0x00, 0x03, > + 0xfc, 0x00, 0x00, 0xff }; > + > +static const struct drm_display_mode repaper_e2200cs021_mode = { > + TINYDRM_MODE(200, 96, 46, 22), > +}; > + > +static const u8 repaper_e2200cs021_cs[] = { 0x00, 0x00, 0x00, 0x00, > + 0x01, 0xff, 0xe0, 0x00 }; > + > +static const struct drm_display_mode repaper_e2271cs021_mode = { > + TINYDRM_MODE(264, 176, 57, 38), > +}; > + > +static const u8 repaper_e2271cs021_cs[] = { 0x00, 0x00, 0x00, 0x7f, > + 0xff, 0xfe, 0x00, 0x00 }; > + > +DEFINE_DRM_GEM_CMA_FOPS(repaper_fops); > + > +static struct drm_driver repaper_driver = { > + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | > + DRIVER_ATOMIC, > + .fops = &repaper_fops, > + TINYDRM_GEM_DRIVER_OPS, > + .name = "repaper", > + .desc = "Pervasive Displays RePaper e-ink panels", > + .date = "20170405", > + .major = 1, > + .minor = 0, > +}; > + > +static const struct of_device_id repaper_of_match[] = { > + { .compatible = "pervasive,e1144cs021", .data = (void *)E1144CS021 }, > + { .compatible = "pervasive,e1190cs021", .data = (void *)E1190CS021 }, > + { .compatible = "pervasive,e2200cs021", .data = (void *)E2200CS021 }, > + { .compatible = "pervasive,e2271cs021", .data = (void *)E2271CS021 }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, repaper_of_match); > + > +static const struct spi_device_id repaper_id[] = { > + { "e1144cs021", E1144CS021 }, > + { "e1190cs021", E1190CS021 }, > + { "e2200cs021", E2200CS021 }, > + { "e2271cs021", E2271CS021 }, > + { }, > +}; > +MODULE_DEVICE_TABLE(spi, repaper_id); > + > +static int repaper_probe(struct spi_device *spi) > +{ > + const struct drm_display_mode *mode; > + const struct spi_device_id *spi_id; > + const struct of_device_id *match; > + struct device *dev = &spi->dev; > + struct tinydrm_device *tdev; > + enum repaper_model model; > + const char *thermal_zone; > + struct repaper_epd *epd; > + size_t line_buffer_size; > + int ret; > + > + match = of_match_device(repaper_of_match, dev); > + if (match) { > + model = (enum repaper_model)match->data; > + } else { > + spi_id = spi_get_device_id(spi); > + model = spi_id->driver_data; > + } > + > + /* The SPI device is used to allocate dma memory */ > + if (!dev->coherent_dma_mask) { > + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); > + if (ret) { > + dev_warn(dev, "Failed to set dma mask %d\n", ret); > + return ret; > + } > + } > + > + epd = devm_kzalloc(dev, sizeof(*epd), GFP_KERNEL); > + if (!epd) > + return -ENOMEM; > + > + epd->spi = spi; > + > + epd->panel_on = devm_gpiod_get(dev, "panel-on", GPIOD_OUT_LOW); > + if (IS_ERR(epd->panel_on)) { > + ret = PTR_ERR(epd->panel_on); > + if (ret != -EPROBE_DEFER) > + dev_err(dev, "Failed to get gpio 'panel-on'\n"); > + return ret; > + } > + > + epd->discharge = devm_gpiod_get(dev, "discharge", GPIOD_OUT_LOW); > + if (IS_ERR(epd->discharge)) { > + ret = PTR_ERR(epd->discharge); > + if (ret != -EPROBE_DEFER) > + dev_err(dev, "Failed to get gpio 'discharge'\n"); > + return ret; > + } > + > + epd->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); > + if (IS_ERR(epd->reset)) { > + ret = PTR_ERR(epd->reset); > + if (ret != -EPROBE_DEFER) > + dev_err(dev, "Failed to get gpio 'reset'\n"); > + return ret; > + } > + > + epd->busy = devm_gpiod_get(dev, "busy", GPIOD_IN); > + if (IS_ERR(epd->busy)) { > + ret = PTR_ERR(epd->busy); > + if (ret != -EPROBE_DEFER) > + dev_err(dev, "Failed to get gpio 'busy'\n"); > + return ret; > + } > + > + if (!device_property_read_string(dev, "pervasive,thermal-zone", > + &thermal_zone)) { > + epd->thermal = thermal_zone_get_zone_by_name(thermal_zone); > + if (IS_ERR(epd->thermal)) { > + dev_err(dev, "Failed to get thermal zone: %s\n", > + thermal_zone); > + return PTR_ERR(epd->thermal); > + } > + } > + > + switch (model) { > + case E1144CS021: > + mode = &repaper_e1144cs021_mode; > + epd->channel_select = repaper_e1144cs021_cs; > + epd->stage_time = 480; > + epd->bytes_per_scan = 96 / 4; > + epd->middle_scan = true; /* data-scan-data */ > + epd->pre_border_byte = false; > + epd->border_byte = REPAPER_BORDER_BYTE_ZERO; > + break; > + > + case E1190CS021: > + mode = &repaper_e1190cs021_mode; > + epd->channel_select = repaper_e1190cs021_cs; > + epd->stage_time = 480; > + epd->bytes_per_scan = 128 / 4 / 2; > + epd->middle_scan = false; /* scan-data-scan */ > + epd->pre_border_byte = false; > + epd->border_byte = REPAPER_BORDER_BYTE_SET; > + break; > + > + case E2200CS021: > + mode = &repaper_e2200cs021_mode; > + epd->channel_select = repaper_e2200cs021_cs; > + epd->stage_time = 480; > + epd->bytes_per_scan = 96 / 4; > + epd->middle_scan = true; /* data-scan-data */ > + epd->pre_border_byte = true; > + epd->border_byte = REPAPER_BORDER_BYTE_NONE; > + break; > + > + case E2271CS021: > + epd->border = devm_gpiod_get(dev, "border", GPIOD_OUT_LOW); > + if (IS_ERR(epd->border)) { > + ret = PTR_ERR(epd->border); > + if (ret != -EPROBE_DEFER) > + dev_err(dev, "Failed to get gpio 'border'\n"); > + return ret; > + } > + > + mode = &repaper_e2271cs021_mode; > + epd->channel_select = repaper_e2271cs021_cs; > + epd->stage_time = 630; > + epd->bytes_per_scan = 176 / 4; > + epd->middle_scan = true; /* data-scan-data */ > + epd->pre_border_byte = true; > + epd->border_byte = REPAPER_BORDER_BYTE_NONE; > + break; > + > + default: > + return -ENODEV; > + } > + > + epd->width = mode->hdisplay; > + epd->height = mode->vdisplay; > + epd->factored_stage_time = epd->stage_time; > + > + line_buffer_size = 2 * epd->width / 8 + epd->bytes_per_scan + 2; > + epd->line_buffer = devm_kzalloc(dev, line_buffer_size, GFP_KERNEL); > + if (!epd->line_buffer) > + return -ENOMEM; > + > + epd->current_frame = devm_kzalloc(dev, epd->width * epd->height / 8, > + GFP_KERNEL); > + if (!epd->current_frame) > + return -ENOMEM; > + > + tdev = &epd->tinydrm; > + > + ret = devm_tinydrm_init(dev, tdev, &repaper_fb_funcs, &repaper_driver); > + if (ret) > + return ret; > + > + ret = tinydrm_display_pipe_init(tdev, &repaper_pipe_funcs, > + DRM_MODE_CONNECTOR_VIRTUAL, > + repaper_formats, > + ARRAY_SIZE(repaper_formats), mode, 0); > + if (ret) > + return ret; > + > + drm_mode_config_reset(tdev->drm); > + > + ret = devm_tinydrm_register(tdev); > + if (ret) > + return ret; > + > + spi_set_drvdata(spi, tdev); > + > + DRM_DEBUG_DRIVER("Initialized %s:%s @%uMHz on minor %d\n", > + tdev->drm->driver->name, dev_name(dev), > + spi->max_speed_hz / 1000000, > + tdev->drm->primary->index); > + > + return 0; > +} > + > +static void repaper_shutdown(struct spi_device *spi) > +{ > + struct tinydrm_device *tdev = spi_get_drvdata(spi); > + > + tinydrm_shutdown(tdev); > +} > + > +static struct spi_driver repaper_spi_driver = { > + .driver = { > + .name = "repaper", > + .owner = THIS_MODULE, > + .of_match_table = repaper_of_match, > + }, > + .id_table = repaper_id, > + .probe = repaper_probe, > + .shutdown = repaper_shutdown, > +}; > +module_spi_driver(repaper_spi_driver); > + > +MODULE_DESCRIPTION("Pervasive Displays RePaper DRM driver"); > +MODULE_AUTHOR("Noralf Trønnes"); > +MODULE_LICENSE("GPL"); > -- > 2.7.4 > > _______________________________________________ > dri-devel mailing list > dri-devel@xxxxxxxxxxxxxxxxxxxxx > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel