Message ID | 20200607133832.1730288-4-paul@crapouillou.net (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | DSI/DBI and TinyDRM driver | expand |
Hi paul, After add this driver, the followinig usage scenarios can be supported? panel 1. crtc->encoder->connector[edp/hdmi/mipi dsi] -> panel panel 2. Panel setup/control and framebuffer upload over SPI the two panel maybe display same/different conctent at same time. 在 2020/6/7 21:38, Paul Cercueil 写道: > The new API function mipi_dsi_maybe_register_tiny_driver() is supposed > to be called by DSI/DBI panel drivers at the end of their probe. > > If it is detected that the panel is not connected to any controller, > because it has no port #0 node in Device Tree that points back to it, > then a TinyDRM driver is registered with it. > > This TinyDRM driver expects that a DCS-compliant protocol is used by the > DSI/DBI panel and can only be used with these. > > Signed-off-by: Paul Cercueil <paul@crapouillou.net> > --- > drivers/gpu/drm/tiny/Kconfig | 8 + > drivers/gpu/drm/tiny/Makefile | 1 + > drivers/gpu/drm/tiny/tiny-dsi.c | 262 ++++++++++++++++++++++++++++++++ > include/drm/drm_mipi_dsi.h | 19 +++ > 4 files changed, 290 insertions(+) > create mode 100644 drivers/gpu/drm/tiny/tiny-dsi.c > > diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig > index 4160e74e4751..54ee58aecf66 100644 > --- a/drivers/gpu/drm/tiny/Kconfig > +++ b/drivers/gpu/drm/tiny/Kconfig > @@ -9,6 +9,14 @@ config DRM_GM12U320 > This is a KMS driver for projectors which use the GM12U320 chipset > for video transfer over USB2/3, such as the Acer C120 mini projector. > > +config TINYDRM_DSI > + tristate "DRM support for generic DBI/DSI display panels" > + depends on DRM && DRM_MIPI_DSI > + select DRM_MIPI_DBI > + select DRM_KMS_CMA_HELPER > + help > + DRM driver for generic DBI/DSI display panels > + > config TINYDRM_HX8357D > tristate "DRM support for HX8357D display panels" > depends on DRM && SPI > diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile > index c96ceee71453..49513db9a307 100644 > --- a/drivers/gpu/drm/tiny/Makefile > +++ b/drivers/gpu/drm/tiny/Makefile > @@ -1,6 +1,7 @@ > # SPDX-License-Identifier: GPL-2.0-only > > obj-$(CONFIG_DRM_GM12U320) += gm12u320.o > +obj-$(CONFIG_TINYDRM_DSI) += tiny-dsi.o > obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o > obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o > obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o > diff --git a/drivers/gpu/drm/tiny/tiny-dsi.c b/drivers/gpu/drm/tiny/tiny-dsi.c > new file mode 100644 > index 000000000000..915e598844bd > --- /dev/null > +++ b/drivers/gpu/drm/tiny/tiny-dsi.c > @@ -0,0 +1,262 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * TinyDRM driver for standard DSI/DBI panels > + * > + * Copyright 2020 Paul Cercueil <paul@crapouillou.net> > + */ > + > +#include <linux/module.h> > + > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_damage_helper.h> > +#include <drm/drm_drv.h> > +#include <drm/drm_fb_helper.h> > +#include <drm/drm_fourcc.h> > +#include <drm/drm_gem_cma_helper.h> > +#include <drm/drm_gem_framebuffer_helper.h> > +#include <drm/drm_mipi_dbi.h> > +#include <drm/drm_mipi_dsi.h> > +#include <drm/drm_modeset_helper.h> > +#include <drm/drm_panel.h> > +#include <drm/drm_probe_helper.h> > + > +#include <video/mipi_display.h> > + > +struct tiny_dsi { > + struct drm_device drm; > + struct drm_connector connector; > + struct drm_simple_display_pipe pipe; > + > + struct mipi_dsi_device *dsi; > + struct drm_panel *panel; > +}; > + > +#define mipi_dcs_command(dsi, cmd, seq...) \ > +({ \ > + u8 d[] = { seq }; \ > + mipi_dsi_dcs_write(dsi, cmd, d, ARRAY_SIZE(d)); \ > +}) > + > +static inline struct tiny_dsi *drm_to_tiny_dsi(struct drm_device *drm) > +{ > + return container_of(drm, struct tiny_dsi, drm); > +} > + > +static void tiny_dsi_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect) > +{ > + struct drm_gem_object *gem = drm_gem_fb_get_obj(fb, 0); > + struct drm_gem_cma_object *cma_obj = to_drm_gem_cma_obj(gem); > + struct tiny_dsi *priv = drm_to_tiny_dsi(fb->dev); > + unsigned int height = rect->y2 - rect->y1; > + unsigned int width = rect->x2 - rect->x1; > + bool fb_convert; > + int idx, ret; > + void *tr; > + > + if (!drm_dev_enter(fb->dev, &idx)) > + return; > + > + DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect)); > + > + fb_convert = width != fb->width || height != fb->height > + || fb->format->format == DRM_FORMAT_XRGB8888; > + if (fb_convert) { > + tr = kzalloc(width * height * 2, GFP_KERNEL); > + > + /* TODO: swap pixels if needed */ > + ret = mipi_dbi_buf_copy(tr, fb, rect, false); > + if (ret) > + goto err_msg; > + } else { > + tr = cma_obj->vaddr; > + } > + > + mipi_dcs_command(priv->dsi, MIPI_DCS_SET_COLUMN_ADDRESS, > + (rect->x1 >> 8) & 0xff, rect->x1 & 0xff, > + (rect->x2 >> 8) & 0xff, rect->x2 & 0xff); > + mipi_dcs_command(priv->dsi, MIPI_DCS_SET_PAGE_ADDRESS, > + (rect->y1 >> 8) & 0xff, rect->y1 & 0xff, > + (rect->y2 >> 8) & 0xff, rect->y2 & 0xff); > + > + ret = mipi_dsi_dcs_write(priv->dsi, MIPI_DCS_WRITE_MEMORY_START, > + tr, width * height * 2); > +err_msg: > + if (ret) > + dev_err_once(fb->dev->dev, "Failed to update display %d\n", ret); > + > + if (fb_convert) > + kfree(tr); > + drm_dev_exit(idx); > +} > + > +static void tiny_dsi_enable(struct drm_simple_display_pipe *pipe, > + struct drm_crtc_state *crtc_state, > + struct drm_plane_state *plane_state) > +{ > + struct tiny_dsi *priv = drm_to_tiny_dsi(pipe->crtc.dev); > + > + drm_panel_enable(priv->panel); > +} > + > +static void tiny_dsi_disable(struct drm_simple_display_pipe *pipe) > +{ > + struct tiny_dsi *priv = drm_to_tiny_dsi(pipe->crtc.dev); > + > + drm_panel_disable(priv->panel); > +} > + > +static void tiny_dsi_update(struct drm_simple_display_pipe *pipe, > + struct drm_plane_state *old_state) > +{ > + struct drm_plane_state *state = pipe->plane.state; > + struct drm_rect rect; > + > + if (drm_atomic_helper_damage_merged(old_state, state, &rect)) > + tiny_dsi_fb_dirty(state->fb, &rect); > +} > + > +static const struct drm_simple_display_pipe_funcs tiny_dsi_pipe_funcs = { > + .enable = tiny_dsi_enable, > + .disable = tiny_dsi_disable, > + .update = tiny_dsi_update, > + .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, > +}; > + > +static int tiny_dsi_connector_get_modes(struct drm_connector *connector) > +{ > + struct tiny_dsi *priv = drm_to_tiny_dsi(connector->dev); > + > + return drm_panel_get_modes(priv->panel, connector); > +} > + > +static const struct drm_connector_helper_funcs tiny_dsi_connector_hfuncs = { > + .get_modes = tiny_dsi_connector_get_modes, > +}; > + > +static const struct drm_connector_funcs tiny_dsi_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, > +}; > + > +DEFINE_DRM_GEM_CMA_FOPS(tiny_dsi_fops); > + > +static const uint32_t tiny_dsi_formats[] = { > + DRM_FORMAT_RGB565, > + DRM_FORMAT_XRGB8888, > +}; > + > +static const struct drm_mode_config_funcs tiny_dsi_mode_config_funcs = { > + .fb_create = drm_gem_fb_create_with_dirty, > + .atomic_check = drm_atomic_helper_check, > + .atomic_commit = drm_atomic_helper_commit, > +}; > + > +static void tiny_dsi_release(struct drm_device *drm) > +{ > + struct tiny_dsi *priv = drm_to_tiny_dsi(drm); > + > + drm_mode_config_cleanup(drm); > + drm_dev_fini(drm); > + kfree(priv); > +} > + > +static struct drm_driver tiny_dsi_driver = { > + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, > + .fops = &tiny_dsi_fops, > + .release = tiny_dsi_release, > + DRM_GEM_CMA_VMAP_DRIVER_OPS, > + .name = "tiny-dsi", > + .desc = "Tiny DSI", > + .date = "20200605", > + .major = 1, > + .minor = 0, > +}; > + > +static void tiny_dsi_remove(void *drm) > +{ > + drm_dev_unplug(drm); > + drm_atomic_helper_shutdown(drm); > +} > + > +int mipi_dsi_register_tiny_driver(struct mipi_dsi_device *dsi) > +{ > + struct device *dev = &dsi->dev; > + struct drm_device *drm; > + struct tiny_dsi *priv; > + static const uint64_t modifiers[] = { > + DRM_FORMAT_MOD_LINEAR, > + DRM_FORMAT_MOD_INVALID > + }; > + int ret; > + > + priv = kzalloc(sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->dsi = dsi; > + drm = &priv->drm; > + > + ret = devm_drm_dev_init(dev, drm, &tiny_dsi_driver); > + if (ret) { > + kfree(priv); > + return ret; > + } > + > + drm_mode_config_init(drm); > + > + priv->panel = of_drm_find_panel(dev->of_node); > + if (IS_ERR(priv->panel)) { > + dev_err(dev, "Unable to find panel\n"); > + return PTR_ERR(priv->panel); > + } > + > + drm->mode_config.preferred_depth = 16; > + > + drm->mode_config.funcs = &tiny_dsi_mode_config_funcs; > + drm->mode_config.min_width = 0; > + drm->mode_config.min_height = 0; > + drm->mode_config.max_width = 4096; > + drm->mode_config.max_height = 4096; > + > + drm_connector_helper_add(&priv->connector, &tiny_dsi_connector_hfuncs); > + ret = drm_connector_init(drm, &priv->connector, &tiny_dsi_connector_funcs, > + DRM_MODE_CONNECTOR_DSI); > + if (ret) { > + dev_err(dev, "Unable to init connector\n"); > + return ret; > + } > + > + ret = drm_simple_display_pipe_init(drm, &priv->pipe, &tiny_dsi_pipe_funcs, > + tiny_dsi_formats, ARRAY_SIZE(tiny_dsi_formats), > + modifiers, &priv->connector); > + if (ret) { > + dev_err(dev, "Unable to init display pipe\n"); > + return ret; > + } > + > + drm_plane_enable_fb_damage_clips(&priv->pipe.plane); > + > + drm_mode_config_reset(drm); > + > + ret = drm_dev_register(drm, 0); > + if (ret) { > + dev_err(dev, "Failed to register DRM driver\n"); > + return ret; > + } > + > + ret = devm_add_action_or_reset(dev, tiny_dsi_remove, drm); > + if (ret) > + return ret; > + > + drm_fbdev_generic_setup(drm, 0); > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(mipi_dsi_register_tiny_driver); > + > +MODULE_DESCRIPTION("DSI/DBI TinyDRM driver"); > +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); > +MODULE_LICENSE("GPL"); > diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h > index 65d2961fc054..0c2589a55df6 100644 > --- a/include/drm/drm_mipi_dsi.h > +++ b/include/drm/drm_mipi_dsi.h > @@ -10,6 +10,7 @@ > #define __DRM_MIPI_DSI_H__ > > #include <linux/device.h> > +#include <linux/of_graph.h> > > struct mipi_dsi_host; > struct mipi_dsi_device; > @@ -337,4 +338,22 @@ void mipi_dsi_driver_unregister(struct mipi_dsi_driver *driver); > module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ > mipi_dsi_driver_unregister) > > +#if IS_ENABLED(CONFIG_TINYDRM_DSI) > +int mipi_dsi_register_tiny_driver(struct mipi_dsi_device *dsi); > +#else > +static inline int mipi_dsi_register_tiny_driver(struct mipi_dsi_device *dsi) > +{ > + return 0; > +} > +#endif > + > +static inline int mipi_dsi_maybe_register_tiny_driver(struct mipi_dsi_device *dsi) > +{ > + /* Register the TinyDRM DSI/DBI driver if the panel has no controller */ > + if (!of_graph_get_port_by_id(dsi->dev.of_node, 0)) > + return mipi_dsi_register_tiny_driver(dsi); > + > + return 0; > +} > + > #endif /* __DRM_MIPI_DSI__ */
Hi Sandy, Le mer. 8 juil. 2020 à 10:26, Sandy Huang <sandy.huang01@yahoo.com> a écrit : > Hi paul, > > After add this driver, the followinig usage scenarios can be > supported? > > panel 1. crtc->encoder->connector[edp/hdmi/mipi dsi] -> panel > > panel 2. Panel setup/control and framebuffer upload over SPI > > the two panel maybe display same/different conctent at same time. Yes, should be totally possible. Cheers, -Paul > > 在 2020/6/7 21:38, Paul Cercueil 写道: >> The new API function mipi_dsi_maybe_register_tiny_driver() is >> supposed >> to be called by DSI/DBI panel drivers at the end of their probe. >> >> If it is detected that the panel is not connected to any controller, >> because it has no port #0 node in Device Tree that points back to it, >> then a TinyDRM driver is registered with it. >> >> This TinyDRM driver expects that a DCS-compliant protocol is used by >> the >> DSI/DBI panel and can only be used with these. >> >> Signed-off-by: Paul Cercueil <paul@crapouillou.net> >> --- >> drivers/gpu/drm/tiny/Kconfig | 8 + >> drivers/gpu/drm/tiny/Makefile | 1 + >> drivers/gpu/drm/tiny/tiny-dsi.c | 262 >> ++++++++++++++++++++++++++++++++ >> include/drm/drm_mipi_dsi.h | 19 +++ >> 4 files changed, 290 insertions(+) >> create mode 100644 drivers/gpu/drm/tiny/tiny-dsi.c >> >> diff --git a/drivers/gpu/drm/tiny/Kconfig >> b/drivers/gpu/drm/tiny/Kconfig >> index 4160e74e4751..54ee58aecf66 100644 >> --- a/drivers/gpu/drm/tiny/Kconfig >> +++ b/drivers/gpu/drm/tiny/Kconfig >> @@ -9,6 +9,14 @@ config DRM_GM12U320 >> This is a KMS driver for projectors which use the GM12U320 >> chipset >> for video transfer over USB2/3, such as the Acer C120 mini >> projector. >> +config TINYDRM_DSI >> + tristate "DRM support for generic DBI/DSI display panels" >> + depends on DRM && DRM_MIPI_DSI >> + select DRM_MIPI_DBI >> + select DRM_KMS_CMA_HELPER >> + help >> + DRM driver for generic DBI/DSI display panels >> + >> config TINYDRM_HX8357D >> tristate "DRM support for HX8357D display panels" >> depends on DRM && SPI >> diff --git a/drivers/gpu/drm/tiny/Makefile >> b/drivers/gpu/drm/tiny/Makefile >> index c96ceee71453..49513db9a307 100644 >> --- a/drivers/gpu/drm/tiny/Makefile >> +++ b/drivers/gpu/drm/tiny/Makefile >> @@ -1,6 +1,7 @@ >> # SPDX-License-Identifier: GPL-2.0-only >> obj-$(CONFIG_DRM_GM12U320) += gm12u320.o >> +obj-$(CONFIG_TINYDRM_DSI) += tiny-dsi.o >> obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o >> obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o >> obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o >> diff --git a/drivers/gpu/drm/tiny/tiny-dsi.c >> b/drivers/gpu/drm/tiny/tiny-dsi.c >> new file mode 100644 >> index 000000000000..915e598844bd >> --- /dev/null >> +++ b/drivers/gpu/drm/tiny/tiny-dsi.c >> @@ -0,0 +1,262 @@ >> +// SPDX-License-Identifier: GPL-2.0-or-later >> +/* >> + * TinyDRM driver for standard DSI/DBI panels >> + * >> + * Copyright 2020 Paul Cercueil <paul@crapouillou.net> >> + */ >> + >> +#include <linux/module.h> >> + >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_damage_helper.h> >> +#include <drm/drm_drv.h> >> +#include <drm/drm_fb_helper.h> >> +#include <drm/drm_fourcc.h> >> +#include <drm/drm_gem_cma_helper.h> >> +#include <drm/drm_gem_framebuffer_helper.h> >> +#include <drm/drm_mipi_dbi.h> >> +#include <drm/drm_mipi_dsi.h> >> +#include <drm/drm_modeset_helper.h> >> +#include <drm/drm_panel.h> >> +#include <drm/drm_probe_helper.h> >> + >> +#include <video/mipi_display.h> >> + >> +struct tiny_dsi { >> + struct drm_device drm; >> + struct drm_connector connector; >> + struct drm_simple_display_pipe pipe; >> + >> + struct mipi_dsi_device *dsi; >> + struct drm_panel *panel; >> +}; >> + >> +#define mipi_dcs_command(dsi, cmd, seq...) \ >> +({ \ >> + u8 d[] = { seq }; \ >> + mipi_dsi_dcs_write(dsi, cmd, d, ARRAY_SIZE(d)); \ >> +}) >> + >> +static inline struct tiny_dsi *drm_to_tiny_dsi(struct drm_device >> *drm) >> +{ >> + return container_of(drm, struct tiny_dsi, drm); >> +} >> + >> +static void tiny_dsi_fb_dirty(struct drm_framebuffer *fb, struct >> drm_rect *rect) >> +{ >> + struct drm_gem_object *gem = drm_gem_fb_get_obj(fb, 0); >> + struct drm_gem_cma_object *cma_obj = to_drm_gem_cma_obj(gem); >> + struct tiny_dsi *priv = drm_to_tiny_dsi(fb->dev); >> + unsigned int height = rect->y2 - rect->y1; >> + unsigned int width = rect->x2 - rect->x1; >> + bool fb_convert; >> + int idx, ret; >> + void *tr; >> + >> + if (!drm_dev_enter(fb->dev, &idx)) >> + return; >> + >> + DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, >> DRM_RECT_ARG(rect)); >> + >> + fb_convert = width != fb->width || height != fb->height >> + || fb->format->format == DRM_FORMAT_XRGB8888; >> + if (fb_convert) { >> + tr = kzalloc(width * height * 2, GFP_KERNEL); >> + >> + /* TODO: swap pixels if needed */ >> + ret = mipi_dbi_buf_copy(tr, fb, rect, false); >> + if (ret) >> + goto err_msg; >> + } else { >> + tr = cma_obj->vaddr; >> + } >> + >> + mipi_dcs_command(priv->dsi, MIPI_DCS_SET_COLUMN_ADDRESS, >> + (rect->x1 >> 8) & 0xff, rect->x1 & 0xff, >> + (rect->x2 >> 8) & 0xff, rect->x2 & 0xff); >> + mipi_dcs_command(priv->dsi, MIPI_DCS_SET_PAGE_ADDRESS, >> + (rect->y1 >> 8) & 0xff, rect->y1 & 0xff, >> + (rect->y2 >> 8) & 0xff, rect->y2 & 0xff); >> + >> + ret = mipi_dsi_dcs_write(priv->dsi, MIPI_DCS_WRITE_MEMORY_START, >> + tr, width * height * 2); >> +err_msg: >> + if (ret) >> + dev_err_once(fb->dev->dev, "Failed to update display %d\n", ret); >> + >> + if (fb_convert) >> + kfree(tr); >> + drm_dev_exit(idx); >> +} >> + >> +static void tiny_dsi_enable(struct drm_simple_display_pipe *pipe, >> + struct drm_crtc_state *crtc_state, >> + struct drm_plane_state *plane_state) >> +{ >> + struct tiny_dsi *priv = drm_to_tiny_dsi(pipe->crtc.dev); >> + >> + drm_panel_enable(priv->panel); >> +} >> + >> +static void tiny_dsi_disable(struct drm_simple_display_pipe *pipe) >> +{ >> + struct tiny_dsi *priv = drm_to_tiny_dsi(pipe->crtc.dev); >> + >> + drm_panel_disable(priv->panel); >> +} >> + >> +static void tiny_dsi_update(struct drm_simple_display_pipe *pipe, >> + struct drm_plane_state *old_state) >> +{ >> + struct drm_plane_state *state = pipe->plane.state; >> + struct drm_rect rect; >> + >> + if (drm_atomic_helper_damage_merged(old_state, state, &rect)) >> + tiny_dsi_fb_dirty(state->fb, &rect); >> +} >> + >> +static const struct drm_simple_display_pipe_funcs >> tiny_dsi_pipe_funcs = { >> + .enable = tiny_dsi_enable, >> + .disable = tiny_dsi_disable, >> + .update = tiny_dsi_update, >> + .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, >> +}; >> + >> +static int tiny_dsi_connector_get_modes(struct drm_connector >> *connector) >> +{ >> + struct tiny_dsi *priv = drm_to_tiny_dsi(connector->dev); >> + >> + return drm_panel_get_modes(priv->panel, connector); >> +} >> + >> +static const struct drm_connector_helper_funcs >> tiny_dsi_connector_hfuncs = { >> + .get_modes = tiny_dsi_connector_get_modes, >> +}; >> + >> +static const struct drm_connector_funcs tiny_dsi_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, >> +}; >> + >> +DEFINE_DRM_GEM_CMA_FOPS(tiny_dsi_fops); >> + >> +static const uint32_t tiny_dsi_formats[] = { >> + DRM_FORMAT_RGB565, >> + DRM_FORMAT_XRGB8888, >> +}; >> + >> +static const struct drm_mode_config_funcs >> tiny_dsi_mode_config_funcs = { >> + .fb_create = drm_gem_fb_create_with_dirty, >> + .atomic_check = drm_atomic_helper_check, >> + .atomic_commit = drm_atomic_helper_commit, >> +}; >> + >> +static void tiny_dsi_release(struct drm_device *drm) >> +{ >> + struct tiny_dsi *priv = drm_to_tiny_dsi(drm); >> + >> + drm_mode_config_cleanup(drm); >> + drm_dev_fini(drm); >> + kfree(priv); >> +} >> + >> +static struct drm_driver tiny_dsi_driver = { >> + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, >> + .fops = &tiny_dsi_fops, >> + .release = tiny_dsi_release, >> + DRM_GEM_CMA_VMAP_DRIVER_OPS, >> + .name = "tiny-dsi", >> + .desc = "Tiny DSI", >> + .date = "20200605", >> + .major = 1, >> + .minor = 0, >> +}; >> + >> +static void tiny_dsi_remove(void *drm) >> +{ >> + drm_dev_unplug(drm); >> + drm_atomic_helper_shutdown(drm); >> +} >> + >> +int mipi_dsi_register_tiny_driver(struct mipi_dsi_device *dsi) >> +{ >> + struct device *dev = &dsi->dev; >> + struct drm_device *drm; >> + struct tiny_dsi *priv; >> + static const uint64_t modifiers[] = { >> + DRM_FORMAT_MOD_LINEAR, >> + DRM_FORMAT_MOD_INVALID >> + }; >> + int ret; >> + >> + priv = kzalloc(sizeof(*priv), GFP_KERNEL); >> + if (!priv) >> + return -ENOMEM; >> + >> + priv->dsi = dsi; >> + drm = &priv->drm; >> + >> + ret = devm_drm_dev_init(dev, drm, &tiny_dsi_driver); >> + if (ret) { >> + kfree(priv); >> + return ret; >> + } >> + >> + drm_mode_config_init(drm); >> + >> + priv->panel = of_drm_find_panel(dev->of_node); >> + if (IS_ERR(priv->panel)) { >> + dev_err(dev, "Unable to find panel\n"); >> + return PTR_ERR(priv->panel); >> + } >> + >> + drm->mode_config.preferred_depth = 16; >> + >> + drm->mode_config.funcs = &tiny_dsi_mode_config_funcs; >> + drm->mode_config.min_width = 0; >> + drm->mode_config.min_height = 0; >> + drm->mode_config.max_width = 4096; >> + drm->mode_config.max_height = 4096; >> + >> + drm_connector_helper_add(&priv->connector, >> &tiny_dsi_connector_hfuncs); >> + ret = drm_connector_init(drm, &priv->connector, >> &tiny_dsi_connector_funcs, >> + DRM_MODE_CONNECTOR_DSI); >> + if (ret) { >> + dev_err(dev, "Unable to init connector\n"); >> + return ret; >> + } >> + >> + ret = drm_simple_display_pipe_init(drm, &priv->pipe, >> &tiny_dsi_pipe_funcs, >> + tiny_dsi_formats, ARRAY_SIZE(tiny_dsi_formats), >> + modifiers, &priv->connector); >> + if (ret) { >> + dev_err(dev, "Unable to init display pipe\n"); >> + return ret; >> + } >> + >> + drm_plane_enable_fb_damage_clips(&priv->pipe.plane); >> + >> + drm_mode_config_reset(drm); >> + >> + ret = drm_dev_register(drm, 0); >> + if (ret) { >> + dev_err(dev, "Failed to register DRM driver\n"); >> + return ret; >> + } >> + >> + ret = devm_add_action_or_reset(dev, tiny_dsi_remove, drm); >> + if (ret) >> + return ret; >> + >> + drm_fbdev_generic_setup(drm, 0); >> + >> + return 0; >> +} >> +EXPORT_SYMBOL_GPL(mipi_dsi_register_tiny_driver); >> + >> +MODULE_DESCRIPTION("DSI/DBI TinyDRM driver"); >> +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); >> +MODULE_LICENSE("GPL"); >> diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h >> index 65d2961fc054..0c2589a55df6 100644 >> --- a/include/drm/drm_mipi_dsi.h >> +++ b/include/drm/drm_mipi_dsi.h >> @@ -10,6 +10,7 @@ >> #define __DRM_MIPI_DSI_H__ >> #include <linux/device.h> >> +#include <linux/of_graph.h> >> struct mipi_dsi_host; >> struct mipi_dsi_device; >> @@ -337,4 +338,22 @@ void mipi_dsi_driver_unregister(struct >> mipi_dsi_driver *driver); >> module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ >> mipi_dsi_driver_unregister) >> +#if IS_ENABLED(CONFIG_TINYDRM_DSI) >> +int mipi_dsi_register_tiny_driver(struct mipi_dsi_device *dsi); >> +#else >> +static inline int mipi_dsi_register_tiny_driver(struct >> mipi_dsi_device *dsi) >> +{ >> + return 0; >> +} >> +#endif >> + >> +static inline int mipi_dsi_maybe_register_tiny_driver(struct >> mipi_dsi_device *dsi) >> +{ >> + /* Register the TinyDRM DSI/DBI driver if the panel has no >> controller */ >> + if (!of_graph_get_port_by_id(dsi->dev.of_node, 0)) >> + return mipi_dsi_register_tiny_driver(dsi); >> + >> + return 0; >> +} >> + >> #endif /* __DRM_MIPI_DSI__ */
diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig index 4160e74e4751..54ee58aecf66 100644 --- a/drivers/gpu/drm/tiny/Kconfig +++ b/drivers/gpu/drm/tiny/Kconfig @@ -9,6 +9,14 @@ config DRM_GM12U320 This is a KMS driver for projectors which use the GM12U320 chipset for video transfer over USB2/3, such as the Acer C120 mini projector. +config TINYDRM_DSI + tristate "DRM support for generic DBI/DSI display panels" + depends on DRM && DRM_MIPI_DSI + select DRM_MIPI_DBI + select DRM_KMS_CMA_HELPER + help + DRM driver for generic DBI/DSI display panels + config TINYDRM_HX8357D tristate "DRM support for HX8357D display panels" depends on DRM && SPI diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile index c96ceee71453..49513db9a307 100644 --- a/drivers/gpu/drm/tiny/Makefile +++ b/drivers/gpu/drm/tiny/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_DRM_GM12U320) += gm12u320.o +obj-$(CONFIG_TINYDRM_DSI) += tiny-dsi.o obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o diff --git a/drivers/gpu/drm/tiny/tiny-dsi.c b/drivers/gpu/drm/tiny/tiny-dsi.c new file mode 100644 index 000000000000..915e598844bd --- /dev/null +++ b/drivers/gpu/drm/tiny/tiny-dsi.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TinyDRM driver for standard DSI/DBI panels + * + * Copyright 2020 Paul Cercueil <paul@crapouillou.net> + */ + +#include <linux/module.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_mipi_dbi.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> + +#include <video/mipi_display.h> + +struct tiny_dsi { + struct drm_device drm; + struct drm_connector connector; + struct drm_simple_display_pipe pipe; + + struct mipi_dsi_device *dsi; + struct drm_panel *panel; +}; + +#define mipi_dcs_command(dsi, cmd, seq...) \ +({ \ + u8 d[] = { seq }; \ + mipi_dsi_dcs_write(dsi, cmd, d, ARRAY_SIZE(d)); \ +}) + +static inline struct tiny_dsi *drm_to_tiny_dsi(struct drm_device *drm) +{ + return container_of(drm, struct tiny_dsi, drm); +} + +static void tiny_dsi_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect) +{ + struct drm_gem_object *gem = drm_gem_fb_get_obj(fb, 0); + struct drm_gem_cma_object *cma_obj = to_drm_gem_cma_obj(gem); + struct tiny_dsi *priv = drm_to_tiny_dsi(fb->dev); + unsigned int height = rect->y2 - rect->y1; + unsigned int width = rect->x2 - rect->x1; + bool fb_convert; + int idx, ret; + void *tr; + + if (!drm_dev_enter(fb->dev, &idx)) + return; + + DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect)); + + fb_convert = width != fb->width || height != fb->height + || fb->format->format == DRM_FORMAT_XRGB8888; + if (fb_convert) { + tr = kzalloc(width * height * 2, GFP_KERNEL); + + /* TODO: swap pixels if needed */ + ret = mipi_dbi_buf_copy(tr, fb, rect, false); + if (ret) + goto err_msg; + } else { + tr = cma_obj->vaddr; + } + + mipi_dcs_command(priv->dsi, MIPI_DCS_SET_COLUMN_ADDRESS, + (rect->x1 >> 8) & 0xff, rect->x1 & 0xff, + (rect->x2 >> 8) & 0xff, rect->x2 & 0xff); + mipi_dcs_command(priv->dsi, MIPI_DCS_SET_PAGE_ADDRESS, + (rect->y1 >> 8) & 0xff, rect->y1 & 0xff, + (rect->y2 >> 8) & 0xff, rect->y2 & 0xff); + + ret = mipi_dsi_dcs_write(priv->dsi, MIPI_DCS_WRITE_MEMORY_START, + tr, width * height * 2); +err_msg: + if (ret) + dev_err_once(fb->dev->dev, "Failed to update display %d\n", ret); + + if (fb_convert) + kfree(tr); + drm_dev_exit(idx); +} + +static void tiny_dsi_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) +{ + struct tiny_dsi *priv = drm_to_tiny_dsi(pipe->crtc.dev); + + drm_panel_enable(priv->panel); +} + +static void tiny_dsi_disable(struct drm_simple_display_pipe *pipe) +{ + struct tiny_dsi *priv = drm_to_tiny_dsi(pipe->crtc.dev); + + drm_panel_disable(priv->panel); +} + +static void tiny_dsi_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct drm_plane_state *state = pipe->plane.state; + struct drm_rect rect; + + if (drm_atomic_helper_damage_merged(old_state, state, &rect)) + tiny_dsi_fb_dirty(state->fb, &rect); +} + +static const struct drm_simple_display_pipe_funcs tiny_dsi_pipe_funcs = { + .enable = tiny_dsi_enable, + .disable = tiny_dsi_disable, + .update = tiny_dsi_update, + .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, +}; + +static int tiny_dsi_connector_get_modes(struct drm_connector *connector) +{ + struct tiny_dsi *priv = drm_to_tiny_dsi(connector->dev); + + return drm_panel_get_modes(priv->panel, connector); +} + +static const struct drm_connector_helper_funcs tiny_dsi_connector_hfuncs = { + .get_modes = tiny_dsi_connector_get_modes, +}; + +static const struct drm_connector_funcs tiny_dsi_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, +}; + +DEFINE_DRM_GEM_CMA_FOPS(tiny_dsi_fops); + +static const uint32_t tiny_dsi_formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, +}; + +static const struct drm_mode_config_funcs tiny_dsi_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void tiny_dsi_release(struct drm_device *drm) +{ + struct tiny_dsi *priv = drm_to_tiny_dsi(drm); + + drm_mode_config_cleanup(drm); + drm_dev_fini(drm); + kfree(priv); +} + +static struct drm_driver tiny_dsi_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .fops = &tiny_dsi_fops, + .release = tiny_dsi_release, + DRM_GEM_CMA_VMAP_DRIVER_OPS, + .name = "tiny-dsi", + .desc = "Tiny DSI", + .date = "20200605", + .major = 1, + .minor = 0, +}; + +static void tiny_dsi_remove(void *drm) +{ + drm_dev_unplug(drm); + drm_atomic_helper_shutdown(drm); +} + +int mipi_dsi_register_tiny_driver(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct drm_device *drm; + struct tiny_dsi *priv; + static const uint64_t modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID + }; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dsi = dsi; + drm = &priv->drm; + + ret = devm_drm_dev_init(dev, drm, &tiny_dsi_driver); + if (ret) { + kfree(priv); + return ret; + } + + drm_mode_config_init(drm); + + priv->panel = of_drm_find_panel(dev->of_node); + if (IS_ERR(priv->panel)) { + dev_err(dev, "Unable to find panel\n"); + return PTR_ERR(priv->panel); + } + + drm->mode_config.preferred_depth = 16; + + drm->mode_config.funcs = &tiny_dsi_mode_config_funcs; + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = 4096; + drm->mode_config.max_height = 4096; + + drm_connector_helper_add(&priv->connector, &tiny_dsi_connector_hfuncs); + ret = drm_connector_init(drm, &priv->connector, &tiny_dsi_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + if (ret) { + dev_err(dev, "Unable to init connector\n"); + return ret; + } + + ret = drm_simple_display_pipe_init(drm, &priv->pipe, &tiny_dsi_pipe_funcs, + tiny_dsi_formats, ARRAY_SIZE(tiny_dsi_formats), + modifiers, &priv->connector); + if (ret) { + dev_err(dev, "Unable to init display pipe\n"); + return ret; + } + + drm_plane_enable_fb_damage_clips(&priv->pipe.plane); + + drm_mode_config_reset(drm); + + ret = drm_dev_register(drm, 0); + if (ret) { + dev_err(dev, "Failed to register DRM driver\n"); + return ret; + } + + ret = devm_add_action_or_reset(dev, tiny_dsi_remove, drm); + if (ret) + return ret; + + drm_fbdev_generic_setup(drm, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(mipi_dsi_register_tiny_driver); + +MODULE_DESCRIPTION("DSI/DBI TinyDRM driver"); +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_LICENSE("GPL"); diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 65d2961fc054..0c2589a55df6 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -10,6 +10,7 @@ #define __DRM_MIPI_DSI_H__ #include <linux/device.h> +#include <linux/of_graph.h> struct mipi_dsi_host; struct mipi_dsi_device; @@ -337,4 +338,22 @@ void mipi_dsi_driver_unregister(struct mipi_dsi_driver *driver); module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ mipi_dsi_driver_unregister) +#if IS_ENABLED(CONFIG_TINYDRM_DSI) +int mipi_dsi_register_tiny_driver(struct mipi_dsi_device *dsi); +#else +static inline int mipi_dsi_register_tiny_driver(struct mipi_dsi_device *dsi) +{ + return 0; +} +#endif + +static inline int mipi_dsi_maybe_register_tiny_driver(struct mipi_dsi_device *dsi) +{ + /* Register the TinyDRM DSI/DBI driver if the panel has no controller */ + if (!of_graph_get_port_by_id(dsi->dev.of_node, 0)) + return mipi_dsi_register_tiny_driver(dsi); + + return 0; +} + #endif /* __DRM_MIPI_DSI__ */
The new API function mipi_dsi_maybe_register_tiny_driver() is supposed to be called by DSI/DBI panel drivers at the end of their probe. If it is detected that the panel is not connected to any controller, because it has no port #0 node in Device Tree that points back to it, then a TinyDRM driver is registered with it. This TinyDRM driver expects that a DCS-compliant protocol is used by the DSI/DBI panel and can only be used with these. Signed-off-by: Paul Cercueil <paul@crapouillou.net> --- drivers/gpu/drm/tiny/Kconfig | 8 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/tiny-dsi.c | 262 ++++++++++++++++++++++++++++++++ include/drm/drm_mipi_dsi.h | 19 +++ 4 files changed, 290 insertions(+) create mode 100644 drivers/gpu/drm/tiny/tiny-dsi.c