Hi everybody,
While working on DT bindings for the Renesas Mobile SoC display controller (a.k.a. LCDC) I quickly realized that display panel implementation based on board code callbacks would need to be replaced by a driver-based panel framework.
Several driver-based panel support solution already exist in the kernel.
- The LCD device class is implemented in drivers/video/backlight/lcd.c and exposes a kernel API in include/linux/lcd.h. That API is tied to the FBDEV API for historical reason, uses board code callback for reset and power management, and doesn't include support for standard features available in today's "smart panels".
- OMAP2+ based systems use custom panel drivers available in drivers/video/omap2/displays. Those drivers are based on OMAP DSS (display controller) specific APIs.
- Similarly, Exynos based systems use custom panel drivers available in drivers/video/exynos. Only a single driver (s6e8ax0) is currently available. That driver is based on Exynos display controller specific APIs and on the LCD device class API.
I've brought up the issue with Tomi Valkeinen (OMAP DSS maintainer) and Marcus Lorentzon (working on panel support for ST/Linaro), and we agreed that a generic panel framework for display devices is needed. These patches implement a first proof of concept.
One of the main reasons for creating a new panel framework instead of adding missing features to the LCD framework is to avoid being tied to the FBDEV framework. Panels will be used by DRM drivers as well, and their API should thus be subsystem-agnostic. Note that the panel framework used the fb_videomode structure in its API, this will be replaced by a common video mode structure shared across subsystems (there's only so many hours per day).
Panels, as used in these patches, are defined as physical devices combining a matrix of pixels and a controller capable of driving that matrix.
Panel physical devices are registered as children of the control bus the panel controller is connected to (depending on the panel type, we can find platform devices for dummy panels with no control bus, or I2C, SPI, DBI, DSI, ... devices). The generic panel framework matches registered panel devices with panel drivers and call the panel drivers probe method, as done by other device classes in the kernel. The driver probe() method is responsible for instantiating a struct panel instance and registering it with the generic panel framework.
Display drivers are panel consumers. They register a panel notifier with the framework, which then calls the notifier when a matching panel is registered. The reason for this asynchronous mode of operation, compared to how drivers acquire regulator or clock resources, is that the panel can use resources provided by the display driver. For instance a panel can be a child of the DBI or DSI bus controlled by the display device, or use a clock provided by that device. We can't defer the display device probe until the panel is registered and also defer the panel device probe until the display is registered. As most display drivers need to handle output devices hotplug (HDMI monitors for instance), handling panel through a notification system seemed to be the easiest solution.
Note that this brings a different issue after registration, as display and panel drivers would take a reference to each other. Those circular references would make driver unloading impossible. I haven't found a good solution for that problem yet (hence the RFC state of those patches), and I would appreciate your input here. This might also be a hint that the framework design is wrong to start with. I guess I can't get everything right on the first try ;-)
Getting hold of the panel is the most complex part. Once done, display drivers can call abstract operations provided by panel drivers to control the panel operation. These patches implement three of those operations (enable, start transfer and get modes). More operations will be needed, and those three operations will likely get modified during review. Most of the panels on devices I own are dumb panels with no control bus, and are thus not the best candidates to design a framework that needs to take complex panels' needs into account.
In addition to the generic panel core, I've implemented MIPI DBI (Display Bus Interface, a parallel bus for panels that supports read/write transfers of commands and data) bus support, as well as three panel drivers (dummy panels with no control bus, and Renesas R61505- and R61517-based panels, both using DBI as their control bus). Only the dummy panel driver has been tested as I lack hardware for the two other drivers.
I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If you can find a clever way to solve the cyclic references issue described above I'll buy you a beer at the next conference we will both attend. If you think the proposed solution is too complex, or too simple, I'm all ears. I personally already feel that we might need something even more generic to support other kinds of external devices connected to display controllers, such as external DSI to HDMI converters for instance. Some kind of video entity exposing abstract operations like the panels do would make sense, in which case panels would "inherit" from that video entity.
Speaking of conferences, I will attend the KS/LPC in San Diego in a bit more than a week, and would be happy to discuss this topic face to face there.
Laurent Pinchart (5): video: Add generic display panel core video: panel: Add dummy panel support video: panel: Add MIPI DBI bus support video: panel: Add R61505 panel support video: panel: Add R61517 panel support
drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/panel/Kconfig | 37 +++ drivers/video/panel/Makefile | 5 + drivers/video/panel/panel-dbi.c | 217 +++++++++++++++ drivers/video/panel/panel-dummy.c | 103 +++++++ drivers/video/panel/panel-r61505.c | 520 ++++++++++++++++++++++++++++++++++++ drivers/video/panel/panel-r61517.c | 408 ++++++++++++++++++++++++++++ drivers/video/panel/panel.c | 269 +++++++++++++++++++ include/video/panel-dbi.h | 92 +++++++ include/video/panel-dummy.h | 25 ++ include/video/panel-r61505.h | 27 ++ include/video/panel-r61517.h | 28 ++ include/video/panel.h | 111 ++++++++ 14 files changed, 1844 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/Kconfig create mode 100644 drivers/video/panel/Makefile create mode 100644 drivers/video/panel/panel-dbi.c create mode 100644 drivers/video/panel/panel-dummy.c create mode 100644 drivers/video/panel/panel-r61505.c create mode 100644 drivers/video/panel/panel-r61517.c create mode 100644 drivers/video/panel/panel.c create mode 100644 include/video/panel-dbi.h create mode 100644 include/video/panel-dummy.h create mode 100644 include/video/panel-r61505.h create mode 100644 include/video/panel-r61517.h create mode 100644 include/video/panel.h
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/panel/Kconfig | 4 + drivers/video/panel/Makefile | 1 + drivers/video/panel/panel.c | 269 ++++++++++++++++++++++++++++++++++++++++++ include/video/panel.h | 111 +++++++++++++++++ 6 files changed, 387 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/Kconfig create mode 100644 drivers/video/panel/Makefile create mode 100644 drivers/video/panel/panel.c create mode 100644 include/video/panel.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 0217f74..2cc394e 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2448,6 +2448,7 @@ source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" source "drivers/video/exynos/Kconfig" source "drivers/video/backlight/Kconfig" +source "drivers/video/panel/Kconfig"
if VT source "drivers/video/console/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index ee8dafb..577240c 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -14,6 +14,7 @@ fb-objs := $(fb-y) obj-$(CONFIG_VT) += console/ obj-$(CONFIG_LOGO) += logo/ obj-y += backlight/ +obj-y += panel/
obj-$(CONFIG_EXYNOS_VIDEO) += exynos/
diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig new file mode 100644 index 0000000..26b1787 --- /dev/null +++ b/drivers/video/panel/Kconfig @@ -0,0 +1,4 @@ +menuconfig DISPLAY_PANEL + tristate "Display Panel" + ---help--- + Support for display panels for graphics devices. diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile new file mode 100644 index 0000000..cf5c5e2 --- /dev/null +++ b/drivers/video/panel/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_DISPLAY_PANEL) += panel.o diff --git a/drivers/video/panel/panel.c b/drivers/video/panel/panel.c new file mode 100644 index 0000000..cfca804 --- /dev/null +++ b/drivers/video/panel/panel.c @@ -0,0 +1,269 @@ +/* + * Display Panel + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> + +#include <video/panel.h> + +static LIST_HEAD(panel_list); +static LIST_HEAD(panel_notifiers); +static DEFINE_MUTEX(panel_mutex); + +/** + * panel_enable - Set the panel operation mode + * @panel: The panel + * @enable: Panel operation mode + * + * - PANEL_ENABLE_OFF turns the panel off completely, possibly including its + * power supplies. Communication with a panel in that mode is not possible. + * - PANEL_ENABLE_BLANK turns the panel on but keep the output blanked. Full + * communication with the panel is supported, including pixel data transfer. + * - PANEL_ENABLE_ON turns the whole panel on, including the output. + * + * Return 0 on success or a negative error code otherwise. + */ +int panel_enable(struct panel *panel, enum panel_enable_mode enable) +{ + int ret; + + if (panel->enable == enable) + return 0; + + if (!panel->ops || !panel->ops->enable) + return 0; + + ret = panel->ops->enable(panel, enable); + if (ret < 0) + return ret; + + panel->enable = enable; + return 0; +} +EXPORT_SYMBOL_GPL(panel_enable); + +/** + * panel_start_transfer - Start frame transfer + * @panel: The panel + * + * Make the panel ready to receive pixel data and start frame transfer. This + * operation can only be called if the panel is in BLANK or ON mode. + * + * Return 0 on success or a negative error code otherwise. + */ +int panel_start_transfer(struct panel *panel) +{ + if (!panel->ops || !panel->ops->start_transfer) + return 0; + + return panel->ops->start_transfer(panel); +} + +/** + * panel_get_modes - Get video modes supported by the panel + * @panel: The panel + * @modes: Pointer to an array of modes + * + * Fill the modes argument with a pointer to an array of video modes. The array + * is owned by the panel. + * + * Return the number of supported modes on success (including 0 if no mode is + * supported) or a negative error code otherwise. + */ +int panel_get_modes(struct panel *panel, const struct fb_videomode **modes) +{ + if (!panel->ops || !panel->ops->get_modes) + return 0; + + return panel->ops->get_modes(panel, modes); +} +EXPORT_SYMBOL_GPL(panel_get_modes); + +static void panel_release(struct kref *ref) +{ + struct panel *panel = container_of(ref, struct panel, ref); + + if (panel->release) + panel->release(panel); +} + +/** + * panel_get - get a reference to a panel + * @panel: the panel + * + * Return the panel pointer. + */ +struct panel *panel_get(struct panel *panel) +{ + if (panel == NULL) + return NULL; + + kref_get(&panel->ref); + return panel; +} +EXPORT_SYMBOL_GPL(panel_get); + +/** + * panel_put - release a reference to a panel + * @panel: the panel + * + * Releasing the last reference to a panel releases the panel itself. + */ +void panel_put(struct panel *panel) +{ + kref_put(&panel->ref, panel_release); +} +EXPORT_SYMBOL_GPL(panel_put); + +static int panel_notifier_match(struct panel *panel, + struct panel_notifier *notifier) +{ + return notifier->dev == NULL || + notifier->dev == panel->dev; +} + +/** + * panel_register_notifier - register a display panel notifier + * @notifier: panel notifier structure we want to register + * + * Panel notifiers are called to notify drivers of panel-related events for + * matching panels. + * + * Notifiers and panels are matched through the device they correspond to. If + * the notifier dev field is equal to the panel dev field the notifier will be + * called when an event is reported. Notifiers with a NULL dev field act as + * catch-all and will be called for all panels. + * + * Supported events are + * + * - PANEL_NOTIFIER_CONNECT reports panel connection and is sent at panel or + * notifier registration time + * - PANEL_NOTIFIER_DISCONNECT reports panel disconnection and is sent at panel + * unregistration time + * + * Registering a notifier sends PANEL_NOTIFIER_CONNECT events for all previously + * registered panels that match the notifiers. + * + * Return 0 on success. + */ +int panel_register_notifier(struct panel_notifier *notifier) +{ + struct panel *panel; + + mutex_lock(&panel_mutex); + list_add_tail(¬ifier->list, &panel_notifiers); + + list_for_each_entry(panel, &panel_list, list) { + if (!panel_notifier_match(panel, notifier)) + continue; + + if (notifier->notify(notifier, panel, PANEL_NOTIFIER_CONNECT)) + break; + } + mutex_unlock(&panel_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(panel_register_notifier); + +/** + * panel_unregister_notifier - unregister a display panel notifier + * @notifier: panel notifier to be unregistered + * + * Unregistration guarantees that the notifier will never be called upon return + * of this function. + */ +void panel_unregister_notifier(struct panel_notifier *notifier) +{ + mutex_lock(&panel_mutex); + list_del(¬ifier->list); + mutex_unlock(&panel_mutex); +} +EXPORT_SYMBOL_GPL(panel_unregister_notifier); + +/** + * panel_register - register a display panel + * @panel: panel to be registered + * + * Register the panel and send the PANEL_NOTIFIER_CONNECT event to all + * matching registered notifiers. + * + * Return 0 on success. + */ +int __must_check __panel_register(struct panel *panel, struct module *owner) +{ + struct panel_notifier *notifier; + + kref_init(&panel->ref); + panel->owner = owner; + panel->enable = PANEL_ENABLE_OFF; + + mutex_lock(&panel_mutex); + list_add(&panel->list, &panel_list); + + list_for_each_entry(notifier, &panel_notifiers, list) { + if (!panel_notifier_match(panel, notifier)) + continue; + + if (notifier->notify(notifier, panel, PANEL_NOTIFIER_CONNECT)) + break; + } + mutex_unlock(&panel_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(__panel_register); + +/** + * panel_unregister - unregister a display panel + * @panel: panel to be unregistered + * + * Unregister the panel and send the PANEL_NOTIFIER_DISCONNECT event to all + * matching registered notifiers. + */ +void panel_unregister(struct panel *panel) +{ + struct panel_notifier *notifier; + + mutex_lock(&panel_mutex); + list_for_each_entry(notifier, &panel_notifiers, list) { + if (!panel_notifier_match(panel, notifier)) + continue; + + notifier->notify(notifier, panel, PANEL_NOTIFIER_DISCONNECT); + } + + list_del(&panel->list); + mutex_unlock(&panel_mutex); + + panel_put(panel); +} +EXPORT_SYMBOL_GPL(panel_unregister); + +static int __init panel_init(void) +{ + return 0; +} + +static void __exit panel_exit(void) +{ +} + +module_init(panel_init); +module_exit(panel_exit) + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Display Panel Core"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel.h b/include/video/panel.h new file mode 100644 index 0000000..bb11141 --- /dev/null +++ b/include/video/panel.h @@ -0,0 +1,111 @@ +/* + * Display Panel + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PANEL_H__ +#define __PANEL_H__ + +#include <linux/fb.h> +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/module.h> + +struct panel; + +#define PANEL_NOTIFIER_CONNECT 1 +#define PANEL_NOTIFIER_DISCONNECT 2 + +struct panel_notifier { + int (*notify)(struct panel_notifier *, struct panel *, int); + struct device *dev; + struct list_head list; +}; + +enum panel_enable_mode { + PANEL_ENABLE_OFF, + PANEL_ENABLE_BLANK, + PANEL_ENABLE_ON, +}; + +struct panel_ops { + int (*enable)(struct panel *panel, enum panel_enable_mode enable); + int (*start_transfer)(struct panel *panel); + int (*get_modes)(struct panel *panel, + const struct fb_videomode **modes); +}; + +struct panel { + struct list_head list; + struct device *dev; + struct module *owner; + struct kref ref; + + const struct panel_ops *ops; + void(*release)(struct panel *panel); + + unsigned int width; + unsigned int height; + + enum panel_enable_mode enable; +}; + +/** + * panel_enable - Set the panel operation mode + * @panel: The panel + * @enable: Panel operation mode + * + * - PANEL_ENABLE_OFF turns the panel off completely, possibly including its + * power supplies. Communication with a panel in that mode is not possible. + * - PANEL_ENABLE_BLANK turns the panel on but keep the output blanked. Full + * communication with the panel is supported, including pixel data transfer. + * - PANEL_ENABLE_ON turns the whole panel on, including the output. + * + * Return 0 on success or a negative error code otherwise. + */ +int panel_enable(struct panel *panel, enum panel_enable_mode enable); + +/** + * panel_start_transfer - Start frame transfer + * @panel: The panel + * + * Make the panel ready to receive pixel data and start frame transfer. This + * operation can only be called if the panel is in BLANK or ON mode. + * + * Return 0 on success or a negative error code otherwise. + */ +int panel_start_transfer(struct panel *panel); + +/** + * panel_get_modes - Get video modes supported by the panel + * @panel: The panel + * @modes: Pointer to an array of modes + * + * Fill the modes argument with a pointer to an array of video modes. The array + * is owned by the panel. + * + * Return the number of supported modes on success (including 0 if no mode is + * supported) or a negative error code otherwise. + */ +int panel_get_modes(struct panel *panel, const struct fb_videomode **modes); + +struct panel *panel_get(struct panel *panel); +void panel_put(struct panel *panel); + +int __must_check __panel_register(struct panel *panel, struct module *owner); +void panel_unregister(struct panel *panel); + +int panel_register_notifier(struct panel_notifier *notifier); +void panel_unregister_notifier(struct panel_notifier *notifier); + +#define panel_register(panel) \ + __panel_register(panel, THIS_MODULE) + +#endif /* __PANEL_H__ */
Hi Laurent,
On Fri, Aug 17, 2012 at 02:49:39AM +0200, Laurent Pinchart wrote:
+/**
- panel_get_modes - Get video modes supported by the panel
- @panel: The panel
- @modes: Pointer to an array of modes
- Fill the modes argument with a pointer to an array of video modes. The array
- is owned by the panel.
- Return the number of supported modes on success (including 0 if no mode is
- supported) or a negative error code otherwise.
- */
+int panel_get_modes(struct panel *panel, const struct fb_videomode **modes) +{
- if (!panel->ops || !panel->ops->get_modes)
return 0;
- return panel->ops->get_modes(panel, modes);
+} +EXPORT_SYMBOL_GPL(panel_get_modes);
You have seen my of videomode helper proposal. One result there was that we want to have ranges for the margin/synclen fields. Does it make sense to base this new panel framework on a more sophisticated internal reprentation of the panel parameters? This could then be converted to struct fb_videomode / struct drm_display_mode as needed. This would also make it more suitable for drm drivers which are not interested in struct fb_videomode.
Related to this I suggest to change the API to be able to iterate over the different modes, like:
struct videomode *panel_get_mode(struct panel *panel, int num);
This was we do not have to have an array of modes in memory, which may be a burden for some panel drivers.
Sascha
Hi Sascha,
On Tuesday 04 September 2012 11:24:46 Sascha Hauer wrote:
Hi Laurent,
On Fri, Aug 17, 2012 at 02:49:39AM +0200, Laurent Pinchart wrote:
+/**
- panel_get_modes - Get video modes supported by the panel
- @panel: The panel
- @modes: Pointer to an array of modes
- Fill the modes argument with a pointer to an array of video modes. The
array
- is owned by the panel.
- Return the number of supported modes on success (including 0 if no
mode is
- supported) or a negative error code otherwise.
- */
+int panel_get_modes(struct panel *panel, const struct fb_videomode **modes) +{
- if (!panel->ops || !panel->ops->get_modes)
return 0;
- return panel->ops->get_modes(panel, modes);
+} +EXPORT_SYMBOL_GPL(panel_get_modes);
You have seen my of videomode helper proposal. One result there was that we want to have ranges for the margin/synclen fields. Does it make sense to base this new panel framework on a more sophisticated internal reprentation of the panel parameters?
I think it does, yes. We need a common video mode structure, and the panel framework should use it. I'll try to rebase my patches on top of your proposal. Have you posted the latest version ?
This could then be converted to struct fb_videomode / struct drm_display_mode as needed. This would also make it more suitable for drm drivers which are not interested in struct fb_videomode.
Related to this I suggest to change the API to be able to iterate over the different modes, like:
struct videomode *panel_get_mode(struct panel *panel, int num);
This was we do not have to have an array of modes in memory, which may be a burden for some panel drivers.
I currently have mixed feelings about this. Both approaches have pros and cons. Iterating over the modes would be more complex for drivers that use panels, and would be race-prone if the modes can change at runtime (OK, this isn't supported by the current panel API proposal).
On Thu, Sep 13, 2012 at 03:40:40AM +0200, Laurent Pinchart wrote:
Hi Sascha,
+int panel_get_modes(struct panel *panel, const struct fb_videomode **modes) +{
- if (!panel->ops || !panel->ops->get_modes)
return 0;
- return panel->ops->get_modes(panel, modes);
+} +EXPORT_SYMBOL_GPL(panel_get_modes);
You have seen my of videomode helper proposal. One result there was that we want to have ranges for the margin/synclen fields. Does it make sense to base this new panel framework on a more sophisticated internal reprentation of the panel parameters?
I think it does, yes. We need a common video mode structure, and the panel framework should use it. I'll try to rebase my patches on top of your proposal. Have you posted the latest version ?
V2 is the newest version. I'd like to implement ranges for the display timings which then makes for a new common video mode structure, which then could be used by drm and fbdev, probably with helper functions to convert from common videomode to drm/fbdev specific variants.
This could then be converted to struct fb_videomode / struct drm_display_mode as needed. This would also make it more suitable for drm drivers which are not interested in struct fb_videomode.
Related to this I suggest to change the API to be able to iterate over the different modes, like:
struct videomode *panel_get_mode(struct panel *panel, int num);
This was we do not have to have an array of modes in memory, which may be a burden for some panel drivers.
I currently have mixed feelings about this. Both approaches have pros and cons. Iterating over the modes would be more complex for drivers that use panels, and would be race-prone if the modes can change at runtime (OK, this isn't supported by the current panel API proposal).
I just remember that the array approach was painful when I worked on an fbdev driver some time ago. There some possible modes came from platform_data, other modes were default modes in the driver and all had to be merged into a single array. I don't remember the situation exactly, but it would have been simpler if it had been a list instead of an array.
Sascha
On Thu, Sep 13, 2012 at 01:29:54PM +0200, Sascha Hauer wrote:
You have seen my of videomode helper proposal. One result there was that we want to have ranges for the margin/synclen fields. Does it make sense to base this new panel framework on a more sophisticated internal reprentation of the panel parameters?
I think it does, yes. We need a common video mode structure, and the panel framework should use it. I'll try to rebase my patches on top of your proposal. Have you posted the latest version ?
V2 is the newest version. I'd like to implement ranges for the display timings which then makes for a new common video mode structure, which then could be used by drm and fbdev, probably with helper functions to convert from common videomode to drm/fbdev specific variants.
Steffen has a reworked series with the latest changes and will post them soon.
rsc
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/panel/Kconfig | 11 ++++ drivers/video/panel/Makefile | 1 + drivers/video/panel/panel-dummy.c | 103 +++++++++++++++++++++++++++++++++++++ include/video/panel-dummy.h | 25 +++++++++ 4 files changed, 140 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/panel-dummy.c create mode 100644 include/video/panel-dummy.h
diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig index 26b1787..36fb9ca 100644 --- a/drivers/video/panel/Kconfig +++ b/drivers/video/panel/Kconfig @@ -2,3 +2,14 @@ menuconfig DISPLAY_PANEL tristate "Display Panel" ---help--- Support for display panels for graphics devices. + +if DISPLAY_PANEL + +config DISPLAY_PANEL_DUMMY + tristate "Dummy Display Panel" + ---help--- + Support dummy panels with no control bus. + + If you are in doubt, say N. + +endif # DISPLAY_PANEL diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile index cf5c5e2..9fc05c2 100644 --- a/drivers/video/panel/Makefile +++ b/drivers/video/panel/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_DISPLAY_PANEL) += panel.o +obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o diff --git a/drivers/video/panel/panel-dummy.c b/drivers/video/panel/panel-dummy.c new file mode 100644 index 0000000..9ba1447 --- /dev/null +++ b/drivers/video/panel/panel-dummy.c @@ -0,0 +1,103 @@ +/* + * Dummy Display Panel + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> + +#include <video/panel-dummy.h> + +struct panel_dummy { + struct panel panel; + const struct panel_dummy_platform_data *pdata; +}; + +#define to_panel_dummy(p) container_of(p, struct panel_dummy, panel) + +static int panel_dummy_get_modes(struct panel *p, + const struct fb_videomode **modes) +{ + struct panel_dummy *panel = to_panel_dummy(p); + + *modes = panel->pdata->mode; + return 1; +} + +static const struct panel_ops panel_dummy_ops = { + .get_modes = panel_dummy_get_modes, +}; + +static void panel_dummy_release(struct panel *p) +{ + struct panel_dummy *panel = to_panel_dummy(p); + + kfree(panel); +} + +static int panel_dummy_remove(struct platform_device *pdev) +{ + struct panel_dummy *panel = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + panel_unregister(&panel->panel); + + return 0; +} + +static int __devinit panel_dummy_probe(struct platform_device *pdev) +{ + const struct panel_dummy_platform_data *pdata = pdev->dev.platform_data; + struct panel_dummy *panel; + int ret; + + if (pdata == NULL) + return -ENODEV; + + panel = kzalloc(sizeof(*panel), GFP_KERNEL); + if (panel == NULL) + return -ENOMEM; + + panel->pdata = pdata; + panel->panel.dev = &pdev->dev; + panel->panel.release = panel_dummy_release; + panel->panel.ops = &panel_dummy_ops; + panel->panel.width = pdata->width; + panel->panel.height = pdata->height; + + ret = panel_register(&panel->panel); + if (ret < 0) { + kfree(panel); + return ret; + } + + platform_set_drvdata(pdev, panel); + + return 0; +} + +static const struct dev_pm_ops panel_dummy_dev_pm_ops = { +}; + +static struct platform_driver panel_dummy_driver = { + .probe = panel_dummy_probe, + .remove = panel_dummy_remove, + .driver = { + .name = "panel_dummy", + .owner = THIS_MODULE, + .pm = &panel_dummy_dev_pm_ops, + }, +}; + +module_platform_driver(panel_dummy_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Dummy Display Panel"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-dummy.h b/include/video/panel-dummy.h new file mode 100644 index 0000000..558a297 --- /dev/null +++ b/include/video/panel-dummy.h @@ -0,0 +1,25 @@ +/* + * Dummy Display Panel + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PANEL_DUMMY_H__ +#define __PANEL_DUMMY_H__ + +#include <linux/fb.h> +#include <video/panel.h> + +struct panel_dummy_platform_data { + unsigned long width; /* Panel width in mm */ + unsigned long height; /* Panel height in mm */ + const struct fb_videomode *mode; +}; + +#endif /* __PANEL_DUMMY_H__ */
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/panel/Kconfig | 4 + drivers/video/panel/Makefile | 1 + drivers/video/panel/panel-dbi.c | 217 +++++++++++++++++++++++++++++++++++++++ include/video/panel-dbi.h | 92 +++++++++++++++++ 4 files changed, 314 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/panel-dbi.c create mode 100644 include/video/panel-dbi.h
diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig index 36fb9ca..fd0b3cf 100644 --- a/drivers/video/panel/Kconfig +++ b/drivers/video/panel/Kconfig @@ -12,4 +12,8 @@ config DISPLAY_PANEL_DUMMY
If you are in doubt, say N.
+config DISPLAY_PANEL_DBI + tristate + default n + endif # DISPLAY_PANEL diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile index 9fc05c2..2ab0520 100644 --- a/drivers/video/panel/Makefile +++ b/drivers/video/panel/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_DISPLAY_PANEL) += panel.o obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o +obj-$(CONFIG_DISPLAY_PANEL_DBI) += panel-dbi.o diff --git a/drivers/video/panel/panel-dbi.c b/drivers/video/panel/panel-dbi.c new file mode 100644 index 0000000..0511997 --- /dev/null +++ b/drivers/video/panel/panel-dbi.c @@ -0,0 +1,217 @@ +/* + * MIPI DBI Bus + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +#include <video/panel-dbi.h> + +/* ----------------------------------------------------------------------------- + * Bus operations + */ + +void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd) +{ + dev->bus->ops->write_command(dev->bus, cmd); +} +EXPORT_SYMBOL_GPL(panel_dbi_write_command); + +void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data) +{ + dev->bus->ops->write_data(dev->bus, data); +} +EXPORT_SYMBOL_GPL(panel_dbi_write_data); + +unsigned long panel_dbi_read_data(struct panel_dbi_device *dev) +{ + return dev->bus->ops->read_data(dev->bus); +} +EXPORT_SYMBOL_GPL(panel_dbi_read_data); + +/* ----------------------------------------------------------------------------- + * Bus type + */ + +static const struct panel_dbi_device_id * +panel_dbi_match_id(const struct panel_dbi_device_id *id, + struct panel_dbi_device *dev) +{ + while (id->name[0]) { + if (strcmp(dev->name, id->name) == 0) { + dev->id_entry = id; + return id; + } + id++; + } + return NULL; +} + +static int panel_dbi_match(struct device *_dev, struct device_driver *_drv) +{ + struct panel_dbi_device *dev = to_panel_dbi_device(_dev); + struct panel_dbi_driver *drv = to_panel_dbi_driver(_drv); + + if (drv->id_table) + return panel_dbi_match_id(drv->id_table, dev) != NULL; + + return (strcmp(dev->name, _drv->name) == 0); +} + +static ssize_t modalias_show(struct device *_dev, struct device_attribute *a, + char *buf) +{ + struct panel_dbi_device *dev = to_panel_dbi_device(_dev); + int len = snprintf(buf, PAGE_SIZE, PANEL_DBI_MODULE_PREFIX "%s\n", + dev->name); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} + +static struct device_attribute panel_dbi_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int panel_dbi_uevent(struct device *_dev, struct kobj_uevent_env *env) +{ + struct panel_dbi_device *dev = to_panel_dbi_device(_dev); + + add_uevent_var(env, "MODALIAS=%s%s", PANEL_DBI_MODULE_PREFIX, + dev->name); + return 0; +} + +static const struct dev_pm_ops panel_dbi_dev_pm_ops = { + .runtime_suspend = pm_generic_runtime_suspend, + .runtime_resume = pm_generic_runtime_resume, + .runtime_idle = pm_generic_runtime_idle, + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, +}; + +static struct bus_type panel_dbi_bus_type = { + .name = "mipi-dbi", + .dev_attrs = panel_dbi_dev_attrs, + .match = panel_dbi_match, + .uevent = panel_dbi_uevent, + .pm = &panel_dbi_dev_pm_ops, +}; + +/* ----------------------------------------------------------------------------- + * Device and driver (un)registration + */ + +/** + * panel_dbi_device_register - register a DBI device + * @dev: DBI device we're registering + */ +int panel_dbi_device_register(struct panel_dbi_device *dev, + struct panel_dbi_bus *bus) +{ + device_initialize(&dev->dev); + + dev->bus = bus; + dev->dev.bus = &panel_dbi_bus_type; + dev->dev.parent = bus->dev; + + if (dev->id != -1) + dev_set_name(&dev->dev, "%s.%d", dev->name, dev->id); + else + dev_set_name(&dev->dev, "%s", dev->name); + + return device_add(&dev->dev); +} +EXPORT_SYMBOL_GPL(panel_dbi_device_register); + +/** + * panel_dbi_device_unregister - unregister a DBI device + * @dev: DBI device we're unregistering + */ +void panel_dbi_device_unregister(struct panel_dbi_device *dev) +{ + device_del(&dev->dev); + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(panel_dbi_device_unregister); + +static int panel_dbi_drv_probe(struct device *_dev) +{ + struct panel_dbi_driver *drv = to_panel_dbi_driver(_dev->driver); + struct panel_dbi_device *dev = to_panel_dbi_device(_dev); + + return drv->probe(dev); +} + +static int panel_dbi_drv_remove(struct device *_dev) +{ + struct panel_dbi_driver *drv = to_panel_dbi_driver(_dev->driver); + struct panel_dbi_device *dev = to_panel_dbi_device(_dev); + + return drv->remove(dev); +} + +/** + * panel_dbi_driver_register - register a driver for DBI devices + * @drv: DBI driver structure + */ +int panel_dbi_driver_register(struct panel_dbi_driver *drv) +{ + drv->driver.bus = &panel_dbi_bus_type; + if (drv->probe) + drv->driver.probe = panel_dbi_drv_probe; + if (drv->remove) + drv->driver.remove = panel_dbi_drv_remove; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(panel_dbi_driver_register); + +/** + * panel_dbi_driver_unregister - unregister a driver for DBI devices + * @drv: DBI driver structure + */ +void panel_dbi_driver_unregister(struct panel_dbi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(panel_dbi_driver_unregister); + +/* ----------------------------------------------------------------------------- + * Init/exit + */ + +static int __init panel_dbi_init(void) +{ + return bus_register(&panel_dbi_bus_type); +} + +static void __exit panel_dbi_exit(void) +{ + bus_unregister(&panel_dbi_bus_type); +} + +module_init(panel_dbi_init); +module_exit(panel_dbi_exit) + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("MIPI DBI Bus"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-dbi.h b/include/video/panel-dbi.h new file mode 100644 index 0000000..799ac41 --- /dev/null +++ b/include/video/panel-dbi.h @@ -0,0 +1,92 @@ +/* + * MIPI DBI Bus + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PANEL_DBI_H__ +#define __PANEL_DBI_H__ + +#include <linux/device.h> +#include <video/panel.h> + +struct panel_dbi_bus; + +struct panel_dbi_bus_ops { + void (*write_command)(struct panel_dbi_bus *bus, unsigned long cmd); + void (*write_data)(struct panel_dbi_bus *bus, unsigned long data); + unsigned long (*read_data)(struct panel_dbi_bus *bus); +}; + +struct panel_dbi_bus { + struct device *dev; + const struct panel_dbi_bus_ops *ops; +}; + +#define PANEL_DBI_MODULE_PREFIX "mipi-dbi:" +#define PANEL_DBI_NAME_SIZE 32 + +struct panel_dbi_device_id { + char name[PANEL_DBI_NAME_SIZE]; + kernel_ulong_t driver_data /* Data private to the driver */ + __aligned(sizeof(kernel_ulong_t)); +}; + +struct panel_dbi_device { + const char *name; + int id; + struct device dev; + + const struct panel_dbi_device_id *id_entry; + struct panel_dbi_bus *bus; +}; + +#define to_panel_dbi_device(d) container_of(d, struct panel_dbi_device, dev) + +int panel_dbi_device_register(struct panel_dbi_device *dev, + struct panel_dbi_bus *bus); +void panel_dbi_device_unregister(struct panel_dbi_device *dev); + +struct panel_dbi_driver { + int(*probe)(struct panel_dbi_device *); + int(*remove)(struct panel_dbi_device *); + struct device_driver driver; + const struct panel_dbi_device_id *id_table; +}; + +#define to_panel_dbi_driver(d) container_of(d, struct panel_dbi_driver, driver) + +int panel_dbi_driver_register(struct panel_dbi_driver *drv); +void panel_dbi_driver_unregister(struct panel_dbi_driver *drv); + +static inline void *panel_dbi_get_drvdata(const struct panel_dbi_device *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void panel_dbi_set_drvdata(struct panel_dbi_device *dev, + void *data) +{ + dev_set_drvdata(&dev->dev, data); +} + +/* module_panel_dbi_driver() - Helper macro for drivers that don't do + * anything special in module init/exit. This eliminates a lot of + * boilerplate. Each module may only use this macro once, and + * calling it replaces module_init() and module_exit() + */ +#define module_panel_dbi_driver(__panel_dbi_driver) \ + module_driver(__panel_dbi_driver, panel_dbi_driver_register, \ + panel_dbi_driver_unregister) + +void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd); +void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data); +unsigned long panel_dbi_read_data(struct panel_dbi_device *dev); + +#endif /* __PANEL_DBI__ */
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
+/* -----------------------------------------------------------------------------
- Bus operations
- */
+void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd) +{
- dev->bus->ops->write_command(dev->bus, cmd);
+} +EXPORT_SYMBOL_GPL(panel_dbi_write_command);
+void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data) +{
- dev->bus->ops->write_data(dev->bus, data);
+} +EXPORT_SYMBOL_GPL(panel_dbi_write_data);
+unsigned long panel_dbi_read_data(struct panel_dbi_device *dev) +{
- return dev->bus->ops->read_data(dev->bus);
+} +EXPORT_SYMBOL_GPL(panel_dbi_read_data);
I'm not that familiar with how to implement bus drivers, can you describe in pseudo code how the SoC's DBI driver would register these?
I think write/read data functions are a bit limited. Shouldn't they be something like write_data(const u8 *buf, int size) and read_data(u8 *buf, int len)?
Something that's totally missing is configuring the DBI bus. There are a bunch of timing related values that need to be configured. See include/video/omapdss.h struct rfbi_timings. While the struct is OMAP specific, if I recall right most of the values match to the MIPI DBI spec.
And this makes me wonder, you use DBI bus for SYS-80 panel. The busses may look similar in operation, but are they really so similar when you take into account the timings (and perhaps something else, it's been years since I read the MIPI DBI spec)?
Then there's the start_transfer. This is something I'm not sure what is the best way to handle it, but the same problems that I mentioned in the previous post related to enable apply here also. For example, what if the panel needs to be update in two parts? This is done in Nokia N9. From panel's perspective, it'd be best to handle it somewhat like this (although asynchronously, probably):
write_update_area(0, 0, xres, yres / 2); write_memory_start() start_pixel_transfer();
wait_transfer_done();
write_update_area(0, yres / 2, xres, yres / 2); write_memory_start() start_pixel_transfer();
Why I said I'm not sure about this is that it does complicate things, as the actual pixel data often comes from the display subsystem hardware, which should probably be controlled by the display driver.
I think there also needs to be some kind of transfer_done notifier, for both the display driver and the panel driver. Although if the display driver handles starting the actual pixel transfer, then it'll get the transfer_done via whatever interrupt the SoC has.
Also as food for thought, videomode timings does not really make sense for DBI panels, at least when you just consider the DBI side. There's really just the resolution of the display, and then the DBI timings. No pck, syncs, etc. Of course in the end there's the actual panel, which does have these video timings. But often they cannot be configured, and often you don't even know them as the specs don't tell them.
Tomi
Hi Tomi,
Thank you for the review.
On Friday 17 August 2012 12:03:02 Tomi Valkeinen wrote:
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
+/*
---- + * Bus operations
- */
+void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd) +{
- dev->bus->ops->write_command(dev->bus, cmd);
+} +EXPORT_SYMBOL_GPL(panel_dbi_write_command);
+void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data) +{
- dev->bus->ops->write_data(dev->bus, data);
+} +EXPORT_SYMBOL_GPL(panel_dbi_write_data);
+unsigned long panel_dbi_read_data(struct panel_dbi_device *dev) +{
- return dev->bus->ops->read_data(dev->bus);
+} +EXPORT_SYMBOL_GPL(panel_dbi_read_data);
I'm not that familiar with how to implement bus drivers, can you describe in pseudo code how the SoC's DBI driver would register these?
Sure.
The DBI bus driver first needs to create a panel_dbi_bus_ops instance:
static const struct panel_dbi_bus_ops sh_mobile_lcdc_dbi_bus_ops = { .write_command = lcdc_dbi_write_command, .write_data = lcdc_dbi_write_data, .read_data = lcdc_dbi_read_data, };
and a panel_dbi_bus instance, usually embedded in its private driver data structure, and initialize it by setting its dev and ops fields:
ch->dbi_bus.dev = ch->lcdc->dev; ch->dbi_bus.ops = &sh_mobile_lcdc_dbi_bus_ops;
In my current implementation, the panel_dbi_device is created in board code:
static struct panel_dbi_device migor_panel_device = { .name = "r61505", .id = 0, .dev = { .platform_data = &migor_panel_info, }, };
A pointer to that structure is passed to the DBI master driver, which then registers the device:
panel_dbi_device_register(dbi, &ch->dbi_bus);
With a DT-based solution the DBI core will expose a function to register DBI devices from OF nodes.
The bus itself is currently not registered with the DBI code because there was no need to.
I think write/read data functions are a bit limited. Shouldn't they be something like write_data(const u8 *buf, int size) and read_data(u8 *buf, int len)?
Good point. My hardware doesn't support multi-byte read/write operations directly so I haven't thought about adding those.
Can your hardware group command + data writes in a single operation ? If so we should expose that at the API level as well.
Is DBI limited to 8-bit data transfers for commands ? Pixels can be transferred 16-bit at a time, commands might as well. While DCS only specifies 8-bit command/data, DBI panels that are not DCS compliant can use 16-bit command/data (the R61505 panel, albeit a SYS-80 panel, does so).
Something that's totally missing is configuring the DBI bus. There are a bunch of timing related values that need to be configured. See include/video/omapdss.h struct rfbi_timings. While the struct is OMAP specific, if I recall right most of the values match to the MIPI DBI spec.
I've left that out currently, and thought about passing that information as platform data to the DBI bus driver. That's the easiest solution, but I agree that it's a hack. Panel should expose their timing requirements to the DBI host. API wise that wouldn't be difficult (we only need to add timing information to the panel platform data and add a function to the DBI API to retrieve it), one of challenges might be to express it in a way that's both universal enough and easy to use for DBI bus drivers.
And this makes me wonder, you use DBI bus for SYS-80 panel. The busses may look similar in operation, but are they really so similar when you take into account the timings (and perhaps something else, it's been years since I read the MIPI DBI spec)?
I'll have to check all the details. SYS-80 is similar to DBI-B, but supports a wider bus width of 18 bits. I think the interfaces are similar enough to use a single bus implementation, possibly with quirks and/or options (see SCCB support in I2C for instance, with flags to ignore acks, force a stop bit generation, ...). We would duplicate lots of code if we had two different implementations, and would prevent a DBI panel to be attached to a SYS-80 host and vice-versa (the format is known to work).
Then there's the start_transfer. This is something I'm not sure what is the best way to handle it, but the same problems that I mentioned in the previous post related to enable apply here also. For example, what if the panel needs to be update in two parts? This is done in Nokia N9. From panel's perspective, it'd be best to handle it somewhat like this (although asynchronously, probably):
write_update_area(0, 0, xres, yres / 2); write_memory_start() start_pixel_transfer();
wait_transfer_done();
write_update_area(0, yres / 2, xres, yres / 2); write_memory_start() start_pixel_transfer();
Why I said I'm not sure about this is that it does complicate things, as the actual pixel data often comes from the display subsystem hardware, which should probably be controlled by the display driver.
I have no solution for this at the moment. That's an advanced (but definitely required) feature, I've tried to concentrate on the basics first.
I think there also needs to be some kind of transfer_done notifier, for both the display driver and the panel driver. Although if the display driver handles starting the actual pixel transfer, then it'll get the transfer_done via whatever interrupt the SoC has.
Also as food for thought, videomode timings does not really make sense for DBI panels, at least when you just consider the DBI side. There's really just the resolution of the display, and then the DBI timings. No pck, syncs, etc. Of course in the end there's the actual panel, which does have these video timings. But often they cannot be configured, and often you don't even know them as the specs don't tell them.
We might just need to provide fake timings. Video mode timings are at the core of display support in all drivers so we can't just get rid of them. The h/v front/back porch and sync won't be used by display drivers for DBI/DSI panels anyway.
On Fri, 2012-08-17 at 12:02 +0200, Laurent Pinchart wrote:
Hi Tomi,
Thank you for the review.
On Friday 17 August 2012 12:03:02 Tomi Valkeinen wrote:
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
+/*
---- + * Bus operations
- */
+void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd) +{
- dev->bus->ops->write_command(dev->bus, cmd);
+} +EXPORT_SYMBOL_GPL(panel_dbi_write_command);
+void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data) +{
- dev->bus->ops->write_data(dev->bus, data);
+} +EXPORT_SYMBOL_GPL(panel_dbi_write_data);
+unsigned long panel_dbi_read_data(struct panel_dbi_device *dev) +{
- return dev->bus->ops->read_data(dev->bus);
+} +EXPORT_SYMBOL_GPL(panel_dbi_read_data);
I'm not that familiar with how to implement bus drivers, can you describe in pseudo code how the SoC's DBI driver would register these?
Sure.
The DBI bus driver first needs to create a panel_dbi_bus_ops instance:
static const struct panel_dbi_bus_ops sh_mobile_lcdc_dbi_bus_ops = { .write_command = lcdc_dbi_write_command, .write_data = lcdc_dbi_write_data, .read_data = lcdc_dbi_read_data, };
Thanks for the example, I think it cleared up things a bit.
As I mentioned earlier, I really think "panel" is not right here. While the whole framework may be called panel framework, the bus drivers are not panels, and we should support external chips also, which are not panels either.
I think write/read data functions are a bit limited. Shouldn't they be something like write_data(const u8 *buf, int size) and read_data(u8 *buf, int len)?
Good point. My hardware doesn't support multi-byte read/write operations directly so I haven't thought about adding those.
OMAP HW doesn't support it either. Well, not quite true, as OMAP's system DMA could be used to write a buffer to the DBI output. But that's really the same as doing the write with a a loop with CPU.
But first, the data type should be byte, not unsigned long. How would you write 8 bits or 16 bits with your API? And second, if the function takes just u8, you'd need lots of calls to do simple writes.
Can your hardware group command + data writes in a single operation ? If so we should expose that at the API level as well.
No it can't. But with DCS that is a common operation, so we could have some helpers to send command + data with one call. Then again, I'd hope to have DCS somehow as a separate library, which would then use DBI/DSI/whatnot to actually send the data.
I'm not quite sure how easy that is because of the differences between the busses.
Is DBI limited to 8-bit data transfers for commands ? Pixels can be transferred 16-bit at a time, commands might as well. While DCS only specifies 8-bit command/data, DBI panels that are not DCS compliant can use 16-bit command/data (the R61505 panel, albeit a SYS-80 panel, does so).
I have to say I don't remember much about DBI =). Looking at OMAP's driver, which was made for omap2 and hasn't been much updated since, I see that there are 4 modes, 8/9/12/16 bits. I think that defines how many of the parallel data lines are used.
However, I don't think that matters for the panel driver when it wants to send data. The panel driver should just call dbi_write(buf, buf_len), and the dbi driver would send the data in the buffer according to the bus width.
Also note that some chips need to change the bus width on the fly. The chip used on N800 wants configuration to be done with 8-bits, and pixel transfers with 16-bits. Who knows why...
So I think this, and generally most of the configuration, should be somewhat dynamic, so that the panel driver can change them when it needs.
Something that's totally missing is configuring the DBI bus. There are a bunch of timing related values that need to be configured. See include/video/omapdss.h struct rfbi_timings. While the struct is OMAP specific, if I recall right most of the values match to the MIPI DBI spec.
I've left that out currently, and thought about passing that information as platform data to the DBI bus driver. That's the easiest solution, but I agree that it's a hack. Panel should expose their timing requirements to the DBI host. API wise that wouldn't be difficult (we only need to add timing information to the panel platform data and add a function to the DBI API to retrieve it), one of challenges might be to express it in a way that's both universal enough and easy to use for DBI bus drivers.
As I pointed above, I think the panel driver shouldn't expose it, but the panel driver should somehow set it. Or at least allowed to change it in some manner. This is actually again, the same problem as with enable and transfer: who controls what's going on.
How I think it should work is something like:
mipi_dbi_set_timings(dbi_dev, mytimings); mipi_dbi_set_bus_width(dbi_dev, 8); mipi_dbi_write(dbi_dev, ...); mipi_dbi_set_bus_width(dbi_dev, 16); start_frame_transfer(dbi_dev, ...);
And this makes me wonder, you use DBI bus for SYS-80 panel. The busses may look similar in operation, but are they really so similar when you take into account the timings (and perhaps something else, it's been years since I read the MIPI DBI spec)?
I'll have to check all the details. SYS-80 is similar to DBI-B, but supports a wider bus width of 18 bits. I think the interfaces are similar enough to use a single bus implementation, possibly with quirks and/or options (see SCCB support in I2C for instance, with flags to ignore acks, force a stop bit generation, ...). We would duplicate lots of code if we had two different implementations, and would prevent a DBI panel to be attached to a SYS-80 host and vice-versa (the format is known to work).
Ah ok, if a DBI panel can be connected to SYS-80 output and vice versa, then I agree they are similar enough.
We might just need to provide fake timings. Video mode timings are at the core of display support in all drivers so we can't just get rid of them. The h/v front/back porch and sync won't be used by display drivers for DBI/DSI panels anyway.
Right. But we should probably think if we can, at the panel level, easily separate conventional panels and smart panels. Then this framework wouldn't need to fake the timings, and it'd be up to the higher level to decide if and how to fake them. Then again, this is no biggie. Just thought that at the lowest level it'd be nice to be "correct" and leave faking to upper layers =).
Tomi
Hi Tomi,
On Friday 17 August 2012 13:51:49 Tomi Valkeinen wrote:
On Fri, 2012-08-17 at 12:02 +0200, Laurent Pinchart wrote:
On Friday 17 August 2012 12:03:02 Tomi Valkeinen wrote:
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
+/*
---- + * Bus operations
- */
+void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd) +{
- dev->bus->ops->write_command(dev->bus, cmd);
+} +EXPORT_SYMBOL_GPL(panel_dbi_write_command);
+void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data) +{
- dev->bus->ops->write_data(dev->bus, data);
+} +EXPORT_SYMBOL_GPL(panel_dbi_write_data);
+unsigned long panel_dbi_read_data(struct panel_dbi_device *dev) +{
- return dev->bus->ops->read_data(dev->bus);
+} +EXPORT_SYMBOL_GPL(panel_dbi_read_data);
I'm not that familiar with how to implement bus drivers, can you describe in pseudo code how the SoC's DBI driver would register these?
Sure.
The DBI bus driver first needs to create a panel_dbi_bus_ops instance:
static const struct panel_dbi_bus_ops sh_mobile_lcdc_dbi_bus_ops = {
.write_command = lcdc_dbi_write_command, .write_data = lcdc_dbi_write_data, .read_data = lcdc_dbi_read_data,
};
Thanks for the example, I think it cleared up things a bit.
As I mentioned earlier, I really think "panel" is not right here. While the whole framework may be called panel framework, the bus drivers are not panels, and we should support external chips also, which are not panels either.
I agree. I've renamed panel_dbi_* to mipi_dbi_*.
I think write/read data functions are a bit limited. Shouldn't they be something like write_data(const u8 *buf, int size) and read_data(u8 *buf, int len)?
Good point. My hardware doesn't support multi-byte read/write operations directly so I haven't thought about adding those.
OMAP HW doesn't support it either. Well, not quite true, as OMAP's system DMA could be used to write a buffer to the DBI output. But that's really the same as doing the write with a a loop with CPU.
But first, the data type should be byte, not unsigned long. How would you write 8 bits or 16 bits with your API?
u8 and u16 both fit in an unsigned long :-) Please see below.
And second, if the function takes just u8, you'd need lots of calls to do simple writes.
I agree, an array write function is a good idea.
Can your hardware group command + data writes in a single operation ? If so we should expose that at the API level as well.
No it can't. But with DCS that is a common operation, so we could have some helpers to send command + data with one call.
Agreed.
Then again, I'd hope to have DCS somehow as a separate library, which would then use DBI/DSI/whatnot to actually send the data.
I'm not quite sure how easy that is because of the differences between the busses.
Is DBI limited to 8-bit data transfers for commands ? Pixels can be transferred 16-bit at a time, commands might as well. While DCS only specifies 8-bit command/data, DBI panels that are not DCS compliant can use 16-bit command/data (the R61505 panel, albeit a SYS-80 panel, does so).
I have to say I don't remember much about DBI =). Looking at OMAP's driver, which was made for omap2 and hasn't been much updated since, I see that there are 4 modes, 8/9/12/16 bits. I think that defines how many of the parallel data lines are used.
SYS-80 also has an 18-bits mode, where bits 0 and 9 are always ignored when transferring instructions and data other than pixels (for pixels the 18-bits bus width can be used to transfer RGB666 in a single clock cycle).
See page 87 of http://read.pudn.com/downloads91/sourcecode/others/348230/e61505_103a.pdf.
However, I don't think that matters for the panel driver when it wants to send data. The panel driver should just call dbi_write(buf, buf_len), and the dbi driver would send the data in the buffer according to the bus width.
According to the DCS specification, commands and parameters are transferred using 8-bit data. Non-DCS panels can however use wider commands and parameters (all commands and parameters are 16-bits wide for the R61505 for instance).
We can add an API to switch the DBI bus width on the fly. For Renesas hardware this would "just" require shifting bits around to output the 8-bit or 16-bit commands on the right data lines (the R61505 uses D17-D9 in 8-bit mode, while the DCS specification mentions D7-D0) based on how the panel is connected and on which lines the panel expects data.
As commands can be expressed on either 8 or 16 bits I would use a 16 type for them.
For parameters, we can either express everything as u8 * in the DBI bus operations, or use a union similar to i2c_smbus_data
union i2c_smbus_data { __u8 byte; __u16 word; __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */ /* and one more for user-space compatibility */ };
Helper functions would be available to perform 8-bit, 16-bit or n*8 bits transfers.
Would that work for your use cases ?
Also note that some chips need to change the bus width on the fly. The chip used on N800 wants configuration to be done with 8-bits, and pixel transfers with 16-bits. Who knows why...
On which data lines is configuration performed ? D7-D0 ?
So I think this, and generally most of the configuration, should be somewhat dynamic, so that the panel driver can change them when it needs.
Something that's totally missing is configuring the DBI bus. There are a bunch of timing related values that need to be configured. See include/video/omapdss.h struct rfbi_timings. While the struct is OMAP specific, if I recall right most of the values match to the MIPI DBI spec.
I've left that out currently, and thought about passing that information as platform data to the DBI bus driver. That's the easiest solution, but I agree that it's a hack. Panel should expose their timing requirements to the DBI host. API wise that wouldn't be difficult (we only need to add timing information to the panel platform data and add a function to the DBI API to retrieve it), one of challenges might be to express it in a way that's both universal enough and easy to use for DBI bus drivers.
As I pointed above, I think the panel driver shouldn't expose it, but the panel driver should somehow set it. Or at least allowed to change it in some manner. This is actually again, the same problem as with enable and transfer: who controls what's going on.
How I think it should work is something like:
mipi_dbi_set_timings(dbi_dev, mytimings); mipi_dbi_set_bus_width(dbi_dev, 8); mipi_dbi_write(dbi_dev, ...); mipi_dbi_set_bus_width(dbi_dev, 16); start_frame_transfer(dbi_dev, ...);
I'll first implement bus width setting.
And this makes me wonder, you use DBI bus for SYS-80 panel. The busses may look similar in operation, but are they really so similar when you take into account the timings (and perhaps something else, it's been years since I read the MIPI DBI spec)?
I'll have to check all the details. SYS-80 is similar to DBI-B, but supports a wider bus width of 18 bits. I think the interfaces are similar enough to use a single bus implementation, possibly with quirks and/or options (see SCCB support in I2C for instance, with flags to ignore acks, force a stop bit generation, ...). We would duplicate lots of code if we had two different implementations, and would prevent a DBI panel to be attached to a SYS-80 host and vice-versa (the format is known to work).
Ah ok, if a DBI panel can be connected to SYS-80 output and vice versa, then I agree they are similar enough.
Not all combination will work (a SYS panel that requires 16-bit transfers won't work with a DBI host that only supports 8-bit), but some do.
We might just need to provide fake timings. Video mode timings are at the core of display support in all drivers so we can't just get rid of them. The h/v front/back porch and sync won't be used by display drivers for DBI/DSI panels anyway.
Right. But we should probably think if we can, at the panel level, easily separate conventional panels and smart panels. Then this framework wouldn't need to fake the timings, and it'd be up to the higher level to decide if and how to fake them. Then again, this is no biggie. Just thought that at the lowest level it'd be nice to be "correct" and leave faking to upper layers =).
But we would then have two different APIs at the lower level depending on the panel type. I'm not sure that's a good thing.
On Fri, 2012-08-17 at 14:33 +0200, Laurent Pinchart wrote:
But first, the data type should be byte, not unsigned long. How would you write 8 bits or 16 bits with your API?
u8 and u16 both fit in an unsigned long :-) Please see below.
Ah, I see, so the driver would just discard 24 bits or 16 bits from the ulong. I somehow thought that if you have 8 bit bus, and you call the write with ulong, 4 bytes will be written.
Then again, I'd hope to have DCS somehow as a separate library, which would then use DBI/DSI/whatnot to actually send the data.
I'm not quite sure how easy that is because of the differences between the busses.
Is DBI limited to 8-bit data transfers for commands ? Pixels can be transferred 16-bit at a time, commands might as well. While DCS only specifies 8-bit command/data, DBI panels that are not DCS compliant can use 16-bit command/data (the R61505 panel, albeit a SYS-80 panel, does so).
I have to say I don't remember much about DBI =). Looking at OMAP's driver, which was made for omap2 and hasn't been much updated since, I see that there are 4 modes, 8/9/12/16 bits. I think that defines how many of the parallel data lines are used.
SYS-80 also has an 18-bits mode, where bits 0 and 9 are always ignored when transferring instructions and data other than pixels (for pixels the 18-bits bus width can be used to transfer RGB666 in a single clock cycle).
See page 87 of http://read.pudn.com/downloads91/sourcecode/others/348230/e61505_103a.pdf.
However, I don't think that matters for the panel driver when it wants to send data. The panel driver should just call dbi_write(buf, buf_len), and the dbi driver would send the data in the buffer according to the bus width.
According to the DCS specification, commands and parameters are transferred using 8-bit data. Non-DCS panels can however use wider commands and parameters (all commands and parameters are 16-bits wide for the R61505 for instance).
We can add an API to switch the DBI bus width on the fly. For Renesas hardware this would "just" require shifting bits around to output the 8-bit or 16-bit commands on the right data lines (the R61505 uses D17-D9 in 8-bit mode, while the DCS specification mentions D7-D0) based on how the panel is connected and on which lines the panel expects data.
As commands can be expressed on either 8 or 16 bits I would use a 16 type for them.
I won't put my head on the block, but I don't think DBI has any restriction on the size of the command. A "command" just means a data transfer while keeping the D/CX line low, and "data" when the line is high. Similar transfers for n bytes can be done in both modes.
For parameters, we can either express everything as u8 * in the DBI bus operations, or use a union similar to i2c_smbus_data
union i2c_smbus_data { __u8 byte; __u16 word; __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */ /* and one more for user-space compatibility */ };
There's no DBI_BLOCK_MAX, so at least identical union won't work. I think it's simplest to have u8 * function as a base, and then a few helpers to write the most common datatypes.
So we could have on the lowest level something like:
dbi_write_command(u8 *buf, size_t size); dbi_write_data(u8 *buf, size_t size);
And possible helpers:
dbi_write_data(u8 *cmd_buf, size_t cmd_size, u8 *data_buf, size_t data_size);
dbi_write_dcs(u8 cmd, u8 *data, size_t size);
And variations:
dbi_write_dcs_0(u8 cmd); dbi_write_dcs_1(u8 cmd, u8 data);
etc. So a simple helper to send 16 bits would be:
dbi_write_data(u16 data) { // or are the bytes the other way around... u8 buf[2] = { data & 0xff, (data >> 8) & 0xff }; return dbi_write_data(buf, 2); }
Helper functions would be available to perform 8-bit, 16-bit or n*8 bits transfers.
Would that work for your use cases ?
Also note that some chips need to change the bus width on the fly. The chip used on N800 wants configuration to be done with 8-bits, and pixel transfers with 16-bits. Who knows why...
On which data lines is configuration performed ? D7-D0 ?
I guess so, but does it matter? All the bus driver needs to know is how to send 8/16/.. bit data. On OMAP we just write the data to a 32 bit register, and the HW takes the lowest n bits. Do the bits represent the data lines directly on Renesans?
The omap driver actually only implements 8 and 16 bit modes, not the 9 and 12 bit modes. I'm not sure what kind of shifting is needed for those.
We might just need to provide fake timings. Video mode timings are at the core of display support in all drivers so we can't just get rid of them. The h/v front/back porch and sync won't be used by display drivers for DBI/DSI panels anyway.
Right. But we should probably think if we can, at the panel level, easily separate conventional panels and smart panels. Then this framework wouldn't need to fake the timings, and it'd be up to the higher level to decide if and how to fake them. Then again, this is no biggie. Just thought that at the lowest level it'd be nice to be "correct" and leave faking to upper layers =).
But we would then have two different APIs at the lower level depending on the panel type. I'm not sure that's a good thing.
Different API for what? Why anyway need panel type specific functions. In the panel struct we could just have an union of the different types of parameters for different types of panels.
But if this complicates things, it's not a biggie. Just something that has been in my mind when dealing with smart panels and assigning dummy video timings for them =).
Tomi
Hi Tomi,
On Friday 17 August 2012 16:06:30 Tomi Valkeinen wrote:
On Fri, 2012-08-17 at 14:33 +0200, Laurent Pinchart wrote:
But first, the data type should be byte, not unsigned long. How would you write 8 bits or 16 bits with your API?
u8 and u16 both fit in an unsigned long :-) Please see below.
Ah, I see, so the driver would just discard 24 bits or 16 bits from the ulong.
That's right.
I somehow thought that if you have 8 bit bus, and you call the write with ulong, 4 bytes will be written.
Then again, I'd hope to have DCS somehow as a separate library, which would then use DBI/DSI/whatnot to actually send the data.
I'm not quite sure how easy that is because of the differences between the busses.
Is DBI limited to 8-bit data transfers for commands ? Pixels can be transferred 16-bit at a time, commands might as well. While DCS only specifies 8-bit command/data, DBI panels that are not DCS compliant can use 16-bit command/data (the R61505 panel, albeit a SYS-80 panel, does so).
I have to say I don't remember much about DBI =). Looking at OMAP's driver, which was made for omap2 and hasn't been much updated since, I see that there are 4 modes, 8/9/12/16 bits. I think that defines how many of the parallel data lines are used.
SYS-80 also has an 18-bits mode, where bits 0 and 9 are always ignored when transferring instructions and data other than pixels (for pixels the 18-bits bus width can be used to transfer RGB666 in a single clock cycle).
See page 87 of http://read.pudn.com/downloads91/sourcecode/others/348230/e61505_103a.pdf.
However, I don't think that matters for the panel driver when it wants to send data. The panel driver should just call dbi_write(buf, buf_len), and the dbi driver would send the data in the buffer according to the bus width.
According to the DCS specification, commands and parameters are transferred using 8-bit data. Non-DCS panels can however use wider commands and parameters (all commands and parameters are 16-bits wide for the R61505 for instance).
We can add an API to switch the DBI bus width on the fly. For Renesas hardware this would "just" require shifting bits around to output the 8-bit or 16-bit commands on the right data lines (the R61505 uses D17-D9 in 8-bit mode, while the DCS specification mentions D7-D0) based on how the panel is connected and on which lines the panel expects data.
As commands can be expressed on either 8 or 16 bits I would use a 16 type for them.
I won't put my head on the block, but I don't think DBI has any restriction on the size of the command. A "command" just means a data transfer while keeping the D/CX line low, and "data" when the line is high. Similar transfers for n bytes can be done in both modes.
Right. I'll see if the API could be simplified by having a single write callback function with a data/command parameter.
For parameters, we can either express everything as u8 * in the DBI bus operations, or use a union similar to i2c_smbus_data
union i2c_smbus_data {
__u8 byte; __u16 word; __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */ /* and one more for user-space compatibility */
};
There's no DBI_BLOCK_MAX, so at least identical union won't work. I think it's simplest to have u8 * function as a base, and then a few helpers to write the most common datatypes.
OK, that sounds good to me.
So we could have on the lowest level something like:
dbi_write_command(u8 *buf, size_t size); dbi_write_data(u8 *buf, size_t size);
And possible helpers:
dbi_write_data(u8 *cmd_buf, size_t cmd_size, u8 *data_buf, size_t data_size);
dbi_write_dcs(u8 cmd, u8 *data, size_t size);
And variations:
dbi_write_dcs_0(u8 cmd); dbi_write_dcs_1(u8 cmd, u8 data);
etc. So a simple helper to send 16 bits would be:
dbi_write_data(u16 data) { // or are the bytes the other way around... u8 buf[2] = { data & 0xff, (data >> 8) & 0xff }; return dbi_write_data(buf, 2); }
Helper functions would be available to perform 8-bit, 16-bit or n*8 bits transfers.
Would that work for your use cases ?
Also note that some chips need to change the bus width on the fly. The chip used on N800 wants configuration to be done with 8-bits, and pixel transfers with 16-bits. Who knows why...
On which data lines is configuration performed ? D7-D0 ?
I guess so, but does it matter? All the bus driver needs to know is how to send 8/16/.. bit data. On OMAP we just write the data to a 32 bit register, and the HW takes the lowest n bits. Do the bits represent the data lines directly on Renesans?
Yes they do. For a SYS-80 panel configured in 18-bits mode, I'll have to write
((data & 0xff00) << 2) | ((data & 0x00ff) << 1)
(d15:8 -> D17:10, d7:0 -> D8:1 where d is the word to be written, and D the physical lines)
to the hardware data register and trigger the write. In 8 bits mode, there would be two write operations with
(data & 0xff00) << 2 (data & 0x00ff) << 10
(d15:8 -> D17:10, d7:0 -> D17:10)
However, when writing a 8-bit command to a DBI panel in either 16- or 8-bits mode, there would be a single write with
d7:0 -> D7:0
How to shift the data thus depends both on the bus width and on which data lines the panel expects data to be present.
I wrote drivers for two DBI panels based on existing board code, the R61505 and the R61517.
The R61505 datasheet is available online, the panel is a SYS-80 panel that supports 8-, 9-, 16- and 18-bits bus widths. It aligns data towards the MSB when using a bus width lower than 18.
The R61517 datasheet doesn't seem to be freely available. The panel seems to be DBI-compliant as it uses a subset of the DCS commands and a wide range of panel-specific commands. The panel is connected using a 16-bit bus, all commands and parameters are 8-bits wide and aligned towards the LSB.
To properly transfer commands and parameters, the DBI host will need to know on how many bits to perform transfers, and how to align data on the bus. For the former, your mipi_dbi_set_bus_width() function could be used, although probably not out of the box. The R61505 panel would call mipi_dbi_set_bus_width() to set the bus width to 16 (as commands and parameters are 16-bits wide), but if the panel is connected using only 8 or 9 data lines, the host would need to split the 16-bits writes into two 8-bits writes. Should that be done transparently ? mipi_dbi_set_bus_width() could possibly act as a mipi_dbi_set_max_bus_width(), but that might be a bit too hackish.
I'd like to hide as much of the complexity as possible in mipi-dbi-bus.c but I don't know whether that's possible.
The omap driver actually only implements 8 and 16 bit modes, not the 9 and 12 bit modes. I'm not sure what kind of shifting is needed for those.
There's no 12-bits mode in DBI-2 as far as I can tell.
We will need to support 8-, 9- and 16-bits modes for DBI-2, and additionally 18-bits mode for SYS-80.
We might just need to provide fake timings. Video mode timings are at the core of display support in all drivers so we can't just get rid of them. The h/v front/back porch and sync won't be used by display drivers for DBI/DSI panels anyway.
Right. But we should probably think if we can, at the panel level, easily separate conventional panels and smart panels. Then this framework wouldn't need to fake the timings, and it'd be up to the higher level to decide if and how to fake them. Then again, this is no biggie. Just thought that at the lowest level it'd be nice to be "correct" and leave faking to upper layers =).
But we would then have two different APIs at the lower level depending on the panel type. I'm not sure that's a good thing.
Different API for what? Why anyway need panel type specific functions. In the panel struct we could just have an union of the different types of parameters for different types of panels.
But if this complicates things, it's not a biggie. Just something that has been in my mind when dealing with smart panels and assigning dummy video timings for them =).
Please feel free to make a proposal for this when I'll post v2. A patch would be nice :-)
The R61505 is a SYS-80 bus panel controller from Renesas.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/panel/Kconfig | 9 + drivers/video/panel/Makefile | 1 + drivers/video/panel/panel-r61505.c | 520 ++++++++++++++++++++++++++++++++++++ include/video/panel-r61505.h | 27 ++ 4 files changed, 557 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/panel-r61505.c create mode 100644 include/video/panel-r61505.h
diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig index fd0b3cf..12d7712 100644 --- a/drivers/video/panel/Kconfig +++ b/drivers/video/panel/Kconfig @@ -16,4 +16,13 @@ config DISPLAY_PANEL_DBI tristate default n
+config DISPLAY_PANEL_R61505 + tristate "Renesas R61505-based Display Panel" + select DISPLAY_PANEL_DBI + ---help--- + Support panels based on the Renesas R61505 panel controller. + Those panels are controlled through a MIPI DBI interface. + + If you are in doubt, say N. + endif # DISPLAY_PANEL diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile index 2ab0520..e4fb9fe 100644 --- a/drivers/video/panel/Makefile +++ b/drivers/video/panel/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_DISPLAY_PANEL) += panel.o obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o obj-$(CONFIG_DISPLAY_PANEL_DBI) += panel-dbi.o +obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o diff --git a/drivers/video/panel/panel-r61505.c b/drivers/video/panel/panel-r61505.c new file mode 100644 index 0000000..e09455e --- /dev/null +++ b/drivers/video/panel/panel-r61505.c @@ -0,0 +1,520 @@ +/* + * Renesas R61505-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Based on SuperH MigoR Quarter VGA LCD Panel + * Copyright (C) 2008 Magnus Damm + * Based on lcd_powertip.c from Kenati Technologies Pvt Ltd. + * Copyright (c) 2007 Ujjwal Pande + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include <video/panel-r61505.h> +#include <video/panel-dbi.h> + +#define R61505_DEVICE_CODE 0x0000 +#define R61505_DEVICE_CODE_VALUE 0x1505 +#define R61505_DRIVER_OUTPUT_CONTROL 0x0001 +#define R61505_DRIVER_OUTPUT_CONTROL_SM (1 << 10) +#define R61505_DRIVER_OUTPUT_CONTROL_SS (1 << 8) +#define R61505_LCD_WAVEFORM 0x0002 +#define R61505_LCD_WAVEFORM_BC0 (1 << 9) +#define R61505_LCD_WAVEFORM_EOR (1 << 8) +#define R61505_ENTRY_MODE 0x0003 +#define R61505_ENTRY_MODE_TRIREG (1 << 15) +#define R61505_ENTRY_MODE_DFM (1 << 14) +#define R61505_ENTRY_MODE_BGR (1 << 12) +#define R61505_ENTRY_MODE_HWM (1 << 9) +#define R61505_ENTRY_MODE_ORG (1 << 7) +#define R61505_ENTRY_MODE_ID1 (1 << 5) +#define R61505_ENTRY_MODE_ID0 (1 << 4) +#define R61505_ENTRY_MODE_AM (1 << 3) +#define R61505_RESIZE_CONTROL 0x0004 +#define R61505_RESIZE_CONTROL_RCV(n) (((n) & 3) << 8) +#define R61505_RESIZE_CONTROL_RCH(n) (((n) & 3) << 4) +#define R61505_RESIZE_CONTROL_RSZ_4 (3 << 0) +#define R61505_RESIZE_CONTROL_RSZ_2 (1 << 0) +#define R61505_RESIZE_CONTROL_RSZ_1 (0 << 0) +#define R61505_DISPLAY_CONTROL1 0x0007 +#define R61505_DISPLAY_CONTROL1_PTDE1 (1 << 13) +#define R61505_DISPLAY_CONTROL1_PTDE0 (1 << 12) +#define R61505_DISPLAY_CONTROL1_BASEE (1 << 8) +#define R61505_DISPLAY_CONTROL1_VON (1 << 6) +#define R61505_DISPLAY_CONTROL1_GON (1 << 5) +#define R61505_DISPLAY_CONTROL1_DTE (1 << 4) +#define R61505_DISPLAY_CONTROL1_COL (1 << 3) +#define R61505_DISPLAY_CONTROL1_D1 (1 << 1) +#define R61505_DISPLAY_CONTROL1_D0 (1 << 0) +#define R61505_DISPLAY_CONTROL2 0x0008 +#define R61505_DISPLAY_CONTROL2_FP(n) (((n) & 0xf) << 8) +#define R61505_DISPLAY_CONTROL2_BP(n) (((n) & 0xf) << 0) +#define R61505_DISPLAY_CONTROL3 0x0009 +#define R61505_DISPLAY_CONTROL3_PTS(n) (((n) & 7) << 8) +#define R61505_DISPLAY_CONTROL3_PTG(n) (((n) & 3) << 3) +#define R61505_DISPLAY_CONTROL3_ICS(n) (((n) & 0xf) << 0) +#define R61505_DISPLAY_CONTROL4 0x000a +#define R61505_DISPLAY_CONTROL4_FMARKOE (1 << 3) +#define R61505_DISPLAY_CONTROL4_FMI_6 (5 << 0) +#define R61505_DISPLAY_CONTROL4_FMI_4 (3 << 0) +#define R61505_DISPLAY_CONTROL4_FMI_2 (1 << 0) +#define R61505_DISPLAY_CONTROL4_FMI_1 (0 << 0) +#define R61505_EXT_DISPLAY_IF_CONTROL1 0x000c +#define R61505_EXT_DISPLAY_IF_CONTROL1_ENC(n) (((n) & 7) << 12) +#define R61505_EXT_DISPLAY_IF_CONTROL1_RM (1 << 8) +#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_VSYNC (2 << 4) +#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_RGB (1 << 4) +#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_ICLK (0 << 4) +#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_6 (2 << 0) +#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_16 (1 << 0) +#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_18 (0 << 0) +#define R61505_FRAME_MARKER_CONTROL 0x000d +#define R61505_FRAME_MARKER_CONTROL_FMP(n) (((n) & 0x1ff) << 0) +#define R61505_EXT_DISPLAY_IF_CONTROL2 0x000f +#define R61505_POWER_CONTROL1 0x0010 +#define R61505_POWER_CONTROL1_SAP (1 << 12) +#define R61505_POWER_CONTROL1_BT(n) (((n) & 0xf) << 8) +#define R61505_POWER_CONTROL1_APE (1 << 7) +#define R61505_POWER_CONTROL1_AP_100 (3 << 4) +#define R61505_POWER_CONTROL1_AP_075 (2 << 4) +#define R61505_POWER_CONTROL1_AP_050 (1 << 4) +#define R61505_POWER_CONTROL1_AP_HALT (0 << 4) +#define R61505_POWER_CONTROL1_DSTB (1 << 2) +#define R61505_POWER_CONTROL1_SLP (1 << 1) +#define R61505_POWER_CONTROL2 0x0011 +#define R61505_POWER_CONTROL2_DC1_HALT (6 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_256 (4 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_128 (3 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_64 (2 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_32 (1 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_16 (0 << 8) +#define R61505_POWER_CONTROL2_DC0_HALT (6 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC_16 (4 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC_8 (3 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC_4 (2 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC_2 (1 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC (0 << 4) +#define R61505_POWER_CONTROL2_VC_100 (7 << 0) +#define R61505_POWER_CONTROL2_VC_076 (4 << 0) +#define R61505_POWER_CONTROL2_VC_089 (1 << 0) +#define R61505_POWER_CONTROL2_VC_094 (0 << 0) +#define R61505_POWER_CONTROL3 0x0012 +#define R61505_POWER_CONTROL3_VCMR (1 << 8) +#define R61505_POWER_CONTROL3_PSON (1 << 5) +#define R61505_POWER_CONTROL3_PON (1 << 4) +#define R61505_POWER_CONTROL3_VRH(n) (((n) & 0xf) << 0) +#define R61505_POWER_CONTROL4 0x0013 +#define R61505_POWER_CONTROL4_VDV(n) (((n) & 0xf) << 8) +#define R61505_POWER_CONTROL5 0x0015 +#define R61505_POWER_CONTROL5_BLDM (1 << 12) +#define R61505_POWER_CONTROL6 0x0017 +#define R61505_POWER_CONTROL6_PSE (1 << 0) +#define R61505_RAM_ADDR_HORZ 0x0020 +#define R61505_RAM_ADDR_VERT 0x0021 +#define R61505_RAM_DATA 0x0022 +#define R61505_POWER_CONTROL7 0x0029 +#define R61505_POWER_CONTROL7_VCM1(n) (((n) & 0x1f) << 0) +#define R61505_GAMMA_CONTROL1 0x0030 +#define R61505_GAMMA_CONTROL2 0x0031 +#define R61505_GAMMA_CONTROL3 0x0032 +#define R61505_GAMMA_CONTROL4 0x0033 +#define R61505_GAMMA_CONTROL5 0x0034 +#define R61505_GAMMA_CONTROL6 0x0035 +#define R61505_GAMMA_CONTROL7 0x0036 +#define R61505_GAMMA_CONTROL8 0x0037 +#define R61505_GAMMA_CONTROL9 0x0038 +#define R61505_GAMMA_CONTROL10 0x0039 +#define R61505_GAMMA_CONTROL11 0x003a +#define R61505_GAMMA_CONTROL12 0x003b +#define R61505_GAMMA_CONTROL13 0x003c +#define R61505_GAMMA_CONTROL14 0x003d +#define R61505_WINDOW_HORZ_START 0x0050 +#define R61505_WINDOW_HORZ_END 0x0051 +#define R61505_WINDOW_VERT_START 0x0052 +#define R61505_WINDOW_VERT_END 0x0053 +#define R61505_DRIVER_OUTPUT_CONTROL2 0x0060 +#define R61505_DRIVER_OUTPUT_CONTROL2_GS (1 << 15) +#define R61505_DRIVER_OUTPUT_CONTROL2_NL(n) (((n) & 0x3f) << 8) +#define R61505_DRIVER_OUTPUT_CONTROL2_SCN(n) (((n) & 0x3f) << 0) +#define R61505_BASE_IMG_DISPLAY_CONTROL 0x0061 +#define R61505_BASE_IMG_DISPLAY_CONTROL_NDL (1 << 2) +#define R61505_BASE_IMG_DISPLAY_CONTROL_VLE (1 << 1) +#define R61505_BASE_IMG_DISPLAY_CONTROL_REV (1 << 0) +#define R61505_VERTICAL_SCROLL_CONTROL 0x006a +#define R61505_PANEL_IF_CONTROL1 0x0090 +#define R61505_PANEL_IF_CONTROL1_DIVI(n) (((n) & 3) << 8) +#define R61505_PANEL_IF_CONTROL1_RTNI(n) (((n) & 0x1f) << 0) +#define R61505_PANEL_IF_CONTROL2 0x0092 +#define R61505_PANEL_IF_CONTROL2_NOWI(n) (((n) & 7) << 8) +#define R61505_PANEL_IF_CONTROL3 0x0093 +#define R61505_PANEL_IF_CONTROL3_MCP(n) (((n) & 7) << 8) +#define R61505_PANEL_IF_CONTROL4 0x0095 +#define R61505_PANEL_IF_CONTROL5 0x0097 +#define R61505_PANEL_IF_CONTROL6 0x0098 +#define R61505_OSCILLATION_CONTROL 0x00a4 +#define R61505_OSCILLATION_CONTROL_CALB (1 << 0) + +struct r61505 { + struct panel panel; + struct panel_dbi_device *dbi; + const struct panel_r61505_platform_data *pdata; +}; + +#define to_panel(p) container_of(p, struct r61505, panel) + +/* ----------------------------------------------------------------------------- + * Read, write and reset + */ + +/* DB0-DB7 are connected to D1-D8, and DB8-DB15 to D10-D17 */ + +static unsigned long adjust_reg18(u16 data) +{ + return (((data << 1) | 0x00000001) & 0x000001ff) | + (((data << 2) | 0x00000200) & 0x0003fe00); +} + +static void r61505_write_command(struct r61505 *panel, u16 reg) +{ + panel_dbi_write_command(panel->dbi, adjust_reg18(reg)); +} + +static void r61505_write(struct r61505 *panel, u16 reg, u16 data) +{ + panel_dbi_write_command(panel->dbi, adjust_reg18(reg)); + panel_dbi_write_data(panel->dbi, adjust_reg18(data)); +} + +static u16 r61505_read(struct r61505 *panel, u16 reg) +{ + unsigned long data; + + panel_dbi_write_command(panel->dbi, adjust_reg18(reg)); + data = panel_dbi_read_data(panel->dbi); + + return ((data >> 1) & 0xff) | ((data >> 2) & 0xff00); +} + +static void r61505_write_array(struct r61505 *panel, + const u16 *data, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i += 2) + r61505_write(panel, data[i], data[i + 1]); +} + +static void r61505_reset(struct r61505 *panel) +{ + if (panel->pdata->reset < 0) + return; + + gpio_set_value(panel->pdata->reset, 0); + usleep_range(2000, 2500); + gpio_set_value(panel->pdata->reset, 1); + usleep_range(1000, 1500); +} + +/* ----------------------------------------------------------------------------- + * Configuration + */ + +static const unsigned short sync_data[] = { + 0x0000, 0x0000, + 0x0000, 0x0000, + 0x0000, 0x0000, + 0x0000, 0x0000, +}; + +static const unsigned short magic0_data[] = { + R61505_DISPLAY_CONTROL2, R61505_DISPLAY_CONTROL2_FP(8) | + R61505_DISPLAY_CONTROL2_BP(8), + R61505_PANEL_IF_CONTROL1, R61505_PANEL_IF_CONTROL1_RTNI(26), + R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_D0, + R61505_POWER_CONTROL6, R61505_POWER_CONTROL6_PSE, + 0x0019, 0x0000, + R61505_POWER_CONTROL1, R61505_POWER_CONTROL1_SAP | + R61505_POWER_CONTROL1_BT(7) | + R61505_POWER_CONTROL1_APE | + R61505_POWER_CONTROL1_AP_100, + R61505_POWER_CONTROL2, R61505_POWER_CONTROL2_DC1_FOSC_32 | + R61505_POWER_CONTROL2_DC0_FOSC_2 | 6, + R61505_POWER_CONTROL3, R61505_POWER_CONTROL3_VCMR | 0x80 | + R61505_POWER_CONTROL3_PON | + R61505_POWER_CONTROL3_VRH(8), + R61505_POWER_CONTROL4, 0x1000 | R61505_POWER_CONTROL4_VDV(4), + R61505_POWER_CONTROL7, R61505_POWER_CONTROL7_VCM1(12), + R61505_POWER_CONTROL3, R61505_POWER_CONTROL3_VCMR | 0x80 | + R61505_POWER_CONTROL3_PSON | + R61505_POWER_CONTROL3_PON | + R61505_POWER_CONTROL3_VRH(8), +}; + +static const unsigned short magic1_data[] = { + R61505_GAMMA_CONTROL1, 0x0307, + R61505_GAMMA_CONTROL2, 0x0303, + R61505_GAMMA_CONTROL3, 0x0603, + R61505_GAMMA_CONTROL4, 0x0202, + R61505_GAMMA_CONTROL5, 0x0202, + R61505_GAMMA_CONTROL6, 0x0202, + R61505_GAMMA_CONTROL7, 0x1f1f, + R61505_GAMMA_CONTROL8, 0x0303, + R61505_GAMMA_CONTROL9, 0x0303, + R61505_GAMMA_CONTROL10, 0x0603, + R61505_GAMMA_CONTROL11, 0x0202, + R61505_GAMMA_CONTROL12, 0x0102, + R61505_GAMMA_CONTROL13, 0x0204, + R61505_GAMMA_CONTROL14, 0x0000, + R61505_DRIVER_OUTPUT_CONTROL, R61505_DRIVER_OUTPUT_CONTROL_SS, + R61505_LCD_WAVEFORM, R61505_LCD_WAVEFORM_BC0 | + R61505_LCD_WAVEFORM_EOR, + R61505_ENTRY_MODE, R61505_ENTRY_MODE_DFM | + R61505_ENTRY_MODE_BGR | + R61505_ENTRY_MODE_ID1 | + R61505_ENTRY_MODE_AM, + R61505_RAM_ADDR_HORZ, 239, + R61505_RAM_ADDR_VERT, 0, + R61505_RESIZE_CONTROL, R61505_RESIZE_CONTROL_RCV(0) | + R61505_RESIZE_CONTROL_RCH(0) | + R61505_RESIZE_CONTROL_RSZ_1, + R61505_DISPLAY_CONTROL3, R61505_DISPLAY_CONTROL3_PTS(0) | + R61505_DISPLAY_CONTROL3_PTG(0) | + R61505_DISPLAY_CONTROL3_ICS(0), + R61505_DISPLAY_CONTROL4, R61505_DISPLAY_CONTROL4_FMARKOE | + R61505_DISPLAY_CONTROL4_FMI_1, + R61505_EXT_DISPLAY_IF_CONTROL1, R61505_EXT_DISPLAY_IF_CONTROL1_ENC(0) | + R61505_EXT_DISPLAY_IF_CONTROL1_DM_ICLK | + R61505_EXT_DISPLAY_IF_CONTROL1_RIM_18, + R61505_FRAME_MARKER_CONTROL, R61505_FRAME_MARKER_CONTROL_FMP(0), + R61505_POWER_CONTROL5, 0x8000, +}; + +static const unsigned short magic2_data[] = { + R61505_BASE_IMG_DISPLAY_CONTROL, R61505_BASE_IMG_DISPLAY_CONTROL_REV, + R61505_PANEL_IF_CONTROL2, R61505_PANEL_IF_CONTROL2_NOWI(1), + R61505_PANEL_IF_CONTROL3, R61505_PANEL_IF_CONTROL3_MCP(1), + R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_GON | + R61505_DISPLAY_CONTROL1_D0, +}; + +static const unsigned short magic3_data[] = { + R61505_POWER_CONTROL1, R61505_POWER_CONTROL1_SAP | + R61505_POWER_CONTROL1_BT(6) | + R61505_POWER_CONTROL1_APE | + R61505_POWER_CONTROL1_AP_100, + R61505_POWER_CONTROL2, R61505_POWER_CONTROL2_DC1_FOSC_32 | + R61505_POWER_CONTROL2_DC0_FOSC_2 | + R61505_POWER_CONTROL2_VC_089, + R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_VON | + R61505_DISPLAY_CONTROL1_GON | + R61505_DISPLAY_CONTROL1_D0, +}; + +static void r61505_enable_panel(struct r61505 *panel) +{ + unsigned long xres = panel->pdata->mode->xres; + unsigned long yres = panel->pdata->mode->yres; + unsigned int i; + + r61505_write_array(panel, sync_data, ARRAY_SIZE(sync_data)); + + r61505_write(panel, R61505_OSCILLATION_CONTROL, + R61505_OSCILLATION_CONTROL_CALB); + usleep_range(10000, 11000); + + r61505_write(panel, R61505_DRIVER_OUTPUT_CONTROL2, + R61505_DRIVER_OUTPUT_CONTROL2_NL((xres / 8) - 1)); + r61505_write_array(panel, magic0_data, ARRAY_SIZE(magic0_data)); + usleep_range(100000, 101000); + + r61505_write_array(panel, magic1_data, ARRAY_SIZE(magic1_data)); + + r61505_write(panel, R61505_WINDOW_HORZ_START, 239 - (yres - 1)); + r61505_write(panel, R61505_WINDOW_HORZ_END, 239); + r61505_write(panel, R61505_WINDOW_VERT_START, 0); + r61505_write(panel, R61505_WINDOW_VERT_END, xres - 1); + + r61505_write_array(panel, magic2_data, ARRAY_SIZE(magic2_data)); + usleep_range(10000, 11000); + + r61505_write_array(panel, magic3_data, ARRAY_SIZE(magic3_data)); + usleep_range(40000, 41000); + + /* Clear GRAM to avoid displaying garbage. */ + r61505_write(panel, R61505_RAM_ADDR_HORZ, 0); + r61505_write(panel, R61505_RAM_ADDR_VERT, 0); + + for (i = 0; i < (xres * 256); i++) /* yes, 256 words per line */ + r61505_write(panel, R61505_RAM_DATA, 0); + + r61505_write(panel, R61505_RAM_ADDR_HORZ, 0); + r61505_write(panel, R61505_RAM_ADDR_VERT, 0); +} + +static void r61505_disable_panel(struct r61505 *panel) +{ + r61505_reset(panel); +} + +static void r61505_display_on(struct r61505 *panel) +{ + r61505_write(panel, R61505_DISPLAY_CONTROL1, + R61505_DISPLAY_CONTROL1_BASEE | + R61505_DISPLAY_CONTROL1_VON | + R61505_DISPLAY_CONTROL1_GON | + R61505_DISPLAY_CONTROL1_DTE | + R61505_DISPLAY_CONTROL1_D1 | + R61505_DISPLAY_CONTROL1_D0); + usleep_range(40000, 41000); +} + +static void r61505_display_off(struct r61505 *panel) +{ + r61505_write(panel, R61505_DISPLAY_CONTROL1, + R61505_DISPLAY_CONTROL1_VON | + R61505_DISPLAY_CONTROL1_GON | + R61505_DISPLAY_CONTROL1_D0); +} + +/* ----------------------------------------------------------------------------- + * Panel operations + */ + +static int r61505_enable(struct panel *p, enum panel_enable_mode enable) +{ + struct r61505 *panel = to_panel(p); + + switch (enable) { + case PANEL_ENABLE_OFF: + r61505_disable_panel(panel); + break; + + case PANEL_ENABLE_BLANK: + if (p->enable == PANEL_ENABLE_OFF) + r61505_enable_panel(panel); + else + r61505_display_off(panel); + break; + + case PANEL_ENABLE_ON: + if (p->enable == PANEL_ENABLE_OFF) + r61505_enable_panel(panel); + + r61505_display_on(panel); + break; + } + + return 0; +} + +static int r61505_start_transfer(struct panel *p) +{ + struct r61505 *panel = to_panel(p); + + r61505_write_command(panel, R61505_RAM_DATA); + usleep_range(100000, 101000); + + return 0; +} + +static int r61505_get_modes(struct panel *p, const struct fb_videomode **modes) +{ + struct r61505 *panel = to_panel(p); + + *modes = panel->pdata->mode; + return 1; +} + +static const struct panel_ops r61505_ops = { + .enable = r61505_enable, + .start_transfer = r61505_start_transfer, + .get_modes = r61505_get_modes, +}; + +static void r61505_release(struct panel *p) +{ + struct r61505 *panel = to_panel(p); + + kfree(panel); +} + +static int r61505_remove(struct panel_dbi_device *dev) +{ + struct r61505 *panel = panel_dbi_get_drvdata(dev); + + panel_dbi_set_drvdata(dev, NULL); + panel_unregister(&panel->panel); + + return 0; +} + +static int __devinit r61505_probe(struct panel_dbi_device *dev) +{ + const struct panel_r61505_platform_data *pdata = dev->dev.platform_data; + struct r61505 *panel; + int ret; + + if (pdata == NULL) + return -ENODEV; + + panel = kzalloc(sizeof(*panel), GFP_KERNEL); + if (panel == NULL) + return -ENOMEM; + + panel->pdata = pdata; + panel->dbi = dev; + + r61505_reset(panel); + r61505_write_array(panel, sync_data, ARRAY_SIZE(sync_data)); + + if (r61505_read(panel, 0) != R61505_DEVICE_CODE_VALUE) { + kfree(panel); + return -ENODEV; + } + + panel->panel.dev = &dev->dev; + panel->panel.release = r61505_release; + panel->panel.ops = &r61505_ops; + panel->panel.width = pdata->width; + panel->panel.height = pdata->height; + + ret = panel_register(&panel->panel); + if (ret < 0) { + kfree(panel); + return ret; + } + + panel_dbi_set_drvdata(dev, panel); + + return 0; +} + +static const struct dev_pm_ops r61505_dev_pm_ops = { +}; + +static struct panel_dbi_driver r61505_driver = { + .probe = r61505_probe, + .remove = r61505_remove, + .driver = { + .name = "panel_r61505", + .owner = THIS_MODULE, + .pm = &r61505_dev_pm_ops, + }, +}; + +module_panel_dbi_driver(r61505_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Renesas R61505-based Display Panel"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-r61505.h b/include/video/panel-r61505.h new file mode 100644 index 0000000..90b3d62 --- /dev/null +++ b/include/video/panel-r61505.h @@ -0,0 +1,27 @@ +/* + * Renesas R61505-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PANEL_R61505_H__ +#define __PANEL_R61505_H__ + +#include <linux/fb.h> +#include <video/panel.h> + +struct panel_r61505_platform_data { + unsigned long width; /* Panel width in mm */ + unsigned long height; /* Panel height in mm */ + const struct fb_videomode *mode; + + int reset; /* Reset GPIO */ +}; + +#endif /* __PANEL_R61505_H__ */
The R61517 is a MIPI DBI panel controller from Renesas.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/panel/Kconfig | 9 + drivers/video/panel/Makefile | 1 + drivers/video/panel/panel-r61517.c | 408 ++++++++++++++++++++++++++++++++++++ include/video/panel-r61517.h | 28 +++ 4 files changed, 446 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/panel-r61517.c create mode 100644 include/video/panel-r61517.h
diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig index 12d7712..bd643be 100644 --- a/drivers/video/panel/Kconfig +++ b/drivers/video/panel/Kconfig @@ -25,4 +25,13 @@ config DISPLAY_PANEL_R61505
If you are in doubt, say N.
+config DISPLAY_PANEL_R61517 + tristate "Renesas R61517-based Display Panel" + select DISPLAY_PANEL_DBI + ---help--- + Support panels based on the Renesas R61517 panel controller. + Those panels are controlled through a MIPI DBI interface. + + If you are in doubt, say N. + endif # DISPLAY_PANEL diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile index e4fb9fe..3c11d26 100644 --- a/drivers/video/panel/Makefile +++ b/drivers/video/panel/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_DISPLAY_PANEL) += panel.o obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o obj-$(CONFIG_DISPLAY_PANEL_DBI) += panel-dbi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o +obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o diff --git a/drivers/video/panel/panel-r61517.c b/drivers/video/panel/panel-r61517.c new file mode 100644 index 0000000..6e8d933 --- /dev/null +++ b/drivers/video/panel/panel-r61517.c @@ -0,0 +1,408 @@ +/* + * Renesas R61517-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Based on KFR2R09 LCD panel support + * Copyright (C) 2009 Magnus Damm + * Register settings based on the out-of-tree t33fb.c driver + * Copyright (C) 2008 Lineo Solutions, Inc. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/gpio.h> + +#include <video/panel-dbi.h> +#include <video/panel-r61517.h> + +struct r61517 { + struct panel panel; + struct panel_dbi_device *dbi; + const struct panel_r61517_platform_data *pdata; +}; + +#define to_panel(p) container_of(p, struct r61517, panel) + +/* ----------------------------------------------------------------------------- + * Read, write and reset + */ + +static void r61517_write_command(struct r61517 *panel, u16 reg) +{ + panel_dbi_write_command(panel->dbi, reg); +} + +static void r61517_write_data(struct r61517 *panel, u16 data) +{ + panel_dbi_write_data(panel->dbi, data); +} + +static void r61517_write(struct r61517 *panel, u16 reg, u16 data) +{ + panel_dbi_write_command(panel->dbi, reg); + panel_dbi_write_data(panel->dbi, data); +} + +static u16 r61517_read_data(struct r61517 *panel) +{ + return panel_dbi_read_data(panel->dbi); +} + +static void __r61517_write_array(struct r61517 *panel, const u8 *data, + unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; ++i) + r61517_write_data(panel, data[i]); +} + +#define r61517_write_array(p, a) \ + __r61517_write_array(p, a, ARRAY_SIZE(a)) + +static void r61517_reset(struct r61517 *panel) +{ + gpio_set_value(panel->pdata->protect, 0); /* PROTECT/ -> L */ + gpio_set_value(panel->pdata->reset, 0); /* LCD_RST/ -> L */ + gpio_set_value(panel->pdata->protect, 1); /* PROTECT/ -> H */ + usleep_range(1100, 1200); + gpio_set_value(panel->pdata->reset, 1); /* LCD_RST/ -> H */ + usleep_range(10, 100); + gpio_set_value(panel->pdata->protect, 0); /* PROTECT/ -> L */ + msleep(20); +} + +/* ----------------------------------------------------------------------------- + * Configuration + */ + +static const u8 data_frame_if[] = { + 0x02, /* WEMODE: 1=cont, 0=one-shot */ + 0x00, 0x00, + 0x00, /* EPF, DFM */ + 0x02, /* RIM[1] : 1 (18bpp) */ +}; + +static const u8 data_panel[] = { + 0x0b, + 0x63, /* 400 lines */ + 0x04, 0x00, 0x00, 0x04, 0x11, 0x00, 0x00, +}; + +static const u8 data_timing[] = { + 0x00, 0x00, 0x13, 0x08, 0x08, +}; + +static const u8 data_timing_src[] = { + 0x11, 0x01, 0x00, 0x01, +}; + +static const u8 data_gamma[] = { + 0x01, 0x02, 0x08, 0x23, 0x03, 0x0c, 0x00, 0x06, 0x00, 0x00, + 0x01, 0x00, 0x0c, 0x23, 0x03, 0x08, 0x02, 0x06, 0x00, 0x00, +}; + +static const u8 data_power[] = { + 0x07, 0xc5, 0xdc, 0x02, 0x33, 0x0a, +}; + +static unsigned long r61517_read_device_code(struct r61517 *panel) +{ + /* access protect OFF */ + r61517_write(panel, 0xb0, 0x00); + + /* deep standby OFF */ + r61517_write(panel, 0xb1, 0x00); + + /* device code command */ + r61517_write_command(panel, 0xbf); + mdelay(50); + + /* dummy read */ + r61517_read_data(panel); + + /* read device code */ + return ((r61517_read_data(panel) & 0xff) << 24) | + ((r61517_read_data(panel) & 0xff) << 16) | + ((r61517_read_data(panel) & 0xff) << 8) | + ((r61517_read_data(panel) & 0xff) << 0); +} + +static void r61517_write_memory_start(struct r61517 *panel) +{ + r61517_write_command(panel, 0x2c); +} + +static void r61517_clear_memory(struct r61517 *panel) +{ + unsigned int i; + + r61517_write_memory_start(panel); + + for (i = 0; i < (240 * 400); i++) + r61517_write_data(panel, 0); +} + +static void r61517_enable_panel(struct r61517 *panel) +{ + /* access protect off */ + r61517_write(panel, 0xb0, 0x00); + + /* exit deep standby mode */ + r61517_write(panel, 0xb1, 0x00); + + /* frame memory I/F */ + r61517_write_command(panel, 0xb3); + r61517_write_array(panel, data_frame_if); + + /* display mode and frame memory write mode */ + r61517_write(panel, 0xb4, 0x00); /* DBI, internal clock */ + + /* panel */ + r61517_write_command(panel, 0xc0); + r61517_write_array(panel, data_panel); + + /* timing (normal) */ + r61517_write_command(panel, 0xc1); + r61517_write_array(panel, data_timing); + + /* timing (partial) */ + r61517_write_command(panel, 0xc2); + r61517_write_array(panel, data_timing); + + /* timing (idle) */ + r61517_write_command(panel, 0xc3); + r61517_write_array(panel, data_timing); + + /* timing (source/VCOM/gate driving) */ + r61517_write_command(panel, 0xc4); + r61517_write_array(panel, data_timing_src); + + /* gamma (red) */ + r61517_write_command(panel, 0xc8); + r61517_write_array(panel, data_gamma); + + /* gamma (green) */ + r61517_write_command(panel, 0xc9); + r61517_write_array(panel, data_gamma); + + /* gamma (blue) */ + r61517_write_command(panel, 0xca); + r61517_write_array(panel, data_gamma); + + /* power (common) */ + r61517_write_command(panel, 0xd0); + r61517_write_array(panel, data_power); + + /* VCOM */ + r61517_write_command(panel, 0xd1); + r61517_write_data(panel, 0x00); + r61517_write_data(panel, 0x0f); + r61517_write_data(panel, 0x02); + + /* power (normal) */ + r61517_write_command(panel, 0xd2); + r61517_write_data(panel, 0x63); + r61517_write_data(panel, 0x24); + + /* power (partial) */ + r61517_write_command(panel, 0xd3); + r61517_write_data(panel, 0x63); + r61517_write_data(panel, 0x24); + + /* power (idle) */ + r61517_write_command(panel, 0xd4); + r61517_write_data(panel, 0x63); + r61517_write_data(panel, 0x24); + + r61517_write_command(panel, 0xd8); + r61517_write_data(panel, 0x77); + r61517_write_data(panel, 0x77); + + /* TE signal */ + r61517_write(panel, 0x35, 0x00); + + /* TE signal line */ + r61517_write_command(panel, 0x44); + r61517_write_data(panel, 0x00); + r61517_write_data(panel, 0x00); + + /* column address */ + r61517_write_command(panel, 0x2a); + r61517_write_data(panel, 0x00); + r61517_write_data(panel, 0x00); + r61517_write_data(panel, 0x00); + r61517_write_data(panel, 0xef); + + /* page address */ + r61517_write_command(panel, 0x2b); + r61517_write_data(panel, 0x00); + r61517_write_data(panel, 0x00); + r61517_write_data(panel, 0x01); + r61517_write_data(panel, 0x8f); + + /* exit sleep mode */ + r61517_write_command(panel, 0x11); + + mdelay(120); + + /* clear vram */ + r61517_clear_memory(panel); +} + +static void r61517_disable_panel(struct r61517 *panel) +{ + r61517_reset(panel); +} + +static void r61517_display_on(struct r61517 *panel) +{ + r61517_write_command(panel, 0x29); + mdelay(1); +} + +static void r61517_display_off(struct r61517 *panel) +{ + r61517_write_command(panel, 0x28); +} + +/* ----------------------------------------------------------------------------- + * Panel operations + */ + +static int r61517_enable(struct panel *p, enum panel_enable_mode enable) +{ + struct r61517 *panel = to_panel(p); + + switch (enable) { + case PANEL_ENABLE_OFF: + r61517_disable_panel(panel); + break; + + case PANEL_ENABLE_BLANK: + if (p->enable == PANEL_ENABLE_OFF) + r61517_enable_panel(panel); + else + r61517_display_off(panel); + break; + + case PANEL_ENABLE_ON: + if (p->enable == PANEL_ENABLE_OFF) + r61517_enable_panel(panel); + + r61517_display_on(panel); + break; + } + + return 0; +} + +static int r61517_start_transfer(struct panel *p) +{ + struct r61517 *panel = to_panel(p); + + r61517_write_memory_start(panel); + return 0; +} + +static int r61517_get_modes(struct panel *p, const struct fb_videomode **modes) +{ + struct r61517 *panel = to_panel(p); + + *modes = panel->pdata->mode; + return 1; +} + +static const struct panel_ops r61517_ops = { + .enable = r61517_enable, + .start_transfer = r61517_start_transfer, + .get_modes = r61517_get_modes, +}; + +static void r61517_release(struct panel *p) +{ + struct r61517 *panel = to_panel(p); + + kfree(panel); +} + +static int r61517_remove(struct panel_dbi_device *dev) +{ + struct r61517 *panel = panel_dbi_get_drvdata(dev); + + panel_dbi_set_drvdata(dev, NULL); + panel_unregister(&panel->panel); + + return 0; +} + +static int __devinit r61517_probe(struct panel_dbi_device *dev) +{ + const struct panel_r61517_platform_data *pdata = dev->dev.platform_data; + struct r61517 *panel; + int ret; + + if (pdata == NULL) + return -ENODEV; + + panel = kzalloc(sizeof(*panel), GFP_KERNEL); + if (panel == NULL) + return -ENOMEM; + + panel->pdata = pdata; + panel->dbi = dev; + + r61517_reset(panel); + + if (r61517_read_device_code(panel) != 0x01221517) { + kfree(panel); + return -ENODEV; + } + + pr_info("R61517 panel controller detected.\n"); + + panel->panel.dev = &dev->dev; + panel->panel.release = r61517_release; + panel->panel.ops = &r61517_ops; + panel->panel.width = pdata->width; + panel->panel.height = pdata->height; + + ret = panel_register(&panel->panel); + if (ret < 0) { + kfree(panel); + return ret; + } + + panel_dbi_set_drvdata(dev, panel); + + return 0; +} + +static const struct dev_pm_ops r61517_dev_pm_ops = { +}; + +static struct panel_dbi_driver r61517_driver = { + .probe = r61517_probe, + .remove = r61517_remove, + .driver = { + .name = "panel_r61517", + .owner = THIS_MODULE, + .pm = &r61517_dev_pm_ops, + }, +}; + +module_panel_dbi_driver(r61517_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Renesas R61517-based Display Panel"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-r61517.h b/include/video/panel-r61517.h new file mode 100644 index 0000000..c9e6ddf --- /dev/null +++ b/include/video/panel-r61517.h @@ -0,0 +1,28 @@ +/* + * Renesas R61517-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PANEL_R61517_H__ +#define __PANEL_R61517_H__ + +#include <linux/fb.h> +#include <video/panel.h> + +struct panel_r61517_platform_data { + unsigned long width; /* Panel width in mm */ + unsigned long height; /* Panel height in mm */ + const struct fb_videomode *mode; + + int protect; /* Protect GPIO */ + int reset; /* Reset GPIO */ +}; + +#endif /* __PANEL_R61517_H__ */
On Friday, August 17, 2012 9:50 AM Laurent Pinchart wrote:
Hi everybody,
While working on DT bindings for the Renesas Mobile SoC display controller (a.k.a. LCDC) I quickly realized that display panel implementation based on board code callbacks would need to be replaced by a driver-based panel framework.
Several driver-based panel support solution already exist in the kernel.
The LCD device class is implemented in drivers/video/backlight/lcd.c and exposes a kernel API in include/linux/lcd.h. That API is tied to the FBDEV API for historical reason, uses board code callback for reset and power management, and doesn't include support for standard features available in today's "smart panels".
OMAP2+ based systems use custom panel drivers available in drivers/video/omap2/displays. Those drivers are based on OMAP DSS (display controller) specific APIs.
Similarly, Exynos based systems use custom panel drivers available in drivers/video/exynos. Only a single driver (s6e8ax0) is currently available. That driver is based on Exynos display controller specific APIs and on the LCD device class API.
Hi Laurent,
I am a Exynos DP maintainer and Samsung FB maintainer.
Actually, eDP (embedded display port) will be faced with this kind of problem. According to the eDP standard, eDP panel can have their own specific registers that handle extra operations. In this case, custom panel driver for this eDP panel will be necessary.
In my opinion, the panel framework would be helpful to solve this problem.
Best regards, Jingoo Han
I've brought up the issue with Tomi Valkeinen (OMAP DSS maintainer) and Marcus Lorentzon (working on panel support for ST/Linaro), and we agreed that a generic panel framework for display devices is needed. These patches implement a first proof of concept.
One of the main reasons for creating a new panel framework instead of adding missing features to the LCD framework is to avoid being tied to the FBDEV framework. Panels will be used by DRM drivers as well, and their API should thus be subsystem-agnostic. Note that the panel framework used the fb_videomode structure in its API, this will be replaced by a common video mode structure shared across subsystems (there's only so many hours per day).
Panels, as used in these patches, are defined as physical devices combining a matrix of pixels and a controller capable of driving that matrix.
Panel physical devices are registered as children of the control bus the panel controller is connected to (depending on the panel type, we can find platform devices for dummy panels with no control bus, or I2C, SPI, DBI, DSI, ... devices). The generic panel framework matches registered panel devices with panel drivers and call the panel drivers probe method, as done by other device classes in the kernel. The driver probe() method is responsible for instantiating a struct panel instance and registering it with the generic panel framework.
Display drivers are panel consumers. They register a panel notifier with the framework, which then calls the notifier when a matching panel is registered. The reason for this asynchronous mode of operation, compared to how drivers acquire regulator or clock resources, is that the panel can use resources provided by the display driver. For instance a panel can be a child of the DBI or DSI bus controlled by the display device, or use a clock provided by that device. We can't defer the display device probe until the panel is registered and also defer the panel device probe until the display is registered. As most display drivers need to handle output devices hotplug (HDMI monitors for instance), handling panel through a notification system seemed to be the easiest solution.
Note that this brings a different issue after registration, as display and panel drivers would take a reference to each other. Those circular references would make driver unloading impossible. I haven't found a good solution for that problem yet (hence the RFC state of those patches), and I would appreciate your input here. This might also be a hint that the framework design is wrong to start with. I guess I can't get everything right on the first try ;-)
Getting hold of the panel is the most complex part. Once done, display drivers can call abstract operations provided by panel drivers to control the panel operation. These patches implement three of those operations (enable, start transfer and get modes). More operations will be needed, and those three operations will likely get modified during review. Most of the panels on devices I own are dumb panels with no control bus, and are thus not the best candidates to design a framework that needs to take complex panels' needs into account.
In addition to the generic panel core, I've implemented MIPI DBI (Display Bus Interface, a parallel bus for panels that supports read/write transfers of commands and data) bus support, as well as three panel drivers (dummy panels with no control bus, and Renesas R61505- and R61517-based panels, both using DBI as their control bus). Only the dummy panel driver has been tested as I lack hardware for the two other drivers.
I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If you can find a clever way to solve the cyclic references issue described above I'll buy you a beer at the next conference we will both attend. If you think the proposed solution is too complex, or too simple, I'm all ears. I personally already feel that we might need something even more generic to support other kinds of external devices connected to display controllers, such as external DSI to HDMI converters for instance. Some kind of video entity exposing abstract operations like the panels do would make sense, in which case panels would "inherit" from that video entity.
Speaking of conferences, I will attend the KS/LPC in San Diego in a bit more than a week, and would be happy to discuss this topic face to face there.
Laurent Pinchart (5): video: Add generic display panel core video: panel: Add dummy panel support video: panel: Add MIPI DBI bus support video: panel: Add R61505 panel support video: panel: Add R61517 panel support
drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/panel/Kconfig | 37 +++ drivers/video/panel/Makefile | 5 + drivers/video/panel/panel-dbi.c | 217 +++++++++++++++ drivers/video/panel/panel-dummy.c | 103 +++++++ drivers/video/panel/panel-r61505.c | 520 ++++++++++++++++++++++++++++++++++++ drivers/video/panel/panel-r61517.c | 408 ++++++++++++++++++++++++++++ drivers/video/panel/panel.c | 269 +++++++++++++++++++ include/video/panel-dbi.h | 92 +++++++ include/video/panel-dummy.h | 25 ++ include/video/panel-r61505.h | 27 ++ include/video/panel-r61517.h | 28 ++ include/video/panel.h | 111 ++++++++ 14 files changed, 1844 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/Kconfig create mode 100644 drivers/video/panel/Makefile create mode 100644 drivers/video/panel/panel-dbi.c create mode 100644 drivers/video/panel/panel-dummy.c create mode 100644 drivers/video/panel/panel-r61505.c create mode 100644 drivers/video/panel/panel-r61517.c create mode 100644 drivers/video/panel/panel.c create mode 100644 include/video/panel-dbi.h create mode 100644 include/video/panel-dummy.h create mode 100644 include/video/panel-r61505.h create mode 100644 include/video/panel-r61517.h create mode 100644 include/video/panel.h
-- Regards,
Laurent Pinchart
-- To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi,
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If
Oookay, where to start... ;)
A few cosmetic/general comments first.
I find the file naming a bit strange. You have panel.c, which is the core framework, panel-dbi.c, which is the DBI bus, panel-r61517.c, which is driver for r61517 panel...
Perhaps something in this direction (in order): panel-core.c, mipi-dbi-bus.c, panel-r61517.c? And we probably end up with quite a lot of panel drivers, perhaps we should already divide these into separate directories, and then we wouldn't need to prefix each panel with "panel-" at all.
---
Should we aim for DT only solution from the start? DT is the direction we are going, and I feel the older platform data stuff would be deprecated soon.
---
Something missing from the intro is how this whole thing should be used. It doesn't help if we know how to turn on the panel, we also need to display something on it =). So I think some kind of diagram/example of how, say, drm would use this thing, and also how the SoC specific DBI bus driver would be done, would clarify things.
---
We have discussed face to face about the different hardware setups and scenarios that we should support, but I'll list some of them here for others:
1) We need to support chains of external display chips and panels. A simple example is a chip that takes DSI in, and outputs DPI. In that case we'd have a chain of SoC -> DSI2DPI -> DPI panel.
In final products I think two external devices is the maximum (at least I've never seen three devices in a row), but in theory and in development environments the chain can be arbitrarily long. Also the connections are not necessarily 1-to-1, but a device can take one input while it has two outputs, or a device can take two inputs.
Now, I think two external devices is a must requirement. I'm not sure if supporting more is an important requirement. However, if we support two devices, it could be that it's trivial to change the framework to support n devices.
2) Panels and display chips are all but standard. They very often have their own sequences how to do things, have bugs, or implement some feature in slightly different way than some other panel. This is why the panel driver should be able to control or define the way things happen.
As an example, Sharp LQ043T1DG01 panel (www.sharpsme.com/download/LQ043T1DG01-SP-072106pdf). It is enabled with the following sequence:
- Enable VCC and AVDD regulators - Wait min 50ms - Enable full video stream (pck, syncs, pixels) from SoC - Wait min 0.5ms - Set DISP GPIO, which turns on the display panel
Here we could split the enabling of panel to two parts, prepare (in this case starts regulators and waits 50ms) and finish (wait 0.5ms and set DISP GPIO), and the upper layer would start the video stream in between.
I realize this could be done with the PANEL_ENABLE_* levels in your RFC, but I don't think the concepts quite match:
- PANEL_ENABLE_BLANK level is needed for "smart panels", as we need to configure them and send the initial frame at that operating level. With dummy panels there's really no such level, there's just one enable sequence that is always done right away.
- I find waiting at the beginning of a function very ugly (what are we waiting for?) and we'd need that when changing the panel to PANEL_ENABLE_ON level.
- It's still limited if the panel is a stranger one (see following example).
Consider the following theoretical panel enable example, taken to absurd level just to show the general problem:
- Enable regulators - Enable video stream - Wait 50ms - Disable video stream - Set enable GPIO - Enable video stream
This one would be rather impossible with the upper layer handling the enabling of the video stream. Thus I see that the panel driver needs to control the sequences, and the Sharp panel driver's enable would look something like:
regulator_enable(...); sleep(); dpi_enable_video(); sleep(); gpip_set(..);
Note that even with this model we still need the PANEL_ENABLE levels you have.
---
I'm not sure I understand the panel unload problem you mentioned. Nobody should have direct references to the panel functions, so there shouldn't be any automatic references that would prevent module unloading. So when the user does rmmod panel-mypanel, the panel driver's remove will be called. It'll unregister itself from the panel framework, which causes notifications and the display driver will stop using the panel. After that nobody has pointers to the panel, and it can safely be unloaded.
It could cause some locking issues, though. First the panel's remove could take a lock, but the remove sequence would cause the display driver to call disable on the panel, which could again try to take the same lock...
Tomi
Hi Tomi,
Thanks a lot for the review.
On Friday 17 August 2012 11:38:14 Tomi Valkeinen wrote:
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If
Oookay, where to start... ;)
A few cosmetic/general comments first.
I find the file naming a bit strange. You have panel.c, which is the core framework, panel-dbi.c, which is the DBI bus, panel-r61517.c, which is driver for r61517 panel...
Perhaps something in this direction (in order): panel-core.c, mipi-dbi-bus.c, panel-r61517.c?
That looks good to me. I'll then rename panel_dbi_* to mipi_dbi_*.
And we probably end up with quite a lot of panel drivers, perhaps we should already divide these into separate directories, and then we wouldn't need to prefix each panel with "panel-" at all.
What kind of directory structure do you have in mind ? Panels are already isolated in drivers/video/panel/ so we could already ditch the panel- prefix in drivers.
Would you also create include/video/panel/ ?
Should we aim for DT only solution from the start? DT is the direction we are going, and I feel the older platform data stuff would be deprecated soon.
Don't forget about non-ARM architectures :-/ We need panel drivers for SH as well, which doesn't use DT. I don't think that would be a big issue, a DT- compliant solution should be easy to use through board code and platform data as well.
Something missing from the intro is how this whole thing should be used. It doesn't help if we know how to turn on the panel, we also need to display something on it =). So I think some kind of diagram/example of how, say, drm would use this thing, and also how the SoC specific DBI bus driver would be done, would clarify things.
Of course. If I had all that information already I would have shared it :-) This is really a first RFC, my goal is to make sure that I'm going in the right direction.
We have discussed face to face about the different hardware setups and scenarios that we should support, but I'll list some of them here for others:
- We need to support chains of external display chips and panels. A
simple example is a chip that takes DSI in, and outputs DPI. In that case we'd have a chain of SoC -> DSI2DPI -> DPI panel.
In final products I think two external devices is the maximum (at least I've never seen three devices in a row), but in theory and in development environments the chain can be arbitrarily long. Also the connections are not necessarily 1-to-1, but a device can take one input while it has two outputs, or a device can take two inputs.
Now, I think two external devices is a must requirement. I'm not sure if supporting more is an important requirement. However, if we support two devices, it could be that it's trivial to change the framework to support n devices.
- Panels and display chips are all but standard. They very often have
their own sequences how to do things, have bugs, or implement some feature in slightly different way than some other panel. This is why the panel driver should be able to control or define the way things happen.
As an example, Sharp LQ043T1DG01 panel (www.sharpsme.com/download/LQ043T1DG01-SP-072106pdf). It is enabled with the following sequence:
- Enable VCC and AVDD regulators
- Wait min 50ms
- Enable full video stream (pck, syncs, pixels) from SoC
- Wait min 0.5ms
- Set DISP GPIO, which turns on the display panel
Here we could split the enabling of panel to two parts, prepare (in this case starts regulators and waits 50ms) and finish (wait 0.5ms and set DISP GPIO), and the upper layer would start the video stream in between.
I realize this could be done with the PANEL_ENABLE_* levels in your RFC, but I don't think the concepts quite match:
- PANEL_ENABLE_BLANK level is needed for "smart panels", as we need to
configure them and send the initial frame at that operating level. With dummy panels there's really no such level, there's just one enable sequence that is always done right away.
- I find waiting at the beginning of a function very ugly (what are we
waiting for?) and we'd need that when changing the panel to PANEL_ENABLE_ON level.
- It's still limited if the panel is a stranger one (see following
example).
Consider the following theoretical panel enable example, taken to absurd level just to show the general problem:
- Enable regulators
- Enable video stream
- Wait 50ms
- Disable video stream
- Set enable GPIO
- Enable video stream
This one would be rather impossible with the upper layer handling the enabling of the video stream. Thus I see that the panel driver needs to control the sequences, and the Sharp panel driver's enable would look something like:
regulator_enable(...); sleep(); dpi_enable_video(); sleep(); gpip_set(..);
I have to admit I have no better solution to propose at the moment, even if I don't really like making the panel control the video stream. When several devices will be present in the chain all of them might have similar annoying requirements, and my feeling is that the resulting code will be quite messy. At the end of the day the only way to really find out is to write an implementation.
Note that even with this model we still need the PANEL_ENABLE levels you have.
I'm not sure I understand the panel unload problem you mentioned. Nobody should have direct references to the panel functions, so there shouldn't be any automatic references that would prevent module unloading. So when the user does rmmod panel-mypanel, the panel driver's remove will be called. It'll unregister itself from the panel framework, which causes notifications and the display driver will stop using the panel. After that nobody has pointers to the panel, and it can safely be unloaded.
It could cause some locking issues, though. First the panel's remove could take a lock, but the remove sequence would cause the display driver to call disable on the panel, which could again try to take the same lock...
We have two possible ways of calling panel operations, either directly (panel-
bus->ops->enable(...)) or indirectly (panel_enable(...)).
The former is what V4L2 currently does with subdevs, and requires display drivers to hold a reference to the panel. The later can do without a direct reference only if we use a global lock, which is something I would like to avoid. A panel-wide lock wouldn't work, as the access function would need to take the lock on a panel instance that can be removed at any time.
Note that this issue is not specific to panels, V4L2 will need a solution as well when V4L2 subdevs will be instantiated from the DT.
On Fri, 2012-08-17 at 13:10 +0200, Laurent Pinchart wrote:
What kind of directory structure do you have in mind ? Panels are already isolated in drivers/video/panel/ so we could already ditch the panel- prefix in drivers.
The same directory also contains files for the framework and buses. But perhaps there's no need for additional directories if the amount of non-panel files is small. And you can easily see from the name that they are not panel drivers (e.g. mipi_dbi_bus.c).
Would you also create include/video/panel/ ?
Perhaps that would be good. Well, having all the files prefixed with panel- is not bad as such, but just feel extra.
Should we aim for DT only solution from the start? DT is the direction we are going, and I feel the older platform data stuff would be deprecated soon.
Don't forget about non-ARM architectures :-/ We need panel drivers for SH as well, which doesn't use DT. I don't think that would be a big issue, a DT- compliant solution should be easy to use through board code and platform data as well.
I didn't forget about them as I didn't even think about them ;). I somehow had the impression that other architectures would also use DT, sooner or later. I could be mistaken, though.
And true, it's not a big issue to support both DT and non-DT versions, but I've been porting omap stuff for DT and keeping the old platform data stuff also there, and it just feels burdensome. For very simple panels it's easy, but when you've passing lots of parameters the code starts to get longer.
This one would be rather impossible with the upper layer handling the enabling of the video stream. Thus I see that the panel driver needs to control the sequences, and the Sharp panel driver's enable would look something like:
regulator_enable(...); sleep(); dpi_enable_video(); sleep(); gpip_set(..);
I have to admit I have no better solution to propose at the moment, even if I don't really like making the panel control the video stream. When several devices will be present in the chain all of them might have similar annoying requirements, and my feeling is that the resulting code will be quite messy. At the end of the day the only way to really find out is to write an implementation.
If we have a chain of devices, and each device uses the bus interface from the previous device in the chain, there shouldn't be a problem. In that model each device can handle the task however is best for it.
I think the problems come from the divided control we'll have. I mean, if the panel driver would decide itself what to send to its output, and it would provide the data (think of an i2c device), this would be very simple. And it actually is for things like configuration data etc, but not so for video stream.
It could cause some locking issues, though. First the panel's remove could take a lock, but the remove sequence would cause the display driver to call disable on the panel, which could again try to take the same lock...
We have two possible ways of calling panel operations, either directly (panel-
bus->ops->enable(...)) or indirectly (panel_enable(...)).
The former is what V4L2 currently does with subdevs, and requires display drivers to hold a reference to the panel. The later can do without a direct reference only if we use a global lock, which is something I would like to
Wouldn't panel_enable() just do the same panel->bus->ops->enable() anyway, and both require a panel reference? I don't see the difference.
avoid. A panel-wide lock wouldn't work, as the access function would need to take the lock on a panel instance that can be removed at any time.
Can't this be handled with some kind of get/put refcounting? If there's a ref, it can't be removed.
Generally about locks, if we define that panel ops may only be called exclusively, does it simplify things? I think we can make such requirements, as there should be only one display framework that handles the panel. Then we don't need locking for things like enable/disable.
Of course we need to be careful about things where calls come from "outside" the display framework. I guess one such thing is rmmod, but if that causes a notification to the display framework, which again handles locking, it shouldn't be a problem.
Another thing to be careful about is if the panel internally uses irqs, workqueues, sysfs files or such. In that case it needs to handle locking.
Tomi
Hi Tomi,
On Friday 17 August 2012 14:42:31 Tomi Valkeinen wrote:
On Fri, 2012-08-17 at 13:10 +0200, Laurent Pinchart wrote:
What kind of directory structure do you have in mind ? Panels are already isolated in drivers/video/panel/ so we could already ditch the panel- prefix in drivers.
The same directory also contains files for the framework and buses. But perhaps there's no need for additional directories if the amount of non-panel files is small. And you can easily see from the name that they are not panel drivers (e.g. mipi_dbi_bus.c).
I don't expect the directory to contain many non-panel files, so let's keep it as-is for now.
mipi-dbi-bus might not belong to include/video/panel/ though, as it can be used for non-panel devices (at least in theory). The future mipi-dsi-bus certainly will.
Would you also create include/video/panel/ ?
Perhaps that would be good. Well, having all the files prefixed with panel- is not bad as such, but just feel extra.
Should we aim for DT only solution from the start? DT is the direction we are going, and I feel the older platform data stuff would be deprecated soon.
Don't forget about non-ARM architectures :-/ We need panel drivers for SH as well, which doesn't use DT. I don't think that would be a big issue, a DT- compliant solution should be easy to use through board code and platform data as well.
I didn't forget about them as I didn't even think about them ;). I somehow had the impression that other architectures would also use DT, sooner or later. I could be mistaken, though.
And true, it's not a big issue to support both DT and non-DT versions, but I've been porting omap stuff for DT and keeping the old platform data stuff also there, and it just feels burdensome. For very simple panels it's easy, but when you've passing lots of parameters the code starts to get longer.
This one would be rather impossible with the upper layer handling the enabling of the video stream. Thus I see that the panel driver needs to control the sequences, and the Sharp panel driver's enable would look something like:
regulator_enable(...); sleep(); dpi_enable_video(); sleep(); gpip_set(..);
I have to admit I have no better solution to propose at the moment, even if I don't really like making the panel control the video stream. When several devices will be present in the chain all of them might have similar annoying requirements, and my feeling is that the resulting code will be quite messy. At the end of the day the only way to really find out is to write an implementation.
If we have a chain of devices, and each device uses the bus interface from the previous device in the chain, there shouldn't be a problem. In that model each device can handle the task however is best for it.
I think the problems come from the divided control we'll have. I mean, if the panel driver would decide itself what to send to its output, and it would provide the data (think of an i2c device), this would be very simple. And it actually is for things like configuration data etc, but not so for video stream.
Would you be able to send incremental patches on top of v2 to implement the solution you have in mind ? It would be neat if you could also implement mipi- dsi-bus for the OMAP DSS and test the code with a real device :-)
It could cause some locking issues, though. First the panel's remove could take a lock, but the remove sequence would cause the display driver to call disable on the panel, which could again try to take the same lock...
We have two possible ways of calling panel operations, either directly (panel->bus->ops->enable(...)) or indirectly (panel_enable(...)).
The former is what V4L2 currently does with subdevs, and requires display drivers to hold a reference to the panel. The later can do without a direct reference only if we use a global lock, which is something I would like to
Wouldn't panel_enable() just do the same panel->bus->ops->enable() anyway, and both require a panel reference? I don't see the difference.
Indeed, you're right. I'm not sure what I was thinking about.
avoid. A panel-wide lock wouldn't work, as the access function would need to take the lock on a panel instance that can be removed at any time.
Can't this be handled with some kind of get/put refcounting? If there's a ref, it can't be removed.
Trouble will come when the display driver will hold a reference to the panel, and the panel will hold a reference to the display driver (for instance because the display driver provides the DBI/DSI bus, or because it provides a clock used by the panel).
Generally about locks, if we define that panel ops may only be called exclusively, does it simplify things? I think we can make such requirements, as there should be only one display framework that handles the panel. Then we don't need locking for things like enable/disable.
Pushing locking to callers would indeed simplify panel drivers, but we need to make sure we won't need to expose a panel to several callers in the future.
Of course we need to be careful about things where calls come from "outside" the display framework. I guess one such thing is rmmod, but if that causes a notification to the display framework, which again handles locking, it shouldn't be a problem.
Another thing to be careful about is if the panel internally uses irqs, workqueues, sysfs files or such. In that case it needs to handle locking.
Of course panels will need to manage concurrency for their own infrastructure.
On Sat, 2012-08-18 at 03:16 +0200, Laurent Pinchart wrote:
Hi Tomi,
mipi-dbi-bus might not belong to include/video/panel/ though, as it can be used for non-panel devices (at least in theory). The future mipi-dsi-bus certainly will.
They are both display busses. So while they could be used for anything, I find it quite unlikely as there are much better alternatives for generic bus needs.
Would you be able to send incremental patches on top of v2 to implement the solution you have in mind ? It would be neat if you could also implement mipi- dsi-bus for the OMAP DSS and test the code with a real device :-)
Yes, I'd like to try this out on OMAP, both DBI and DSI. However, I fear it'll be quite complex due to the dependencies all around we have in the current driver. We're working on simplifying things so that it'll be easier to try thing like the panel framework, though, so we're going in the right direction.
Generally about locks, if we define that panel ops may only be called exclusively, does it simplify things? I think we can make such requirements, as there should be only one display framework that handles the panel. Then we don't need locking for things like enable/disable.
Pushing locking to callers would indeed simplify panel drivers, but we need to make sure we won't need to expose a panel to several callers in the future.
I have a feeling that would be a bad idea.
Display related stuff are quite sensitive to any delays, so any extra transactions over, say, DSI bus could cause a noticeable glitch on the screen. I'm not sure what are all the possible ops that a panel can offer, but I think all that affect the display or could cause delays should be handled by one controlling entity (drm or such). The controlling entity needs to handle locking anyway, so in that sense I don't think it's an extra burden for it.
The things that come to my mind that could possibly cause calls to the panel outside drm: debugfs, sysfs, audio, backlight. Of those, I think backlight should go through drm. Audio, no idea. debugfs and sysfs locking needs to be handled by the panel driver, and they are a bit problematic as I guess having them requires full locking.
Tomi
Hi Tomi,
On Monday 20 August 2012 14:39:30 Tomi Valkeinen wrote:
On Sat, 2012-08-18 at 03:16 +0200, Laurent Pinchart wrote:
Hi Tomi,
mipi-dbi-bus might not belong to include/video/panel/ though, as it can be used for non-panel devices (at least in theory). The future mipi-dsi-bus certainly will.
They are both display busses. So while they could be used for anything, I find it quite unlikely as there are much better alternatives for generic bus needs.
My point is that they could be used for display devices other than panels. This is especially true for DSI, as there are DSI to HDMI converters. Technically speaking that's also true for DBI, as DBI chips convert from DBI to DPI, but we can group both the DBI-to-DPI chip and the panel in a single panel object.
Would you be able to send incremental patches on top of v2 to implement the solution you have in mind ? It would be neat if you could also implement mipi- dsi-bus for the OMAP DSS and test the code with a real device :-)
Yes, I'd like to try this out on OMAP, both DBI and DSI. However, I fear it'll be quite complex due to the dependencies all around we have in the current driver. We're working on simplifying things so that it'll be easier to try thing like the panel framework, though, so we're going in the right direction.
If you want the panel framework to support your use cases I'm afraid you will need to work on that ;-)
Generally about locks, if we define that panel ops may only be called exclusively, does it simplify things? I think we can make such requirements, as there should be only one display framework that handles the panel. Then we don't need locking for things like enable/disable.
Pushing locking to callers would indeed simplify panel drivers, but we need to make sure we won't need to expose a panel to several callers in the future.
I have a feeling that would be a bad idea.
Display related stuff are quite sensitive to any delays, so any extra transactions over, say, DSI bus could cause a noticeable glitch on the screen. I'm not sure what are all the possible ops that a panel can offer, but I think all that affect the display or could cause delays should be handled by one controlling entity (drm or such). The controlling entity needs to handle locking anyway, so in that sense I don't think it's an extra burden for it.
The things that come to my mind that could possibly cause calls to the panel outside drm: debugfs, sysfs, audio, backlight. Of those, I think backlight should go through drm. Audio, no idea. debugfs and sysfs locking needs to be handled by the panel driver, and they are a bit problematic as I guess having them requires full locking.
On Tue, 2012-08-21 at 01:29 +0200, Laurent Pinchart wrote:
Hi Tomi,
On Monday 20 August 2012 14:39:30 Tomi Valkeinen wrote:
On Sat, 2012-08-18 at 03:16 +0200, Laurent Pinchart wrote:
Hi Tomi,
mipi-dbi-bus might not belong to include/video/panel/ though, as it can be used for non-panel devices (at least in theory). The future mipi-dsi-bus certainly will.
They are both display busses. So while they could be used for anything, I find it quite unlikely as there are much better alternatives for generic bus needs.
My point is that they could be used for display devices other than panels. This is especially true for DSI, as there are DSI to HDMI converters. Technically speaking that's also true for DBI, as DBI chips convert from DBI to DPI, but we can group both the DBI-to-DPI chip and the panel in a single panel object.
Ah, ok. I thought "panels" would include these buffer/converter chips.
I think we should have one driver for one indivisible hardware entity. So if you've got a panel module that contains DBI receiver, buffer memory and a DPI panel, let's just have one "DBI panel" driver for it.
If we get lots of different panel modules containing the same DBI RX IP, we could have the DBI IP part as a common library, but still have one panel driver per panel module.
But how do you see the case for separate converter/buffer chips? Are they part of the generic panel framework? I guess they kinda have to be. On one side they use the "panel" API control the bus they are connected to, and on the other they offer an API for the connected panel to use the bus they provide.
Did you just mean we should have a separate directory for them, while still part of the same framework, or...?
Tomi
Hi Tomi,
On Tuesday 21 August 2012 08:49:57 Tomi Valkeinen wrote:
On Tue, 2012-08-21 at 01:29 +0200, Laurent Pinchart wrote:
On Monday 20 August 2012 14:39:30 Tomi Valkeinen wrote:
On Sat, 2012-08-18 at 03:16 +0200, Laurent Pinchart wrote:
Hi Tomi,
mipi-dbi-bus might not belong to include/video/panel/ though, as it can be used for non-panel devices (at least in theory). The future mipi-dsi-bus certainly will.
They are both display busses. So while they could be used for anything, I find it quite unlikely as there are much better alternatives for generic bus needs.
My point is that they could be used for display devices other than panels. This is especially true for DSI, as there are DSI to HDMI converters. Technically speaking that's also true for DBI, as DBI chips convert from DBI to DPI, but we can group both the DBI-to-DPI chip and the panel in a single panel object.
Ah, ok. I thought "panels" would include these buffer/converter chips.
I think we should have one driver for one indivisible hardware entity. So if you've got a panel module that contains DBI receiver, buffer memory and a DPI panel, let's just have one "DBI panel" driver for it.
If we get lots of different panel modules containing the same DBI RX IP, we could have the DBI IP part as a common library, but still have one panel driver per panel module.
Sounds good to me.
But how do you see the case for separate converter/buffer chips? Are they part of the generic panel framework? I guess they kinda have to be. On one side they use the "panel" API control the bus they are connected to, and on the other they offer an API for the connected panel to use the bus they provide.
The DBI/DSI APIs will not be panel-specific (they won't contain any reference to "panel") I'm thus thinking of moving them from drivers/video/panel/ to drivers/video/.
Furthermore, a DSI-to-HDMI converter chip is not a panel, but needs to expose display-related operations in a way similar to panels. I was thus wondering if we shouldn't replace the panel structure with some kind of video entity structure that would expose operations similar to panels. We could then extend that structure with converter-specific operations later. The framework would become a bit more generic, while remaining display-specific.
Did you just mean we should have a separate directory for them, while still part of the same framework, or...?
Hi Laurent, Do you plan to add an API to get and parse EDID to mode list? video mode is tightly coupled with panel that is capable of hot-plug. Or you are busy on modifying EDID parsing code for sharing it amoung DRM/FB/etc? I see you mentioned this in Mar. It is great if you are considering add more info into video mode, such as pixel repeating, 3D timing related parameter. I have some code for CEA modes filtering and 3D parsing, but still tight coupled with FB and with a little hack style.
My HDMI driver is implemented as lcd device as you mentioned here. But more complex than other lcd devices for a kthread is handling hot-plug/EDID/HDCP/ASoC etc.
I also feel a little weird to add code parsing HDMI audio related info in fbmod.c in my current implementation, thought it is the only place to handle EDID in kernel. Your panel framework provide a better place to add panel related audio/HDCP code. panel notifier can also trigger hot-plug related feature, such as HDCP start.
Looking forward to your hot-plug panel patch. Or I can help add it if you would like me to.
Thanks! Jun
Hi Laurent,
Basically I agree that we need a common panel framework. I just have some questions: 1. I think we should add color format in videomode - if we use such common video mode structure shared across subsystems. In HDMI, colors are bind with timings tightly. We need a combined videomode with timing and color format together. 2. I think we should add "set_videomode" interface. It helps HDMI monitors to set EDIDs.
Hi Zhou,
On Tuesday 04 September 2012 16:20:38 Zhou Zhu wrote:
Hi Laurent,
Basically I agree that we need a common panel framework. I just have some questions:
- I think we should add color format in videomode - if we use such
common video mode structure shared across subsystems. In HDMI, colors are bind with timings tightly. We need a combined videomode with timing and color format together.
What kind of color formats do you have in mind ?
- I think we should add "set_videomode" interface. It helps HDMI
monitors to set EDIDs.
For panels that support several video modes, sure, we need a way to set the video mode. I don't have access to any such panel though, that's why the operation has been left out. It wouldn't be difficult to add it when a real use case will come up.
What do you mean exactly about HDMI monitors setting EDID ?
Hi Jun,
I've finally been able to resume my work on the panel framework (I hope to post a v2 at the end of the week).
On Thursday 23 August 2012 14:23:01 Jun Nie wrote:
Hi Laurent, Do you plan to add an API to get and parse EDID to mode list?
An API to get the raw EDID data is likely needed. Parsing EDID data in the panel driver and providing the modes to the caller isn't enough, as EDID contains more than just video modes. I'm not sure whether a driver for an EDID-aware panel should parse the EDID data internally and provide both modes and raw EDID data, or only raw EDID data.
video mode is tightly coupled with panel that is capable of hot-plug. Or you are busy on modifying EDID parsing code for sharing it amoung DRM/FB/etc? I see you mentioned this in Mar.
That's needed as well, but -ENOTIME :-S
It is great if you are considering add more info into video mode, such as pixel repeating, 3D timing related parameter.
Please have a look at "[PATCH 2/2 v6] of: add generic videomode description" on dri-devel. There's a proposal for a common video mode structure.
I have some code for CEA modes filtering and 3D parsing, but still tight coupled with FB and with a little hack style.
My HDMI driver is implemented as lcd device as you mentioned here.
But more complex than other lcd devices for a kthread is handling hot-plug/EDID/HDCP/ASoC etc.
I also feel a little weird to add code parsing HDMI audio related
info in fbmod.c in my current implementation, thought it is the only place to handle EDID in kernel. Your panel framework provide a better place to add panel related audio/HDCP code. panel notifier can also trigger hot-plug related feature, such as HDCP start.
That's a good idea. I was wondering whether to put the common EDID parser in drivers/gpu/drm, drivers/video or drivers/media. Putting it wherever the panel framework will be might be a good option as well.
Looking forward to your hot-plug panel patch. Or I can help add it
if you would like me to.
I'll try to post a v2 at the end of the week, but likely without much hot-plug support. Patches and enhancement proposals will be welcome.
Hi Tomi,
2012/8/17 Tomi Valkeinen tomi.valkeinen@ti.com:
Hi,
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If
Oookay, where to start... ;)
A few cosmetic/general comments first.
I find the file naming a bit strange. You have panel.c, which is the core framework, panel-dbi.c, which is the DBI bus, panel-r61517.c, which is driver for r61517 panel...
Perhaps something in this direction (in order): panel-core.c, mipi-dbi-bus.c, panel-r61517.c? And we probably end up with quite a lot of panel drivers, perhaps we should already divide these into separate directories, and then we wouldn't need to prefix each panel with "panel-" at all.
Should we aim for DT only solution from the start? DT is the direction we are going, and I feel the older platform data stuff would be deprecated soon.
Something missing from the intro is how this whole thing should be used. It doesn't help if we know how to turn on the panel, we also need to display something on it =). So I think some kind of diagram/example of how, say, drm would use this thing, and also how the SoC specific DBI bus driver would be done, would clarify things.
We have discussed face to face about the different hardware setups and scenarios that we should support, but I'll list some of them here for others:
- We need to support chains of external display chips and panels. A
simple example is a chip that takes DSI in, and outputs DPI. In that case we'd have a chain of SoC -> DSI2DPI -> DPI panel.
In final products I think two external devices is the maximum (at least I've never seen three devices in a row), but in theory and in development environments the chain can be arbitrarily long. Also the connections are not necessarily 1-to-1, but a device can take one input while it has two outputs, or a device can take two inputs.
Now, I think two external devices is a must requirement. I'm not sure if supporting more is an important requirement. However, if we support two devices, it could be that it's trivial to change the framework to support n devices.
- Panels and display chips are all but standard. They very often have
their own sequences how to do things, have bugs, or implement some feature in slightly different way than some other panel. This is why the panel driver should be able to control or define the way things happen.
As an example, Sharp LQ043T1DG01 panel (www.sharpsme.com/download/LQ043T1DG01-SP-072106pdf). It is enabled with the following sequence:
- Enable VCC and AVDD regulators
- Wait min 50ms
- Enable full video stream (pck, syncs, pixels) from SoC
- Wait min 0.5ms
- Set DISP GPIO, which turns on the display panel
Here we could split the enabling of panel to two parts, prepare (in this case starts regulators and waits 50ms) and finish (wait 0.5ms and set DISP GPIO), and the upper layer would start the video stream in between.
I realize this could be done with the PANEL_ENABLE_* levels in your RFC, but I don't think the concepts quite match:
- PANEL_ENABLE_BLANK level is needed for "smart panels", as we need to
configure them and send the initial frame at that operating level. With
The smart panel means command mode way(same as cpu mode)? This panel includes framebuffer internally and needs triggering from Display controller to update a new frame on that internal framebuffer. I think we also need this trigger interface.
Thanks, Inki Dae
dummy panels there's really no such level, there's just one enable sequence that is always done right away.
- I find waiting at the beginning of a function very ugly (what are we
waiting for?) and we'd need that when changing the panel to PANEL_ENABLE_ON level.
- It's still limited if the panel is a stranger one (see following
example).
Consider the following theoretical panel enable example, taken to absurd level just to show the general problem:
- Enable regulators
- Enable video stream
- Wait 50ms
- Disable video stream
- Set enable GPIO
- Enable video stream
This one would be rather impossible with the upper layer handling the enabling of the video stream. Thus I see that the panel driver needs to control the sequences, and the Sharp panel driver's enable would look something like:
regulator_enable(...); sleep(); dpi_enable_video(); sleep(); gpip_set(..);
Note that even with this model we still need the PANEL_ENABLE levels you have.
I'm not sure I understand the panel unload problem you mentioned. Nobody should have direct references to the panel functions, so there shouldn't be any automatic references that would prevent module unloading. So when the user does rmmod panel-mypanel, the panel driver's remove will be called. It'll unregister itself from the panel framework, which causes notifications and the display driver will stop using the panel. After that nobody has pointers to the panel, and it can safely be unloaded.
It could cause some locking issues, though. First the panel's remove could take a lock, but the remove sequence would cause the display driver to call disable on the panel, which could again try to take the same lock...
Tomi
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi,
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
Hi everybody,
While working on DT bindings for the Renesas Mobile SoC display controller (a.k.a. LCDC) I quickly realized that display panel implementation based on board code callbacks would need to be replaced by a driver-based panel framework.
I thought I'd try to approach the common panel framework by creating device tree examples that OMAP would need. I only thought about the connections and organization, not individual properties, so I have not filled any "compatible" or such properties.
What I have below is first DT data for OMAP SoC (more or less what omap4 has), then display examples of different board setups. The hardware examples I have here are all real, so no imaginary use cases =).
We don't need to implement support for all these at once, but I think the DT data structure should be right from the start, so I'm posting this to get discussion about the format.
OMAP SoC ========
So here's first the SoC specific display nodes. OMAP has a DSS (display subsystem) block, which contains the following elements:
- DISPC (display controller) reads the pixels from memory and outputs them using specified video timings. DISPC has three outputs, LCD0, LCD1 and TV. These are SoC internal outputs, they do not go outside the SoC.
- DPI gets its data from DISPC's LCD0, and outputs MIPI DPI (parallel RBG)
- Two independent DSI modules, which get their data from LCD0 or LCD1, and output MIPI DSI (a serial two-way video bus)
- HDMI, gets data from DISPC's TV output and outputs HDMI
/ { ocp { dss { dispc { dss-lcd0: output@0 { };
dss-lcd1: output@1 { };
dss-tv: output@2 { }; };
dpi: dpi { video-source = <&dss-lcd0>; };
dsi0: dsi@0 { video-source = <&dss-lcd0>; };
dsi1: dsi@1 { video-source = <&dss-lcd1>; };
hdmi: hdmi { video-source = <&dss-tv>; }; }; }; };
I have defined all the relevant nodes, and video-source property is used to point to the source for video data. I also define aliases for the SoC outputs so that panels can use them.
One thing to note is that the video sources for some of the blocks, like DSI, are not hardcoded in the HW, so dsi0 could get its data from LCD0 or LCD1. However, I don't think they are usually changed during runtime, and the dss driver cannot change them independently for sure (meaning that some upper layer should tell it how to change the config). Thus I specify sane defaults here, but the board dts files can of course override the video sources.
Another thing to note is that we have more outputs from OMAP than we have outputs from DISPC. This means that the same video source is used by multiple sinks (LCD0 used by DPI and DSI0). DPI and DSI0 cannot be used at the same time, obviously.
And third thing to note, DISPC node defines outputs explicitly, as it has multiple outputs, whereas the external outputs do not as they have only one output. Thus the node's output is implicitly the node itself. So, instead of having:
ds0: dsi@0 { video-source = <&dss-lcd0>; dsi0-out0: output@0 { }; };
I have:
dsi0: dsi@0 { video-source = <&dss-lcd0>; };
Of this I'm a bit unsure. I believe in most cases there's only one output, so it'd be nice to have a shorter representation, but I'm not sure if it's good to handle the cases for single and multiple outputs differently. Or if it's good to mix control and data busses, as, for example, dsi0 can be used as both control and data bus. Having the output defined explicitly would separate the control and data bus nodes.
Simple DPI panel ================
Here a board has a DPI panel, which is controlled via i2c. Panel nodes are children of the control bus, so in this case we define the panel under i2c2.
&i2c2 { dpi-lcd-panel { video-source = <&dpi>;
}; };
HDMI ====
OMAP has a HDMI output, but it cannot be connected directly to an HDMI cable. TI uses tpd12s015 chip in its board, which provides ESD, level-shifting and whatnot (I'm an SW guy, google for the chip to read the details =). tpd12s015 has a few GPIOs and powers that need to be controlled, so we need a driver for it.
There's no control bus for the tpd12s015, so it's platform device. Then there's the device for the HDMI monitor, and the DDC lines are connected to OMAP's i2c4, thus the hdmi monitor device is a child of i2c.
/ { hdmi-connector: tpd12s015 { video-source = <&hdmi>; }; };
&i2c4 { hdmi-monitor { video-source = <&hdmi-connector>; }; };
DSI bridge chip ===============
In this board we have a DSI bridge chip that is controlled via DSI, and gets the pixel data via DSI video mode stream. The chip converts this to LVDS. We then have an LVDS panel connected to this bridge chip, which is controlled via SPI.
&dsi0 { dsi2lvds: dsi2lvds { video-source = <&dsi0>;
}; };
&spi2 { lvds-lcd-panel { video-source = <&dsi2lvds>; }; };
High res dual-DSI panel =======================
Here we have a DSI video mode panel that gets its data from two DSI buses. This allows it to get the combined bandwidth of the DSI buses, achieving higher resolution than with single DSI bus. The panel is controlled via the first DSI bus.
&dsi0 { dual-dsi-panel { video-source-1 = <&dsi0>; video-source-2 = <&dsi1>;
}; };
DSI buffer chip with two outputs ================================
This board has a DSI command mode buffer chip, that contains its own framebuffer. The buffer chip has two DPI outputs, which get the pixels from the framebuffer. Similar to OMAP's DISPC this chip has multiple outputs so they need to be defined explicitly. And we also have two dummy DPI panels connected to this buffer chip.
&dsi0 { dsibuf { video-source = <&dsi0>;
dsibuf-dpi0: output@0 { };
dsibuf-dpi1: output@1 { }; }; };
/ { dpi-lcd-panel@0 { video-source = <&dsibuf-dpi0>;
};
dpi-lcd-panel@1 { video-source = <&dsibuf-dpi1>;
}; };
Tomi
Hi Tomi,
On Wednesday 19 September 2012 14:45:29 Tomi Valkeinen wrote:
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
Hi everybody,
While working on DT bindings for the Renesas Mobile SoC display controller (a.k.a. LCDC) I quickly realized that display panel implementation based on board code callbacks would need to be replaced by a driver-based panel framework.
I thought I'd try to approach the common panel framework by creating device tree examples that OMAP would need. I only thought about the connections and organization, not individual properties, so I have not filled any "compatible" or such properties.
What I have below is first DT data for OMAP SoC (more or less what omap4 has), then display examples of different board setups. The hardware examples I have here are all real, so no imaginary use cases =).
We don't need to implement support for all these at once, but I think the DT data structure should be right from the start, so I'm posting this to get discussion about the format.
Thank you for working on this proposal.
OMAP SoC
So here's first the SoC specific display nodes. OMAP has a DSS (display subsystem) block, which contains the following elements:
- DISPC (display controller) reads the pixels from memory and outputs
them using specified video timings. DISPC has three outputs, LCD0, LCD1 and TV. These are SoC internal outputs, they do not go outside the SoC.
- DPI gets its data from DISPC's LCD0, and outputs MIPI DPI (parallel
RBG)
- Two independent DSI modules, which get their data from LCD0 or LCD1,
and output MIPI DSI (a serial two-way video bus)
- HDMI, gets data from DISPC's TV output and outputs HDMI
/ { ocp { dss { dispc { dss-lcd0: output@0 { };
dss-lcd1: output@1 { }; dss-tv: output@2 { }; }; dpi: dpi { video-source = <&dss-lcd0>; }; dsi0: dsi@0 { video-source = <&dss-lcd0>; }; dsi1: dsi@1 { video-source = <&dss-lcd1>; }; hdmi: hdmi { video-source = <&dss-tv>; }; };
}; };
I have defined all the relevant nodes, and video-source property is used to point to the source for video data. I also define aliases for the SoC outputs so that panels can use them.
One thing to note is that the video sources for some of the blocks, like DSI, are not hardcoded in the HW, so dsi0 could get its data from LCD0 or LCD1.
What about the source that are hardwired in hardware ? Shouldn't those be hardcoded in the driver instead ?
However, I don't think they are usually changed during runtime, and the dss driver cannot change them independently for sure (meaning that some upper layer should tell it how to change the config). Thus I specify sane defaults here, but the board dts files can of course override the video sources.
I'm not sure whether default settings like those really belong to the DT. I'm no expert on that topic though.
Another thing to note is that we have more outputs from OMAP than we have outputs from DISPC. This means that the same video source is used by multiple sinks (LCD0 used by DPI and DSI0). DPI and DSI0 cannot be used at the same time, obviously.
It might not be really obvious, as I don't see what prevents DPI and DSI0 to be used at the same time :-) Do they share physical pins ?
And third thing to note, DISPC node defines outputs explicitly, as it has multiple outputs, whereas the external outputs do not as they have only one output. Thus the node's output is implicitly the node itself. So, instead of having:
ds0: dsi@0 { video-source = <&dss-lcd0>; dsi0-out0: output@0 { }; };
I have:
dsi0: dsi@0 { video-source = <&dss-lcd0>; };
What about defining the data sinks instead of the data sources ? I find it more logical for the DSS to get the panel it's connected to than the other way around.
Of this I'm a bit unsure. I believe in most cases there's only one output, so it'd be nice to have a shorter representation, but I'm not sure if it's good to handle the cases for single and multiple outputs differently. Or if it's good to mix control and data busses, as, for example, dsi0 can be used as both control and data bus. Having the output defined explicitly would separate the control and data bus nodes.
Simple DPI panel
Here a board has a DPI panel, which is controlled via i2c. Panel nodes are children of the control bus, so in this case we define the panel under i2c2.
&i2c2 { dpi-lcd-panel { video-source = <&dpi>;
}; };
HDMI
OMAP has a HDMI output, but it cannot be connected directly to an HDMI cable. TI uses tpd12s015 chip in its board, which provides ESD, level-shifting and whatnot (I'm an SW guy, google for the chip to read the details =). tpd12s015 has a few GPIOs and powers that need to be controlled, so we need a driver for it.
There's no control bus for the tpd12s015, so it's platform device. Then there's the device for the HDMI monitor, and the DDC lines are connected to OMAP's i2c4, thus the hdmi monitor device is a child of i2c.
/ { hdmi-connector: tpd12s015 { video-source = <&hdmi>; }; };
&i2c4 { hdmi-monitor { video-source = <&hdmi-connector>; }; };
So this implied we would have the following chain ?
DISPC (on SoC) -> HDMI (on SoC) -> TPD12S015 (on board) -> HDMI monitor (off board)
Should we then have a driver for the HDMI monitor ?
DSI bridge chip
In this board we have a DSI bridge chip that is controlled via DSI, and gets the pixel data via DSI video mode stream. The chip converts this to LVDS. We then have an LVDS panel connected to this bridge chip, which is controlled via SPI.
&dsi0 { dsi2lvds: dsi2lvds { video-source = <&dsi0>;
}; };
&spi2 { lvds-lcd-panel { video-source = <&dsi2lvds>; }; };
High res dual-DSI panel
Here we have a DSI video mode panel that gets its data from two DSI buses. This allows it to get the combined bandwidth of the DSI buses, achieving higher resolution than with single DSI bus. The panel is controlled via the first DSI bus.
&dsi0 { dual-dsi-panel { video-source-1 = <&dsi0>; video-source-2 = <&dsi1>;
}; };
DSI buffer chip with two outputs
This board has a DSI command mode buffer chip, that contains its own framebuffer. The buffer chip has two DPI outputs, which get the pixels from the framebuffer. Similar to OMAP's DISPC this chip has multiple outputs so they need to be defined explicitly. And we also have two dummy DPI panels connected to this buffer chip.
&dsi0 { dsibuf { video-source = <&dsi0>;
dsibuf-dpi0: output@0 { }; dsibuf-dpi1: output@1 { };
}; };
/ { dpi-lcd-panel@0 { video-source = <&dsibuf-dpi0>;
};
dpi-lcd-panel@1 { video-source = <&dsibuf-dpi1>;
}; };
On 2012-10-31 15:13, Laurent Pinchart wrote:
OMAP SoC
So here's first the SoC specific display nodes. OMAP has a DSS (display subsystem) block, which contains the following elements:
- DISPC (display controller) reads the pixels from memory and outputs
them using specified video timings. DISPC has three outputs, LCD0, LCD1 and TV. These are SoC internal outputs, they do not go outside the SoC.
- DPI gets its data from DISPC's LCD0, and outputs MIPI DPI (parallel
RBG)
- Two independent DSI modules, which get their data from LCD0 or LCD1,
and output MIPI DSI (a serial two-way video bus)
- HDMI, gets data from DISPC's TV output and outputs HDMI
/ { ocp { dss { dispc { dss-lcd0: output@0 { };
dss-lcd1: output@1 { }; dss-tv: output@2 { }; }; dpi: dpi { video-source = <&dss-lcd0>; }; dsi0: dsi@0 { video-source = <&dss-lcd0>; }; dsi1: dsi@1 { video-source = <&dss-lcd1>; }; hdmi: hdmi { video-source = <&dss-tv>; }; };
}; };
I have defined all the relevant nodes, and video-source property is used to point to the source for video data. I also define aliases for the SoC outputs so that panels can use them.
One thing to note is that the video sources for some of the blocks, like DSI, are not hardcoded in the HW, so dsi0 could get its data from LCD0 or LCD1.
What about the source that are hardwired in hardware ? Shouldn't those be hardcoded in the driver instead ?
Even if both the DSI and the DISPC are parts of OMAP DSS, and part of the SoC, they are separate IPs. We should look at them the same way we'd consider chips that are outside the SoC.
So things that are internal to a device can (and I think should) be hardcoded in the driver, but integration details, the connections between the IPs, etc, should be described in the DT data.
Then again, we do have (and need) a driver for the "dss" node in the above DT data. This dss represents dss_core, a "glue" IP that contains the rest of the DSS blocks and muxes and such. It could be argued that this dss_core driver does indeed know the integration details, and thus there's no need to represent them in the DT data.
However, I do think that we should represent the DISPC outputs with generic display entities inside CPF, just like DSI and the panels. And we do need to set the connections between these entities. So the question is, do we get those connections from the DT data, or are they hardcoded in the dss_core driver.
I don't currently have strong opinions on either direction. Both make sense to me. But I think this is SoC driver implementation specific, and the common panel framework doesn't need to force this to either direction. Both should be possible from CPF's point of view.
However, I don't think they are usually changed during runtime, and the dss driver cannot change them independently for sure (meaning that some upper layer should tell it how to change the config). Thus I specify sane defaults here, but the board dts files can of course override the video sources.
I'm not sure whether default settings like those really belong to the DT. I'm no expert on that topic though.
I agree. But I also don't have a good solution how the driver would find good choices for these settings...
Another thing to note is that we have more outputs from OMAP than we have outputs from DISPC. This means that the same video source is used by multiple sinks (LCD0 used by DPI and DSI0). DPI and DSI0 cannot be used at the same time, obviously.
It might not be really obvious, as I don't see what prevents DPI and DSI0 to be used at the same time :-) Do they share physical pins ?
I think they do, but even if they didn't, there's just one source for two outputs. So if the SoC design is such that the only video source for DPI is LCD0, and the only video source for DSI0 is LCD0, and presuming you can't send the video data to both destinations, then only one of DPI and DSI0 can be enabled at the same time.
Even if the LCD0 could send the pixel stream to both DPI and DSI0, it'd be "interesting", because the original video timings and pixel clock are programmed in the LCD0 output, and thus both DPI and DSI0 get the same timings. If DPI would want to use some other mode, DSI would most likely go nuts.
So my opinion is that we should only allow 1:1 connections between sources and sinks. If a component has multiple outputs, and even if those outputs give the exact same data, it should define multiple sources.
And third thing to note, DISPC node defines outputs explicitly, as it has multiple outputs, whereas the external outputs do not as they have only one output. Thus the node's output is implicitly the node itself. So, instead of having:
ds0: dsi@0 { video-source = <&dss-lcd0>; dsi0-out0: output@0 { }; };
I have:
dsi0: dsi@0 { video-source = <&dss-lcd0>; };
What about defining the data sinks instead of the data sources ? I find it more logical for the DSS to get the panel it's connected to than the other way around.
Good question. We look at this from different angles. Is the DSS connected to the panel, or is the panel connected to the DSS? ;)
I see this the same way than, say, regulators. We have a device and a driver for it, and the driver wants to use a hw resource. If the resource is a regulator, the DT data for the device will contain a reference to the regulator. The driver will then get the regulator, and use it to operate the device.
Similarly for the display devices. We have a driver for a, say, i2c device. The DT data for that device will contain a reference to the source for the video data. The driver for the device will then use this reference to enable/disable/etc the video stream (i.e. operate the device).
The regulators don't even really know anything about the users of the regulators. Somebody just "gets" a regulator and enables it. The same should work for display entities also. There's no need for the video source to know anything about the sink.
So looking at a chain like:
DISPC -> DPI -> Panel
I see the component on the right side using the component on its left. And thus it makes sense to me to have references from right to left, i.e. panel has reference to DPI, etc.
Also, it makes sense with DT data files. For example, for omap4 we'll have omap4.dtsi, which describes common omap4 details. This file would contain the omap dss nodes. Then we have omap4-panda.dts, which includes omap4.dtsi. omap4-panda.dts contains data for the displays on this board. Thus we have something like:
omap4.dtsi:
dss { dsi0: dsi@0 { ... }; };
omap4-panda.dts:
/ { panel { video-source = <&dsi0>; }; };
So it comes out quite nicely. If we had the references the other way around, omap4-panda.dts would need to have the panel definition, and also override the dsi0 node from omap4.dtsi, and set the sink to the panel.
I have to say we've had this mind-set with omapdss for a long time, so I may be a bit blind to other alternative. Do you have any practical examples how linking the other way around would be better?
Of this I'm a bit unsure. I believe in most cases there's only one output, so it'd be nice to have a shorter representation, but I'm not sure if it's good to handle the cases for single and multiple outputs differently. Or if it's good to mix control and data busses, as, for example, dsi0 can be used as both control and data bus. Having the output defined explicitly would separate the control and data bus nodes.
Simple DPI panel
Here a board has a DPI panel, which is controlled via i2c. Panel nodes are children of the control bus, so in this case we define the panel under i2c2.
&i2c2 { dpi-lcd-panel { video-source = <&dpi>;
}; };
HDMI
OMAP has a HDMI output, but it cannot be connected directly to an HDMI cable. TI uses tpd12s015 chip in its board, which provides ESD, level-shifting and whatnot (I'm an SW guy, google for the chip to read the details =). tpd12s015 has a few GPIOs and powers that need to be controlled, so we need a driver for it.
There's no control bus for the tpd12s015, so it's platform device. Then there's the device for the HDMI monitor, and the DDC lines are connected to OMAP's i2c4, thus the hdmi monitor device is a child of i2c.
/ { hdmi-connector: tpd12s015 { video-source = <&hdmi>; }; };
&i2c4 { hdmi-monitor { video-source = <&hdmi-connector>; }; };
So this implied we would have the following chain ?
DISPC (on SoC) -> HDMI (on SoC) -> TPD12S015 (on board) -> HDMI monitor (off board)
Yes.
Although to be honest, I'm not sure if the TPD12S015 should be part of the chain, or somehow separate. It's so simple, kinda pass-through IP, that it would be nicer to have the HDMI monitor connected to the OMAP HDMI. But this is omap specific question, not really CPF thing.
Should we then have a driver for the HDMI monitor ?
Yes, that is my opinion:
- It would be strange if for some video chains we would have panel devices in the end of the chain, and for some we wouldn't. I guess we'd need some trickery to make them both work the same way if there's no panel devices for some cases.
- While in 99% of the cases we can use a common, simple HDMI monitor driver, there could be HDMI monitors with special features. We could detect this monitor by reading the EDID and if it's this special case, use a specific driver for it instead of the common HDMI driver. This is perhaps not very likely with HDMI, but I could imagine eDP panels with special features.
So I imagine that we could use hot-plug here. The HDMI monitor device would not exist until a HDMI cable is connected. The SoC's HDMI driver (or whatever is before the HDMI monitor in the chain) gets a hotplug interrupt, and it would then add the device, which would then trigger probe of the corresponding HDMI monitor driver.
Actually, thinking about this, what I said in the above paragraph wouldn't work. The SoC's HDMI driver can't know what kind of device to create, unless we have a HDMI bus and HDMI devices. Which, I think, we shouldn't have, as HDMI monitors are usually controlled via i2c, and thus the HDMI monitors should be i2c devices.
So I don't really know how this hotplug would work =). It's just an idea, not a scenario I have at hand.
Tomi
Hi Laurent. sorry for being late.
2012/8/17 Laurent Pinchart laurent.pinchart@ideasonboard.com:
Hi everybody,
While working on DT bindings for the Renesas Mobile SoC display controller (a.k.a. LCDC) I quickly realized that display panel implementation based on board code callbacks would need to be replaced by a driver-based panel framework.
Several driver-based panel support solution already exist in the kernel.
The LCD device class is implemented in drivers/video/backlight/lcd.c and exposes a kernel API in include/linux/lcd.h. That API is tied to the FBDEV API for historical reason, uses board code callback for reset and power management, and doesn't include support for standard features available in today's "smart panels".
OMAP2+ based systems use custom panel drivers available in drivers/video/omap2/displays. Those drivers are based on OMAP DSS (display controller) specific APIs.
Similarly, Exynos based systems use custom panel drivers available in drivers/video/exynos. Only a single driver (s6e8ax0) is currently available. That driver is based on Exynos display controller specific APIs and on the LCD device class API.
I've brought up the issue with Tomi Valkeinen (OMAP DSS maintainer) and Marcus Lorentzon (working on panel support for ST/Linaro), and we agreed that a generic panel framework for display devices is needed. These patches implement a first proof of concept.
One of the main reasons for creating a new panel framework instead of adding missing features to the LCD framework is to avoid being tied to the FBDEV framework. Panels will be used by DRM drivers as well, and their API should thus be subsystem-agnostic. Note that the panel framework used the fb_videomode structure in its API, this will be replaced by a common video mode structure shared across subsystems (there's only so many hours per day).
Panels, as used in these patches, are defined as physical devices combining a matrix of pixels and a controller capable of driving that matrix.
Panel physical devices are registered as children of the control bus the panel controller is connected to (depending on the panel type, we can find platform devices for dummy panels with no control bus, or I2C, SPI, DBI, DSI, ... devices). The generic panel framework matches registered panel devices with panel drivers and call the panel drivers probe method, as done by other device classes in the kernel. The driver probe() method is responsible for instantiating a struct panel instance and registering it with the generic panel framework.
Display drivers are panel consumers. They register a panel notifier with the framework, which then calls the notifier when a matching panel is registered. The reason for this asynchronous mode of operation, compared to how drivers acquire regulator or clock resources, is that the panel can use resources provided by the display driver. For instance a panel can be a child of the DBI or DSI bus controlled by the display device, or use a clock provided by that device. We can't defer the display device probe until the panel is registered and also defer the panel device probe until the display is registered. As most display drivers need to handle output devices hotplug (HDMI monitors for instance), handling panel through a notification system seemed to be the easiest solution.
Note that this brings a different issue after registration, as display and panel drivers would take a reference to each other. Those circular references would make driver unloading impossible. I haven't found a good solution for that problem yet (hence the RFC state of those patches), and I would appreciate your input here. This might also be a hint that the framework design is wrong to start with. I guess I can't get everything right on the first try ;-)
Getting hold of the panel is the most complex part. Once done, display drivers can call abstract operations provided by panel drivers to control the panel operation. These patches implement three of those operations (enable, start transfer and get modes). More operations will be needed, and those three operations will likely get modified during review. Most of the panels on devices I own are dumb panels with no control bus, and are thus not the best candidates to design a framework that needs to take complex panels' needs into account.
In addition to the generic panel core, I've implemented MIPI DBI (Display Bus Interface, a parallel bus for panels that supports read/write transfers of commands and data) bus support, as well as three panel drivers (dummy panels with no control bus, and Renesas R61505- and R61517-based panels, both using DBI as their control bus). Only the dummy panel driver has been tested as I lack hardware for the two other drivers.
I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If you can find a clever way to solve the cyclic references issue described above I'll buy you a beer at the next conference we will both attend. If you think the proposed solution is too complex, or too simple, I'm all ears. I personally already feel that we might need something even more generic to support other kinds of external devices connected to display controllers, such as external DSI to HDMI converters for instance. Some kind of video entity exposing abstract operations like the panels do would make sense, in which case panels would "inherit" from that video entity.
Speaking of conferences, I will attend the KS/LPC in San Diego in a bit more than a week, and would be happy to discuss this topic face to face there.
Laurent Pinchart (5): video: Add generic display panel core video: panel: Add dummy panel support video: panel: Add MIPI DBI bus support video: panel: Add R61505 panel support video: panel: Add R61517 panel support
how about using 'buses' directory instead of 'panel' and adding 'panel' under that like below? video/buess: display bus frameworks such as MIPI-DBI/DSI and eDP are placed. video/buess/panel: panel drivers based on display bus-based drivers are placed.
I think MIPI-DBI(Display Bus Interface)/DSI(Display Serial Interface) and eDP are the bus interfaces for display controllers such as DISC(OMAP SoC) and FIMC(Exynos SoC).
Thanks, Inki Dae
drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/panel/Kconfig | 37 +++ drivers/video/panel/Makefile | 5 + drivers/video/panel/panel-dbi.c | 217 +++++++++++++++ drivers/video/panel/panel-dummy.c | 103 +++++++ drivers/video/panel/panel-r61505.c | 520 ++++++++++++++++++++++++++++++++++++ drivers/video/panel/panel-r61517.c | 408 ++++++++++++++++++++++++++++ drivers/video/panel/panel.c | 269 +++++++++++++++++++ include/video/panel-dbi.h | 92 +++++++ include/video/panel-dummy.h | 25 ++ include/video/panel-r61505.h | 27 ++ include/video/panel-r61517.h | 28 ++ include/video/panel.h | 111 ++++++++ 14 files changed, 1844 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/Kconfig create mode 100644 drivers/video/panel/Makefile create mode 100644 drivers/video/panel/panel-dbi.c create mode 100644 drivers/video/panel/panel-dummy.c create mode 100644 drivers/video/panel/panel-r61505.c create mode 100644 drivers/video/panel/panel-r61517.c create mode 100644 drivers/video/panel/panel.c create mode 100644 include/video/panel-dbi.h create mode 100644 include/video/panel-dummy.h create mode 100644 include/video/panel-r61505.h create mode 100644 include/video/panel-r61517.h create mode 100644 include/video/panel.h
-- Regards,
Laurent Pinchart
-- To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
correct some typo. Sorry for this.
2012/10/20 Inki Dae inki.dae@samsung.com:
Hi Laurent. sorry for being late.
2012/8/17 Laurent Pinchart laurent.pinchart@ideasonboard.com:
Hi everybody,
While working on DT bindings for the Renesas Mobile SoC display controller (a.k.a. LCDC) I quickly realized that display panel implementation based on board code callbacks would need to be replaced by a driver-based panel framework.
Several driver-based panel support solution already exist in the kernel.
The LCD device class is implemented in drivers/video/backlight/lcd.c and exposes a kernel API in include/linux/lcd.h. That API is tied to the FBDEV API for historical reason, uses board code callback for reset and power management, and doesn't include support for standard features available in today's "smart panels".
OMAP2+ based systems use custom panel drivers available in drivers/video/omap2/displays. Those drivers are based on OMAP DSS (display controller) specific APIs.
Similarly, Exynos based systems use custom panel drivers available in drivers/video/exynos. Only a single driver (s6e8ax0) is currently available. That driver is based on Exynos display controller specific APIs and on the LCD device class API.
I've brought up the issue with Tomi Valkeinen (OMAP DSS maintainer) and Marcus Lorentzon (working on panel support for ST/Linaro), and we agreed that a generic panel framework for display devices is needed. These patches implement a first proof of concept.
One of the main reasons for creating a new panel framework instead of adding missing features to the LCD framework is to avoid being tied to the FBDEV framework. Panels will be used by DRM drivers as well, and their API should thus be subsystem-agnostic. Note that the panel framework used the fb_videomode structure in its API, this will be replaced by a common video mode structure shared across subsystems (there's only so many hours per day).
Panels, as used in these patches, are defined as physical devices combining a matrix of pixels and a controller capable of driving that matrix.
Panel physical devices are registered as children of the control bus the panel controller is connected to (depending on the panel type, we can find platform devices for dummy panels with no control bus, or I2C, SPI, DBI, DSI, ... devices). The generic panel framework matches registered panel devices with panel drivers and call the panel drivers probe method, as done by other device classes in the kernel. The driver probe() method is responsible for instantiating a struct panel instance and registering it with the generic panel framework.
Display drivers are panel consumers. They register a panel notifier with the framework, which then calls the notifier when a matching panel is registered. The reason for this asynchronous mode of operation, compared to how drivers acquire regulator or clock resources, is that the panel can use resources provided by the display driver. For instance a panel can be a child of the DBI or DSI bus controlled by the display device, or use a clock provided by that device. We can't defer the display device probe until the panel is registered and also defer the panel device probe until the display is registered. As most display drivers need to handle output devices hotplug (HDMI monitors for instance), handling panel through a notification system seemed to be the easiest solution.
Note that this brings a different issue after registration, as display and panel drivers would take a reference to each other. Those circular references would make driver unloading impossible. I haven't found a good solution for that problem yet (hence the RFC state of those patches), and I would appreciate your input here. This might also be a hint that the framework design is wrong to start with. I guess I can't get everything right on the first try ;-)
Getting hold of the panel is the most complex part. Once done, display drivers can call abstract operations provided by panel drivers to control the panel operation. These patches implement three of those operations (enable, start transfer and get modes). More operations will be needed, and those three operations will likely get modified during review. Most of the panels on devices I own are dumb panels with no control bus, and are thus not the best candidates to design a framework that needs to take complex panels' needs into account.
In addition to the generic panel core, I've implemented MIPI DBI (Display Bus Interface, a parallel bus for panels that supports read/write transfers of commands and data) bus support, as well as three panel drivers (dummy panels with no control bus, and Renesas R61505- and R61517-based panels, both using DBI as their control bus). Only the dummy panel driver has been tested as I lack hardware for the two other drivers.
I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If you can find a clever way to solve the cyclic references issue described above I'll buy you a beer at the next conference we will both attend. If you think the proposed solution is too complex, or too simple, I'm all ears. I personally already feel that we might need something even more generic to support other kinds of external devices connected to display controllers, such as external DSI to HDMI converters for instance. Some kind of video entity exposing abstract operations like the panels do would make sense, in which case panels would "inherit" from that video entity.
Speaking of conferences, I will attend the KS/LPC in San Diego in a bit more than a week, and would be happy to discuss this topic face to face there.
Laurent Pinchart (5): video: Add generic display panel core video: panel: Add dummy panel support video: panel: Add MIPI DBI bus support video: panel: Add R61505 panel support video: panel: Add R61517 panel support
how about using 'buses' directory instead of 'panel' and adding
s/buses/busses
'panel' under that like below? video/buess: display bus frameworks such as MIPI-DBI/DSI and eDP are placed. video/buess/panel: panel drivers based on display bus-based drivers are placed.
video/busses: display bus frameworks such as MIPI-DBI/DSI and eDP are placed. video/busses/panel: panel drivers based on display bus-based drivers are placed.
I think MIPI-DBI(Display Bus Interface)/DSI(Display Serial Interface) and eDP are the bus interfaces for display controllers such as DISC(OMAP SoC) and FIMC(Exynos SoC).
Thanks, Inki Dae
drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/panel/Kconfig | 37 +++ drivers/video/panel/Makefile | 5 + drivers/video/panel/panel-dbi.c | 217 +++++++++++++++ drivers/video/panel/panel-dummy.c | 103 +++++++ drivers/video/panel/panel-r61505.c | 520 ++++++++++++++++++++++++++++++++++++ drivers/video/panel/panel-r61517.c | 408 ++++++++++++++++++++++++++++ drivers/video/panel/panel.c | 269 +++++++++++++++++++ include/video/panel-dbi.h | 92 +++++++ include/video/panel-dummy.h | 25 ++ include/video/panel-r61505.h | 27 ++ include/video/panel-r61517.h | 28 ++ include/video/panel.h | 111 ++++++++ 14 files changed, 1844 insertions(+), 0 deletions(-) create mode 100644 drivers/video/panel/Kconfig create mode 100644 drivers/video/panel/Makefile create mode 100644 drivers/video/panel/panel-dbi.c create mode 100644 drivers/video/panel/panel-dummy.c create mode 100644 drivers/video/panel/panel-r61505.c create mode 100644 drivers/video/panel/panel-r61517.c create mode 100644 drivers/video/panel/panel.c create mode 100644 include/video/panel-dbi.h create mode 100644 include/video/panel-dummy.h create mode 100644 include/video/panel-r61505.h create mode 100644 include/video/panel-r61517.h create mode 100644 include/video/panel.h
-- Regards,
Laurent Pinchart
-- To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Inki,
On Saturday 20 October 2012 22:10:17 Inki Dae wrote:
Hi Laurent. sorry for being late.
No worries.
2012/8/17 Laurent Pinchart laurent.pinchart@ideasonboard.com:
Hi everybody,
While working on DT bindings for the Renesas Mobile SoC display controller (a.k.a. LCDC) I quickly realized that display panel implementation based on board code callbacks would need to be replaced by a driver-based panel framework.
Several driver-based panel support solution already exist in the kernel.
The LCD device class is implemented in drivers/video/backlight/lcd.c and exposes a kernel API in include/linux/lcd.h. That API is tied to the FBDEV API for historical reason, uses board code callback for reset and power management, and doesn't include support for standard features available in today's "smart panels".
OMAP2+ based systems use custom panel drivers available in
drivers/video/omap2/displays. Those drivers are based on OMAP DSS (display controller) specific APIs.
Similarly, Exynos based systems use custom panel drivers available in
drivers/video/exynos. Only a single driver (s6e8ax0) is currently available. That driver is based on Exynos display controller specific APIs and on the LCD device class API.
I've brought up the issue with Tomi Valkeinen (OMAP DSS maintainer) and Marcus Lorentzon (working on panel support for ST/Linaro), and we agreed that a generic panel framework for display devices is needed. These patches implement a first proof of concept.
One of the main reasons for creating a new panel framework instead of adding missing features to the LCD framework is to avoid being tied to the FBDEV framework. Panels will be used by DRM drivers as well, and their API should thus be subsystem-agnostic. Note that the panel framework used the fb_videomode structure in its API, this will be replaced by a common video mode structure shared across subsystems (there's only so many hours per day).
Panels, as used in these patches, are defined as physical devices combining a matrix of pixels and a controller capable of driving that matrix.
Panel physical devices are registered as children of the control bus the panel controller is connected to (depending on the panel type, we can find platform devices for dummy panels with no control bus, or I2C, SPI, DBI, DSI, ... devices). The generic panel framework matches registered panel devices with panel drivers and call the panel drivers probe method, as done by other device classes in the kernel. The driver probe() method is responsible for instantiating a struct panel instance and registering it with the generic panel framework.
Display drivers are panel consumers. They register a panel notifier with the framework, which then calls the notifier when a matching panel is registered. The reason for this asynchronous mode of operation, compared to how drivers acquire regulator or clock resources, is that the panel can use resources provided by the display driver. For instance a panel can be a child of the DBI or DSI bus controlled by the display device, or use a clock provided by that device. We can't defer the display device probe until the panel is registered and also defer the panel device probe until the display is registered. As most display drivers need to handle output devices hotplug (HDMI monitors for instance), handling panel through a notification system seemed to be the easiest solution.
Note that this brings a different issue after registration, as display and panel drivers would take a reference to each other. Those circular references would make driver unloading impossible. I haven't found a good solution for that problem yet (hence the RFC state of those patches), and I would appreciate your input here. This might also be a hint that the framework design is wrong to start with. I guess I can't get everything right on the first try ;-)
Getting hold of the panel is the most complex part. Once done, display drivers can call abstract operations provided by panel drivers to control the panel operation. These patches implement three of those operations (enable, start transfer and get modes). More operations will be needed, and those three operations will likely get modified during review. Most of the panels on devices I own are dumb panels with no control bus, and are thus not the best candidates to design a framework that needs to take complex panels' needs into account.
In addition to the generic panel core, I've implemented MIPI DBI (Display Bus Interface, a parallel bus for panels that supports read/write transfers of commands and data) bus support, as well as three panel drivers (dummy panels with no control bus, and Renesas R61505- and R61517-based panels, both using DBI as their control bus). Only the dummy panel driver has been tested as I lack hardware for the two other drivers.
I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If you can find a clever way to solve the cyclic references issue described above I'll buy you a beer at the next conference we will both attend. If you think the proposed solution is too complex, or too simple, I'm all ears. I personally already feel that we might need something even more generic to support other kinds of external devices connected to display controllers, such as external DSI to HDMI converters for instance. Some kind of video entity exposing abstract operations like the panels do would make sense, in which case panels would "inherit" from that video entity.
Speaking of conferences, I will attend the KS/LPC in San Diego in a bit more than a week, and would be happy to discuss this topic face to face there.
Laurent Pinchart (5): video: Add generic display panel core video: panel: Add dummy panel support video: panel: Add MIPI DBI bus support video: panel: Add R61505 panel support video: panel: Add R61517 panel support
how about using 'buses' directory instead of 'panel' and adding 'panel' under that like below? video/buess: display bus frameworks such as MIPI-DBI/DSI and eDP are placed. video/buess/panel: panel drivers based on display bus-based drivers are placed.
I think MIPI-DBI(Display Bus Interface)/DSI(Display Serial Interface) and eDP are the bus interfaces for display controllers such as DISC(OMAP SoC) and FIMC(Exynos SoC).
After discussing the generic panel framework at Linaro Connect, we came to the conclusion that "panel" is too limiting a name. I will send an RFC v2 titled "common display framework", with a drivers/video/display/ directory. I'm unsure whether panels should go to drivers/video/panels/ or drivers/video/display/panels/.
dri-devel@lists.freedesktop.org