On 29.03.23 12:56, Philipp Zabel wrote: > Port helper functions for the panel-mipi-dbi driver from the Linux > kernel. > > Signed-off-by: Philipp Zabel <p.zabel@xxxxxxxxxxxxxx> > --- > drivers/video/mipi_dbi.c | 242 +++++++++++++++++++++++++++++++++++++++ > include/video/mipi_dbi.h | 63 ++++++++++ > 2 files changed, 305 insertions(+) > > diff --git a/drivers/video/mipi_dbi.c b/drivers/video/mipi_dbi.c > index 50d2fc4b29b9..9aa16abb1c9b 100644 > --- a/drivers/video/mipi_dbi.c > +++ b/drivers/video/mipi_dbi.c > @@ -13,6 +13,7 @@ > #include <gpiod.h> > #include <regulator.h> > #include <spi/spi.h> > +#include <video/backlight.h> > #include <video/mipi_dbi.h> > > #include <video/vpl.h> > @@ -196,6 +197,178 @@ int mipi_dbi_command_stackbuf(struct mipi_dbi *dbi, u8 cmd, const u8 *data, > } > EXPORT_SYMBOL(mipi_dbi_command_stackbuf); > > +/** > + * mipi_dbi_buf_copy - Copy a framebuffer, transforming it if necessary > + * @dst: The destination buffer > + * @info: The source framebuffer info > + * @swap: When true, swap MSB/LSB of 16-bit values > + * > + * Returns: > + * Zero on success, negative error code on failure. > + */ > +static void mipi_dbi_buf_copy(void *dst, struct fb_info *info, bool swap) > +{ > + u16 *src = (u16 *)info->screen_base; > + u16 *dst16 = dst; > + size_t len = info->xres * info->yres; > + int i; > + > + if (swap) { > + for (i = 0; i < len; i++) { > + *dst16++ = *src << 8 | *src >> 8; > + src++; > + } > + } else { > + memcpy(dst, src, len * 2); Do we need this? Why can't we send out framebuffer directly if there is no swapping to be done? > + } > +} > + > +static void mipi_dbi_set_window_address(struct mipi_dbi_dev *dbidev, > + unsigned int xs, unsigned int xe, > + unsigned int ys, unsigned int ye) > +{ > + struct mipi_dbi *dbi = &dbidev->dbi; > + > + xs += dbidev->mode.left_margin; > + xe += dbidev->mode.left_margin; > + ys += dbidev->mode.upper_margin; > + ye += dbidev->mode.upper_margin; > + > + mipi_dbi_command(dbi, MIPI_DCS_SET_COLUMN_ADDRESS, (xs >> 8) & 0xff, > + xs & 0xff, (xe >> 8) & 0xff, xe & 0xff); > + mipi_dbi_command(dbi, MIPI_DCS_SET_PAGE_ADDRESS, (ys >> 8) & 0xff, > + ys & 0xff, (ye >> 8) & 0xff, ye & 0xff); > +} > + > +static void mipi_dbi_fb_dirty(struct mipi_dbi_dev *dbidev, struct fb_info *info) > +{ > + struct mipi_dbi *dbi = &dbidev->dbi; > + u16 width = info->xres; > + u16 height = info->yres; > + size_t len = width * height * 2; > + int ret; > + > + mipi_dbi_buf_copy(dbidev->tx_buf, info, dbi->swap_bytes); > + > + mipi_dbi_set_window_address(dbidev, 0, width - 1, 0, height - 1); > + > + ret = mipi_dbi_command_buf(dbi, MIPI_DCS_WRITE_MEMORY_START, dbidev->tx_buf, len); > + if (ret) > + pr_err_once("Failed to update display %d\n", ret); FYI, there's %pe support for printing error codes as string if they were compiled in. > +} > + > +/** > + * mipi_dbi_enable_flush - MIPI DBI enable helper > + * @dbidev: MIPI DBI device structure > + * @crtc_state: CRTC state > + * @plane_state: Plane state > + * > + * Flushes the whole framebuffer and enables the backlight. Drivers can use this > + * in their &drm_simple_display_pipe_funcs->enable callback. fb_ops->fb_enable ? > + * > + * Note: Drivers which don't use mipi_dbi_pipe_update() because they have custom > + * framebuffer flushing, can't use this function since they both use the same > + * flushing code. > + */ > +void mipi_dbi_enable_flush(struct mipi_dbi_dev *dbidev, > + struct fb_info *info) > +{ > + mipi_dbi_fb_dirty(dbidev, info); > + > + if (dbidev->backlight) > + backlight_set_brightness_default(dbidev->backlight); > +} > +EXPORT_SYMBOL(mipi_dbi_enable_flush); > + > + > +static void mipi_dbi_blank(struct mipi_dbi_dev *dbidev) > +{ > + u16 height = dbidev->mode.xres; > + u16 width = dbidev->mode.yres; > + struct mipi_dbi *dbi = &dbidev->dbi; > + size_t len = width * height * 2; > + > + memset(dbidev->tx_buf, 0, len); > + > + mipi_dbi_set_window_address(dbidev, 0, width - 1, 0, height - 1); > + mipi_dbi_command_buf(dbi, MIPI_DCS_WRITE_MEMORY_START, dbidev->tx_buf, len); > +} > + > +/** > + * mipi_dbi_fb_disable - MIPI DBI framebuffer disable helper > + * @info: Framebuffer info > + * > + * This function disables backlight if present, if not the display memory is > + * blanked. The regulator is disabled if in use. Drivers can use this as their > + * &fb_ops->fb_disable callback. > + */ > +void mipi_dbi_fb_disable(struct fb_info *info) > +{ > + struct mipi_dbi_dev *dbidev = container_of(info, struct mipi_dbi_dev, info); > + > + if (dbidev->backlight) > + backlight_set_brightness(dbidev->backlight, 0); > + else > + mipi_dbi_blank(dbidev); > + > + if (dbidev->regulator) > + regulator_disable(dbidev->regulator); > + if (dbidev->io_regulator) > + regulator_disable(dbidev->io_regulator); Calling regulator_disable on NULL pointer is a no-op. > +} > +EXPORT_SYMBOL(mipi_dbi_fb_disable); > + > +void mipi_dbi_fb_flush(struct fb_info *info) > +{ > + struct mipi_dbi_dev *dbidev = container_of(info, struct mipi_dbi_dev, info); > + > + mipi_dbi_fb_dirty(dbidev, info); > +} > +EXPORT_SYMBOL(mipi_dbi_fb_flush); > + > +/** > + * mipi_dbi_dev_init - MIPI DBI device initialization > + * @dbidev: MIPI DBI device structure to initialize > + * @ops: Framebuffer operations > + * @mode: Display mode > + * > + * This function sets up a &fb_info with one fixed &fb_videomode. > + * Additionally &mipi_dbi.tx_buf is allocated. > + * > + * Supported format: RGB565. > + * > + * Returns: > + * Zero on success, negative error code on failure. > + */ > +int mipi_dbi_dev_init(struct mipi_dbi_dev *dbidev, struct fb_ops *ops, > + struct fb_videomode *mode) > +{ > + struct fb_info *info = &dbidev->info; > + > + info->mode = mode; > + info->fbops = ops; > + info->dev.parent = dbidev->dev; > + > + info->xres = mode->xres; > + info->yres = mode->yres; > + info->bits_per_pixel = 16; > + info->line_length = info->xres * (info->bits_per_pixel >> 3); > + > + info->screen_size = info->line_length * info->yres; > + info->screen_base = kzalloc(info->screen_size, GFP_KERNEL); dma_alloc? In case some SPI driver gets DMA support. > + > + info->red.length = 5; > + info->red.offset = 11; > + info->green.length = 6; > + info->green.offset = 5; > + info->blue.length = 5; > + info->blue.offset = 0; > + > + dbidev->tx_buf = kzalloc(mode->xres * mode->yres * 2, GFP_KERNEL); Use info->screen_size here as well? > + > + return 0; > +} > + > /** > * mipi_dbi_hw_reset - Hardware reset of controller > * @dbi: MIPI DBI structure > @@ -246,6 +419,75 @@ bool mipi_dbi_display_is_on(struct mipi_dbi *dbi) > } > EXPORT_SYMBOL(mipi_dbi_display_is_on); > > +static int mipi_dbi_poweron_reset_conditional(struct mipi_dbi_dev *dbidev, bool cond) > +{ > + struct device *dev = dbidev->dev; > + struct mipi_dbi *dbi = &dbidev->dbi; > + int ret; > + > + if (dbidev->regulator) { > + ret = regulator_enable(dbidev->regulator); returns 0 when called on NULL pointer, so no need for if above. > + if (ret) { > + dev_err(dev, "Failed to enable regulator (%d)\n", ret); > + return ret; > + } > + } > + > + if (dbidev->io_regulator) { > + ret = regulator_enable(dbidev->io_regulator); Ditto > + if (ret) { > + dev_err(dev, "Failed to enable I/O regulator (%d)\n", ret); > + if (dbidev->regulator) > + regulator_disable(dbidev->regulator); > + return ret; > + } > + } > + > + if (cond && mipi_dbi_display_is_on(dbi)) > + return 1; > + > + mipi_dbi_hw_reset(dbi); > + ret = mipi_dbi_command(dbi, MIPI_DCS_SOFT_RESET); > + if (ret) { > + dev_err(dev, "Failed to send reset command (%d)\n", ret); > + if (dbidev->regulator) > + regulator_disable(dbidev->regulator); > + if (dbidev->io_regulator) > + regulator_disable(dbidev->io_regulator); As above. > + return ret; > + } > + > + /* > + * If we did a hw reset, we know the controller is in Sleep mode and > + * per MIPI DSC spec should wait 5ms after soft reset. If we didn't, > + * we assume worst case and wait 120ms. > + */ > + if (dbi->reset) > + mdelay(5); > + else > + mdelay(120); > + > + return 0; > +} > + > +/** > + * mipi_dbi_poweron_conditional_reset - MIPI DBI poweron and conditional reset > + * @dbidev: MIPI DBI device structure > + * > + * This function enables the regulator if used and if the display is off, it > + * does a hardware and software reset. If mipi_dbi_display_is_on() determines > + * that the display is on, no reset is performed. > + * > + * Returns: > + * Zero if the controller was reset, 1 if the display was already on, or a > + * negative error code. > + */ > +int mipi_dbi_poweron_conditional_reset(struct mipi_dbi_dev *dbidev) > +{ > + return mipi_dbi_poweron_reset_conditional(dbidev, true); > +} > +EXPORT_SYMBOL(mipi_dbi_poweron_conditional_reset); > + > #if IS_ENABLED(CONFIG_SPI) > > /** > diff --git a/include/video/mipi_dbi.h b/include/video/mipi_dbi.h > index 54526006935f..917f7ddd597f 100644 > --- a/include/video/mipi_dbi.h > +++ b/include/video/mipi_dbi.h > @@ -11,6 +11,7 @@ > #include <linux/types.h> > #include <spi/spi.h> > #include <driver.h> > +#include <fb.h> > > struct regulator; > struct fb_videomode; > @@ -55,6 +56,61 @@ struct mipi_dbi { > struct list_head list; > }; > > +/** > + * struct mipi_dbi_dev - MIPI DBI device > + */ > +struct mipi_dbi_dev { > + /** > + * @dev: Device > + */ > + struct device *dev; > + > + /** > + * @info: Framebuffer info > + */ > + struct fb_info info; > + > + /** > + * @mode: Fixed display mode > + */ > + struct fb_videomode mode; > + > + /** > + * @tx_buf: Buffer used for transfer (copy clip rect area) > + */ > + u8 *tx_buf; > + > + /** > + * @backlight_node: backlight device node (optional) > + */ > + struct device_node *backlight_node; > + > + /** > + * @backlight: backlight device (optional) > + */ > + struct backlight_device *backlight; > + > + /** > + * @regulator: power regulator (Vdd) (optional) > + */ > + struct regulator *regulator; > + > + /** > + * @io_regulator: I/O power regulator (Vddi) (optional) > + */ > + struct regulator *io_regulator; > + > + /** > + * @dbi: MIPI DBI interface > + */ > + struct mipi_dbi dbi; > + > + /** > + * @driver_private: Driver private data. > + */ > + void *driver_private; > +}; > + > static inline const char *mipi_dbi_name(struct mipi_dbi *dbi) > { > return dev_name(&dbi->spi->dev); > @@ -62,8 +118,15 @@ static inline const char *mipi_dbi_name(struct mipi_dbi *dbi) > > int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *dbi, > int dc); > +int mipi_dbi_dev_init(struct mipi_dbi_dev *dbidev, > + struct fb_ops *ops, struct fb_videomode *mode); > +void mipi_dbi_fb_flush(struct fb_info *info); > +void mipi_dbi_enable_flush(struct mipi_dbi_dev *dbidev, > + struct fb_info *info); > +void mipi_dbi_fb_disable(struct fb_info *info); > void mipi_dbi_hw_reset(struct mipi_dbi *dbi); > bool mipi_dbi_display_is_on(struct mipi_dbi *dbi); > +int mipi_dbi_poweron_conditional_reset(struct mipi_dbi_dev *dbidev); > > u32 mipi_dbi_spi_cmd_max_speed(struct spi_device *spi, size_t len); > int mipi_dbi_spi_transfer(struct spi_device *spi, u32 speed_hz, -- Pengutronix e.K. | | Steuerwalder Str. 21 | http://www.pengutronix.de/ | 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |