Hello,
This is a proposal to address one of the issue I've been facing with the atmel-hlcdc driver.
This driver provides a DPI (or raw RGB) bus on which we might access several devices (a panel, an HDMI encoder, or any other type of encoder).
My problem is that I have to find the best bus format according to the devices connected on the bus. Another problem is when 2 devices on the same bus requires 2 orthogonal formats, we should be able to detect this kind of issue and reject the requested mode setting.
This implementation was designed in a few hours and thus is far from perfect. It is given as a starting point to further discuss the available solution I have to support this kind of configuration.
Patches 1 and 2 implement a basic DPI bus infrastructure (or whatever name we decide to use: DPI is a MIPI standard that involves more than just video bus format description).
Patches 3 and 4 add DPI panel support, and patches 5, 6 and 7 implement a DPI host in the atmel-hlcdc driver (the driver I'm currently working on).
This implementation also has the benefit of keeping the display controller driver indepedant of slave device types (panels, encoders, bridges, ...).
Feel free to share your thoughts on this problem (and/or the proposed solution).
Best Regards,
Boris
Boris Brezillon (7): drm: add DPI bus support drm: add DPI connector/encoder definitions drm: support panels connected on a DPI bus drm: panel: move foxlink fl500wvr00-a0t panel to the DPI panel list drm: atmel-hlcdc: add DPI support drm: atmel-hlcdc: move to DPI ARM: at91/dt: define sama5d3xek panel as a DPI device
arch/arm/boot/dts/sama5d36ek.dts | 12 +- arch/arm/boot/dts/sama5d3_lcd.dtsi | 6 +- arch/arm/boot/dts/sama5d3xdm.dtsi | 28 +- drivers/gpu/drm/Kconfig | 4 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/atmel-hlcdc/Kconfig | 1 + drivers/gpu/drm/atmel-hlcdc/Makefile | 2 +- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 11 +- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 12 +- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 6 +- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dpi.c | 212 +++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c | 443 ----------------------- drivers/gpu/drm/drm_crtc.c | 2 + drivers/gpu/drm/drm_mipi_dpi.c | 369 +++++++++++++++++++ drivers/gpu/drm/panel/panel-simple.c | 331 +++++++++++++++-- include/drm/drm_mipi_dpi.h | 169 +++++++++ include/uapi/drm/drm_mode.h | 2 + 17 files changed, 1100 insertions(+), 511 deletions(-) create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dpi.c delete mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c create mode 100644 drivers/gpu/drm/drm_mipi_dpi.c create mode 100644 include/drm/drm_mipi_dpi.h
The DPI bus is a parallel bus used to interface with video components. This bus provide some control signals (HSYNC, VSYNC) and a parallel data bus used to transfer content to slave devices (panels, encoders, ...)
This DPI layer is providing a way to negotiate a video format suiting all the activated DPI devices on the bus.
As usual with busses, there are two ends: - the host: this is the device aggregating the devices on a DPI bus. It is also responsible for choosing the best format given the devices connected on its bus. - the device: it's describing a specific hardware (a panel, an encoder, or any other DRM device), and as such is attached to a driver. This driver will typically create a DRM encoder (and possibly the associated connector) and attach it to the DRM device embedding the DPI bus.
This approach will ease the work of display controllers, and hopefully provide a standard way to bind slave DRM devices to DRM devices (assuming your device is connected on DPI bus).
The term MIPI DPI or even DPI might not be the appropriate one as MIPI DPI is a standard specify more things than just the video bus format being used on the bus.
Signed-off-by: Boris Brezillon boris.brezillon@free-electrons.com --- drivers/gpu/drm/Kconfig | 4 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_mipi_dpi.c | 369 +++++++++++++++++++++++++++++++++++++++++ include/drm/drm_mipi_dpi.h | 169 +++++++++++++++++++ 4 files changed, 543 insertions(+) create mode 100644 drivers/gpu/drm/drm_mipi_dpi.c create mode 100644 include/drm/drm_mipi_dpi.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 2d97f7e..8ad8983 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -20,6 +20,10 @@ menuconfig DRM details. You should also select and configure AGP (/dev/agpgart) support if it is available for your platform.
+config DRM_MIPI_DPI + bool + depends on DRM + config DRM_MIPI_DSI bool depends on DRM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index abb4f29..a77b2bc 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o CFLAGS_drm_trace_points.o := -I$(src)
obj-$(CONFIG_DRM) += drm.o +obj-$(CONFIG_DRM_MIPI_DPI) += drm_mipi_dpi.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-$(CONFIG_DRM_USB) += drm_usb.o obj-$(CONFIG_DRM_TTM) += ttm/ diff --git a/drivers/gpu/drm/drm_mipi_dpi.c b/drivers/gpu/drm/drm_mipi_dpi.c new file mode 100644 index 0000000..403922c --- /dev/null +++ b/drivers/gpu/drm/drm_mipi_dpi.c @@ -0,0 +1,369 @@ +/* + * MIPI DPI Bus + * + * Copyright (C) 2014, Atmel + * Copyright (C) 2014, Free Electrons + * + * Author: Boris Brezillon + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <drm/drm_mipi_dpi.h> + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> + +#include <video/mipi_display.h> + +static int mipi_dpi_device_match(struct device *dev, struct device_driver *drv) +{ + return of_driver_match_device(dev, drv); +} + +static const struct dev_pm_ops mipi_dpi_device_pm_ops = { + .runtime_suspend = pm_generic_runtime_suspend, + .runtime_resume = pm_generic_runtime_resume, + .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 mipi_dpi_bus_type = { + .name = "mipi-dpi", + .match = mipi_dpi_device_match, + .pm = &mipi_dpi_device_pm_ops, +}; + +static void mipi_dpi_dev_release(struct device *dev) +{ + struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev); + + of_node_put(dev->of_node); + kfree(dpi); +} + +static const struct device_type mipi_dpi_device_type = { + .release = mipi_dpi_dev_release, +}; + +static struct mipi_dpi_device *mipi_dpi_device_alloc(struct mipi_dpi_host *host) +{ + struct mipi_dpi_device *dpi; + + dpi = kzalloc(sizeof(*dpi), GFP_KERNEL); + if (!dpi) + return ERR_PTR(-ENOMEM); + + dpi->host = host; + dpi->dev.bus = &mipi_dpi_bus_type; + dpi->dev.parent = host->dev; + dpi->dev.type = &mipi_dpi_device_type; + + device_initialize(&dpi->dev); + + return dpi; +} + +static int mipi_dpi_device_add(struct mipi_dpi_device *dpi) +{ + struct mipi_dpi_host *host = dpi->host; + + dev_set_name(&dpi->dev, "%s.%d", dev_name(host->dev), dpi->id); + + return device_add(&dpi->dev); +} + +static struct mipi_dpi_device * +of_mipi_dpi_device_add(struct mipi_dpi_host *host, struct device_node *node) +{ + struct mipi_dpi_device *dpi; + struct device *dev = host->dev; + int ret; + u32 reg; + + ret = of_property_read_u32(node, "reg", ®); + if (ret) { + dev_err(dev, "device node %s has no valid reg property: %d\n", + node->full_name, ret); + return ERR_PTR(-EINVAL); + } + + dpi = mipi_dpi_device_alloc(host); + if (IS_ERR(dpi)) { + dev_err(dev, "failed to allocate DPI device %s: %ld\n", + node->full_name, PTR_ERR(dpi)); + return dpi; + } + + dpi->dev.of_node = of_node_get(node); + dpi->id = reg; + + ret = mipi_dpi_device_add(dpi); + if (ret) { + dev_err(dev, "failed to add DPI device %s: %d\n", + node->full_name, ret); + kfree(dpi); + return ERR_PTR(ret); + } + + return dpi; +} + +void mipi_dpi_host_init(struct mipi_dpi_host *host) +{ + mutex_init(&host->lock); + INIT_LIST_HEAD(&host->devices); +} +EXPORT_SYMBOL(mipi_dpi_host_init); + +int mipi_dpi_host_register(struct mipi_dpi_host *host) +{ + struct device_node *bus_node; + struct device_node *node; + + if (!host->dev) + return -EINVAL; + + bus_node = host->of_node ?: host->dev->of_node; + if (!bus_node) + return -EINVAL; + + for_each_available_child_of_node(bus_node, node) { + pr_info("%s:%i\n", __func__, __LINE__); + /* skip nodes without reg property */ + if (!of_find_property(node, "reg", NULL)) + continue; + pr_info("%s:%i\n", __func__, __LINE__); + of_mipi_dpi_device_add(host, node); + } + + return 0; +} +EXPORT_SYMBOL(mipi_dpi_host_register); + +static int mipi_dpi_remove_device_fn(struct device *dev, void *priv) +{ + struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev); + + device_unregister(&dpi->dev); + + return 0; +} + +void mipi_dpi_host_unregister(struct mipi_dpi_host *host) +{ + device_for_each_child(host->dev, NULL, mipi_dpi_remove_device_fn); +} +EXPORT_SYMBOL(mipi_dpi_host_unregister); + +int mipi_dpi_host_apply_format(struct mipi_dpi_host *host) +{ + enum video_bus_format bus_fmt; + struct mipi_dpi_device *dpi; + int ret; + + mutex_lock(&host->lock); + + ret = host->ops->best_format(host, &bus_fmt); + if (ret) + goto err_unlock; + + ret = host->ops->set_format(host, bus_fmt); + if (ret) + goto err_unlock; + + list_for_each_entry(dpi, &host->devices, node) { + if (!dpi->enabled) + continue; + + if (dpi->ops && dpi->ops->set_format) { + ret = dpi->ops->set_format(dpi, dpi->next_format); + if (ret) + goto err_restore_formats; + } + } + + list_for_each_entry(dpi, &host->devices, node) { + dpi->current_format = dpi->next_format; + } + + host->current_format = bus_fmt; + + mutex_unlock(&host->lock); + + return 0; + +err_restore_formats: + list_for_each_entry(dpi, &host->devices, node) { + if (!dpi->enabled) + continue; + + if (dpi->ops && dpi->ops->set_format) + dpi->ops->set_format(dpi, dpi->current_format); + } + + host->ops->set_format(host, host->current_format); + +err_unlock: + mutex_unlock(&host->lock); + + return ret; +} +EXPORT_SYMBOL(mipi_dpi_host_apply_format); + +/** + * mipi_dpi_attach - attach a DPI device to its DPI host + * @dpi: DPI peripheral + */ +int mipi_dpi_attach(struct mipi_dpi_device *dpi) +{ + const struct mipi_dpi_host_ops *ops = dpi->host->ops; + bool agreed = false; + int ret; + int i; + + if (!ops || !ops->attach) + return -ENOSYS; + + if (!dpi->num_supported_formats) + return -EINVAL; + + if (dpi->num_supported_formats == 1) + dpi->current_format = dpi->supported_formats[0]; + else if (!dpi->ops || !dpi->ops->set_format) + return -EINVAL; + + for (i = 0; !agreed && i < dpi->host->num_supported_formats; i++) { + enum video_bus_format hfmt = dpi->host->supported_formats[i]; + int j; + + for (j = 0; j < dpi->num_supported_formats; j++) { + if (hfmt != dpi->host->supported_formats[i]) + continue; + + agreed = true; + break; + } + } + + if (!agreed) + return -ENOTSUPP; + + mutex_lock(&dpi->host->lock); + ret = ops->attach(dpi->host, dpi); + if (!ret) + list_add_tail(&dpi->node, &dpi->host->devices); + mutex_unlock(&dpi->host->lock); + + return ret; +} +EXPORT_SYMBOL(mipi_dpi_attach); + +/** + * mipi_dpi_detach - detach a DPI device from its DPI host + * @dpi: DPI peripheral + */ +int mipi_dpi_detach(struct mipi_dpi_device *dpi) +{ + const struct mipi_dpi_host_ops *ops = dpi->host->ops; + int ret; + + if (!ops || !ops->detach) + return -ENOSYS; + + mutex_lock(&dpi->host->lock); + ret = ops->detach(dpi->host, dpi); + if (!ret) + list_del(&dpi->node); + mutex_unlock(&dpi->host->lock); + + return ret; +} +EXPORT_SYMBOL(mipi_dpi_detach); + +static int mipi_dpi_drv_probe(struct device *dev) +{ + struct mipi_dpi_driver *drv = to_mipi_dpi_driver(dev->driver); + struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev); + + return drv->probe(dpi); +} + +static int mipi_dpi_drv_remove(struct device *dev) +{ + struct mipi_dpi_driver *drv = to_mipi_dpi_driver(dev->driver); + struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev); + + return drv->remove(dpi); +} + +static void mipi_dpi_drv_shutdown(struct device *dev) +{ + struct mipi_dpi_driver *drv = to_mipi_dpi_driver(dev->driver); + struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev); + + drv->shutdown(dpi); +} + +/** + * mipi_dpi_driver_register - register a driver for DPI devices + * @drv: DPI driver structure + */ +int mipi_dpi_driver_register(struct mipi_dpi_driver *drv) +{ + drv->driver.bus = &mipi_dpi_bus_type; + if (drv->probe) + drv->driver.probe = mipi_dpi_drv_probe; + if (drv->remove) + drv->driver.remove = mipi_dpi_drv_remove; + if (drv->shutdown) + drv->driver.shutdown = mipi_dpi_drv_shutdown; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL(mipi_dpi_driver_register); + +/** + * mipi_dpi_driver_unregister - unregister a driver for DPI devices + * @drv: DPI driver structure + */ +void mipi_dpi_driver_unregister(struct mipi_dpi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL(mipi_dpi_driver_unregister); + +static int __init mipi_dpi_bus_init(void) +{ + return bus_register(&mipi_dpi_bus_type); +} +postcore_initcall(mipi_dpi_bus_init); + +MODULE_AUTHOR("Boris Brezillon boris.brezillon@free-electrons.com"); +MODULE_DESCRIPTION("MIPI DPI Bus"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/include/drm/drm_mipi_dpi.h b/include/drm/drm_mipi_dpi.h new file mode 100644 index 0000000..6388c3f --- /dev/null +++ b/include/drm/drm_mipi_dpi.h @@ -0,0 +1,169 @@ +/* + * MIPI DPI Bus + * + * Copyright (C) 2014, Atmel + * Copyright (C) 2014, Free Electrons + * + * Author: Boris Brezillon boris.brezillon@free-electrons.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 __DRM_MIPI_DPI_H__ +#define __DRM_MIPI_DPI_H__ + +#include <linux/device.h> +#include <linux/video-bus-format.h> + +struct mipi_dpi_host; +struct mipi_dpi_device; + +/** + * struct mipi_dpi_host_ops - DPI bus operations + * @attach: attach DPI device to DPI host + * @detach: detach DPI device from DPI host + * @best_format: choose best video bus format depending on the connected + * devices. + * @set_format: configure the DPI bus to use a specific video bus format + */ +struct mipi_dpi_host_ops { + int (*attach)(struct mipi_dpi_host *host, + struct mipi_dpi_device *dpi); + int (*detach)(struct mipi_dpi_host *host, + struct mipi_dpi_device *dpi); + int (*best_format)(struct mipi_dpi_host *host, + enum video_bus_format *fmt); + int (*set_format)(struct mipi_dpi_host *host, + enum video_bus_format fmt); +}; + +/** + * struct mipi_dpi_host - DPI host device + * @dev: device exposing the DPI host + * @ddev: drm device, typically used by dpi devices to register new + * encoders/connectors + * @of_node: DPI host device node + * @possible_crtcs: the crtc this dpi host is attached to, typically + * used by dpi devices to register new encoders/connectors + * @current_format: the current video bus format + * @supported_formats: a array of supported formats + * @num_supported_formats: the number of formats contained in the + * supported_formats array + * @devices: list of DPI devices attached to this host + * @lock: lock to access DPI devices + * @ops: DPI host operations + */ +struct mipi_dpi_host { + struct device *dev; + struct drm_device *ddev; + struct device_node *of_node; + u32 possible_crtcs; + enum video_bus_format current_format; + const enum video_bus_format *supported_formats; + int num_supported_formats; + struct list_head devices; + struct mutex lock; + const struct mipi_dpi_host_ops *ops; +}; + +void mipi_dpi_host_init(struct mipi_dpi_host *host); +int mipi_dpi_host_register(struct mipi_dpi_host *host); +void mipi_dpi_host_unregister(struct mipi_dpi_host *host); +int mipi_dpi_host_apply_format(struct mipi_dpi_host *host); + +/** + * struct mipi_dpi_device_ops - DPI device operations + * @set_format: configure the video bus format to be used on this device + */ +struct mipi_dpi_device_ops { + int (*set_format)(struct mipi_dpi_device *dev, + enum video_bus_format format); +}; + +/** + * struct mipi_dpi_device - DPI peripheral device + * @node: DPI device list entry + * @host: DPI host for this peripheral + * @dev: driver model device node for this peripheral + * @id: the DPI device id on the bus + * @supported_formats: a array of supported formats + * @num_supported_formats: the number of formats contained in the + * supported_formats array + * @current_format: the current video bus format + * @next_format: the next video bus format to be set when + * mipi_dpi_host_apply_format is called + * @enabled: set to true is the DPI device is enabled (slave device is + * active) + * @ops: DPI device operations + */ +struct mipi_dpi_device { + struct list_head node; + struct mipi_dpi_host *host; + struct device dev; + unsigned int id; + const enum video_bus_format *supported_formats; + int num_supported_formats; + enum video_bus_format current_format; + enum video_bus_format next_format; + bool enabled; + const struct mipi_dpi_device_ops *ops; +}; + +static inline struct mipi_dpi_device *to_mipi_dpi_device(struct device *dev) +{ + return container_of(dev, struct mipi_dpi_device, dev); +} + +int mipi_dpi_attach(struct mipi_dpi_device *dpi); +int mipi_dpi_detach(struct mipi_dpi_device *dpi); + +static inline void mipi_dpi_enable(struct mipi_dpi_device *dpi) +{ + dpi->enabled = true; +} + +static inline void mipi_dpi_disable(struct mipi_dpi_device *dpi) +{ + dpi->enabled = false; +} + +/** + * struct mipi_dpi_driver - DPI driver + * @driver: device driver model driver + * @probe: callback for device binding + * @remove: callback for device unbinding + * @shutdown: called at shutdown time to quiesce the device + */ +struct mipi_dpi_driver { + struct device_driver driver; + int(*probe)(struct mipi_dpi_device *dpi); + int(*remove)(struct mipi_dpi_device *dpi); + void (*shutdown)(struct mipi_dpi_device *dpi); +}; + +static inline struct mipi_dpi_driver * +to_mipi_dpi_driver(struct device_driver *driver) +{ + return container_of(driver, struct mipi_dpi_driver, driver); +} + +static inline void *mipi_dpi_get_drvdata(const struct mipi_dpi_device *dpi) +{ + return dev_get_drvdata(&dpi->dev); +} + +static inline void mipi_dpi_set_drvdata(struct mipi_dpi_device *dpi, void *data) +{ + dev_set_drvdata(&dpi->dev, data); +} + +int mipi_dpi_driver_register(struct mipi_dpi_driver *driver); +void mipi_dpi_driver_unregister(struct mipi_dpi_driver *driver); + +#define module_mipi_dpi_driver(__mipi_dpi_driver) \ + module_driver(__mipi_dpi_driver, mipi_dpi_driver_register, \ + mipi_dpi_driver_unregister) + +#endif /* __DRM_MIPI_DPI__ */
Add DPI connector and encoder types so that we can properly define devices directly connected on this kind of bus (like simple panels).
Signed-off-by: Boris Brezillon boris.brezillon@free-electrons.com --- drivers/gpu/drm/drm_crtc.c | 2 ++ include/uapi/drm/drm_mode.h | 2 ++ 2 files changed, 4 insertions(+)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index c31420f..99b89ee 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -266,6 +266,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = { DRM_MODE_CONNECTOR_eDP, "eDP" }, { DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" }, { DRM_MODE_CONNECTOR_DSI, "DSI" }, + { DRM_MODE_CONNECTOR_DPI, "DPI" }, };
static const struct drm_prop_enum_list drm_encoder_enum_list[] = @@ -277,6 +278,7 @@ static const struct drm_prop_enum_list drm_encoder_enum_list[] = { DRM_MODE_ENCODER_VIRTUAL, "Virtual" }, { DRM_MODE_ENCODER_DSI, "DSI" }, { DRM_MODE_ENCODER_DPMST, "DP MST" }, + { DRM_MODE_ENCODER_DPI, "DPI" }, };
static const struct drm_prop_enum_list drm_subpixel_enum_list[] = diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index a0db2d4a..9a8669e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -187,6 +187,7 @@ struct drm_mode_get_plane_res { #define DRM_MODE_ENCODER_VIRTUAL 5 #define DRM_MODE_ENCODER_DSI 6 #define DRM_MODE_ENCODER_DPMST 7 +#define DRM_MODE_ENCODER_DPI 8
struct drm_mode_get_encoder { __u32 encoder_id; @@ -226,6 +227,7 @@ struct drm_mode_get_encoder { #define DRM_MODE_CONNECTOR_eDP 14 #define DRM_MODE_CONNECTOR_VIRTUAL 15 #define DRM_MODE_CONNECTOR_DSI 16 +#define DRM_MODE_CONNECTOR_DPI 17
struct drm_mode_get_connector {
Add support for panels connected on a DPI bus. This implementation includes DRM encoder/connector creation and attachment to the DRM device so that the only thing needed to access such a panel is to implement a DPI host in your driver and define the appropriate node in your DT (under the DPI node).
Signed-off-by: Boris Brezillon boris.brezillon@free-electrons.com --- drivers/gpu/drm/panel/panel-simple.c | 277 +++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+)
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 11bff3f..fd448ca 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -30,7 +30,9 @@
#include <drm/drmP.h> #include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> #include <drm/drm_mipi_dsi.h> +#include <drm/drm_mipi_dpi.h> #include <drm/drm_panel.h>
struct panel_desc { @@ -851,6 +853,275 @@ static struct mipi_dsi_driver panel_simple_dsi_driver = { .shutdown = panel_simple_dsi_shutdown, };
+struct panel_desc_dpi { + struct panel_desc desc; + + enum video_bus_format format; +}; + +struct panel_dpi { + struct drm_encoder encoder; + struct drm_connector connector; + int dpms_mode; + + struct mipi_dpi_device *dev; +}; + +static const struct of_device_id dpi_of_match[] = { + { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, dsi_of_match); + +static inline struct panel_dpi * +drm_encoder_to_panel_dpi(struct drm_encoder *encoder) +{ + return container_of(encoder, struct panel_dpi, encoder); +} + +static void panel_dpi_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct panel_dpi *dpanel = drm_encoder_to_panel_dpi(encoder); + struct panel_simple *panel = mipi_dpi_get_drvdata(dpanel->dev); + + if (mode != DRM_MODE_DPMS_ON) + mode = DRM_MODE_DPMS_OFF; + + if (mode == dpanel->dpms_mode) + return; + + switch (mode) { + case DRM_MODE_DPMS_ON: + drm_panel_prepare(&panel->base); + drm_panel_enable(&panel->base); + mipi_dpi_enable(dpanel->dev); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + mipi_dpi_disable(dpanel->dev); + drm_panel_disable(&panel->base); + drm_panel_unprepare(&panel->base); + break; + } + + dpanel->dpms_mode = mode; +} + +static bool panel_dpi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void panel_dpi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ +} + +static void panel_dpi_encoder_prepare(struct drm_encoder *encoder) +{ + panel_dpi_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void panel_dpi_encoder_commit(struct drm_encoder *encoder) +{ + panel_dpi_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static const struct drm_encoder_helper_funcs panel_dpi_encoder_helper_funcs = { + .dpms = panel_dpi_encoder_dpms, + .mode_fixup = panel_dpi_encoder_mode_fixup, + .mode_set = panel_dpi_encoder_mode_set, + .prepare = panel_dpi_encoder_prepare, + .commit = panel_dpi_encoder_commit, +}; + +static const struct drm_encoder_funcs panel_dpi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static inline struct panel_dpi * +drm_connector_to_panel_dpi(struct drm_connector *connector) +{ + return container_of(connector, struct panel_dpi, connector); +} + +static int panel_dpi_connector_get_modes(struct drm_connector *connector) +{ + struct panel_dpi *dpanel = drm_connector_to_panel_dpi(connector); + struct panel_simple *panel = mipi_dpi_get_drvdata(dpanel->dev); + + return drm_panel_get_modes(&panel->base); +} + +static struct drm_encoder * +panel_dpi_connector_best_encoder(struct drm_connector *connector) +{ + struct panel_dpi *dpanel = drm_connector_to_panel_dpi(connector); + + return &dpanel->encoder; +} + +static int panel_dpi_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs panel_dpi_connector_helper_funcs = { + .get_modes = panel_dpi_connector_get_modes, + .mode_valid = panel_dpi_connector_mode_valid, + .best_encoder = panel_dpi_connector_best_encoder, +}; + +static enum drm_connector_status +panel_dpi_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void +panel_dpi_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs panel_dpi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = panel_dpi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = panel_dpi_connector_destroy, +}; + +static int panel_simple_dpi_probe(struct mipi_dpi_device *dpi) +{ + const struct panel_desc_dpi *desc; + const struct of_device_id *id; + struct panel_simple *panel; + struct panel_dpi *dpanel; + int err; + + id = of_match_node(dpi_of_match, dpi->dev.of_node); + if (!id) + return -ENODEV; + + desc = id->data; + + dpanel = devm_kzalloc(&dpi->dev, sizeof(*dpanel), GFP_KERNEL); + if (!dpanel) + return -ENOMEM; + + dpanel->dev = dpi; + + err = panel_simple_probe(&dpi->dev, &desc->desc); + if (err < 0) + return err; + + dpanel->dpms_mode = DRM_MODE_DPMS_OFF; + dpi->supported_formats = &desc->format; + dpi->num_supported_formats = 1; + + err = mipi_dpi_attach(dpi); + if (err) + goto err_panel_remove; + + dpanel->encoder.possible_crtcs = dpi->host->possible_crtcs; + drm_encoder_helper_add(&dpanel->encoder, + &panel_dpi_encoder_helper_funcs); + err = drm_encoder_init(dpi->host->ddev, &dpanel->encoder, + &panel_dpi_encoder_funcs, DRM_MODE_ENCODER_DPI); + if (err) + goto err_dpi_detach; + + dpanel->connector.status = connector_status_connected; + drm_connector_helper_add(&dpanel->connector, + &panel_dpi_connector_helper_funcs); + err = drm_connector_init(dpi->host->ddev, &dpanel->connector, + &panel_dpi_connector_funcs, + DRM_MODE_CONNECTOR_DPI); + if (err) + goto err_encoder_cleanup; + + panel = mipi_dpi_get_drvdata(dpi); + err = drm_panel_attach(&panel->base, &dpanel->connector); + if (err) + goto err_connector_cleanup; + + err = drm_mode_connector_attach_encoder(&dpanel->connector, + &dpanel->encoder); + if (err) + goto err_panel_detach; + + err = drm_connector_register(&dpanel->connector); + if (err) + goto err_panel_detach; + + drm_reinit_primary_mode_group(dpi->host->ddev); + drm_kms_helper_hotplug_event(dpi->host->ddev); + + return 0; + +err_panel_detach: + drm_panel_detach(&panel->base); +err_connector_cleanup: + drm_connector_cleanup(&dpanel->connector); +err_encoder_cleanup: + drm_encoder_cleanup(&dpanel->encoder); +err_dpi_detach: + mipi_dpi_detach(dpi); +err_panel_remove: + panel_simple_remove(&dpi->dev); + + return err; +} + +static int panel_simple_dpi_remove(struct mipi_dpi_device *dpi) +{ + struct panel_simple *panel; + struct panel_dpi *dpanel; + + int err; + + panel = mipi_dpi_get_drvdata(dpi); + dpanel = drm_connector_to_panel_dpi(panel->base.connector); + + drm_connector_unregister(&dpanel->connector); + drm_connector_cleanup(&dpanel->connector); + + drm_panel_detach(&panel->base); + + drm_encoder_cleanup(&dpanel->encoder); + + drm_reinit_primary_mode_group(dpi->host->ddev); + + err = mipi_dpi_detach(dpi); + if (err < 0) + dev_err(&dpi->dev, "failed to detach from DPI host: %d\n", err); + + return panel_simple_remove(&dpi->dev); +} + +static void panel_simple_dpi_shutdown(struct mipi_dpi_device *dpi) +{ + panel_simple_shutdown(&dpi->dev); +} + +static struct mipi_dpi_driver panel_simple_dpi_driver = { + .driver = { + .name = "panel-simple-dpi", + .owner = THIS_MODULE, + .of_match_table = dpi_of_match, + }, + .probe = panel_simple_dpi_probe, + .remove = panel_simple_dpi_remove, + .shutdown = panel_simple_dpi_shutdown, +}; + static int __init panel_simple_init(void) { int err; @@ -865,6 +1136,12 @@ static int __init panel_simple_init(void) return err; }
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DPI)) { + err = mipi_dpi_driver_register(&panel_simple_dpi_driver); + if (err < 0) + return err; + } + return 0; } module_init(panel_simple_init);
Signed-off-by: Boris Brezillon boris.brezillon@free-electrons.com --- drivers/gpu/drm/panel/panel-simple.c | 54 +++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-)
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index fd448ca..51715e8 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -503,29 +503,6 @@ static const struct panel_desc edt_etm0700g0dh6 = { }, };
-static const struct drm_display_mode foxlink_fl500wvr00_a0t_mode = { - .clock = 32260, - .hdisplay = 800, - .hsync_start = 800 + 168, - .hsync_end = 800 + 168 + 64, - .htotal = 800 + 168 + 64 + 88, - .vdisplay = 480, - .vsync_start = 480 + 37, - .vsync_end = 480 + 37 + 2, - .vtotal = 480 + 37 + 2 + 8, - .vrefresh = 60, -}; - -static const struct panel_desc foxlink_fl500wvr00_a0t = { - .modes = &foxlink_fl500wvr00_a0t_mode, - .num_modes = 1, - .size = { - .width = 108, - .height = 65, - }, - .bus_format = VIDEO_BUS_FMT_RGB888_1X24, -}; - static const struct drm_display_mode innolux_n116bge_mode = { .clock = 71000, .hdisplay = 1366, @@ -645,9 +622,6 @@ static const struct of_device_id platform_of_match[] = { .compatible = "edt,etm0700g0dh6", .data = &edt_etm0700g0dh6, }, { - .compatible = "foxlink,fl500wvr00-a0t", - .data = &foxlink_fl500wvr00_a0t, - }, { .compatible = "innolux,n116bge", .data = &innolux_n116bge, }, { @@ -867,8 +841,36 @@ struct panel_dpi { struct mipi_dpi_device *dev; };
+static const struct drm_display_mode foxlink_fl500wvr00_a0t_mode = { + .clock = 32260, + .hdisplay = 800, + .hsync_start = 800 + 168, + .hsync_end = 800 + 168 + 64, + .htotal = 800 + 168 + 64 + 88, + .vdisplay = 480, + .vsync_start = 480 + 37, + .vsync_end = 480 + 37 + 2, + .vtotal = 480 + 37 + 2 + 8, + .vrefresh = 60, +}; + +static const struct panel_desc_dpi foxlink_fl500wvr00_a0t = { + .desc = { + .modes = &foxlink_fl500wvr00_a0t_mode, + .num_modes = 1, + .size = { + .width = 108, + .height = 65, + }, + }, + .format = VIDEO_BUS_FMT_RGB888_1X24, +}; + static const struct of_device_id dpi_of_match[] = { { + .compatible = "foxlink,fl500wvr00-a0t", + .data = &foxlink_fl500wvr00_a0t, + }, { /* sentinel */ } };
Implement a DPI host in the HLCDC driver.
Signed-off-by: Boris Brezillon boris.brezillon@free-electrons.com --- drivers/gpu/drm/atmel-hlcdc/Kconfig | 1 + drivers/gpu/drm/atmel-hlcdc/Makefile | 1 + drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dpi.c | 212 ++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dpi.c
diff --git a/drivers/gpu/drm/atmel-hlcdc/Kconfig b/drivers/gpu/drm/atmel-hlcdc/Kconfig index 942407f..af660e2 100644 --- a/drivers/gpu/drm/atmel-hlcdc/Kconfig +++ b/drivers/gpu/drm/atmel-hlcdc/Kconfig @@ -5,6 +5,7 @@ config DRM_ATMEL_HLCDC select DRM_KMS_HELPER select DRM_KMS_FB_HELPER select DRM_KMS_CMA_HELPER + select DRM_MIPI_DPI select DRM_PANEL select MFD_ATMEL_HLCDC depends on OF diff --git a/drivers/gpu/drm/atmel-hlcdc/Makefile b/drivers/gpu/drm/atmel-hlcdc/Makefile index 10ae426..979e431 100644 --- a/drivers/gpu/drm/atmel-hlcdc/Makefile +++ b/drivers/gpu/drm/atmel-hlcdc/Makefile @@ -1,5 +1,6 @@ atmel-hlcdc-dc-y := atmel_hlcdc_crtc.o \ atmel_hlcdc_dc.o \ + atmel_hlcdc_dpi.o \ atmel_hlcdc_layer.o \ atmel_hlcdc_output.o \ atmel_hlcdc_plane.o diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dpi.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dpi.c new file mode 100644 index 0000000..b563dfc --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dpi.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2014 Free Electrons + * Copyright (C) 2014 Atmel + * + * Author: Boris BREZILLON boris.brezillon@free-electrons.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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/of_graph.h> + +#include <drm/drmP.h> +#include <drm/drm_mipi_dpi.h> + +#include "atmel_hlcdc_dc.h" + +enum atmel_hlcdc_output_mode { + ATMEL_HLCDC_OUTPUT_FMT_RGB444, + ATMEL_HLCDC_OUTPUT_FMT_RGB565, + ATMEL_HLCDC_OUTPUT_FMT_RGB666, + ATMEL_HLCDC_OUTPUT_FMT_RGB888, +}; + +struct atmel_hlcdc_dpi_host { + struct mipi_dpi_host base; + struct atmel_hlcdc_dc *dc; +}; + +static inline struct atmel_hlcdc_dpi_host * +to_atmel_hlcdc_dpi_host(struct mipi_dpi_host *host) +{ + return container_of(host, struct atmel_hlcdc_dpi_host, base); +} + +static int atmel_hlcdc_dpi_attach(struct mipi_dpi_host *host, + struct mipi_dpi_device *dpi) +{ + return 0; +} + +static int atmel_hlcdc_dpi_detach(struct mipi_dpi_host *host, + struct mipi_dpi_device *dpi) +{ + return 0; +} + +static int atmel_hlcdc_dpi_best_format_exclusive(struct mipi_dpi_host *host, + enum video_bus_format *format) +{ + struct mipi_dpi_device *dpi; + bool agreed = false; + int i; + + for (i = 0; i < host->num_supported_formats; i++) { + enum video_bus_format hfmt = host->supported_formats[i]; + agreed = true; + + list_for_each_entry(dpi, &host->devices, node) { + int j; + + if (!dpi->enabled) + continue; + + for (j = 0; j < dpi->num_supported_formats; j++) { + if (hfmt == dpi->supported_formats[j]) + break; + } + + if (j == dpi->num_supported_formats) { + agreed = false; + break; + } + } + + if (agreed) { + *format = hfmt; + break; + } + } + + if (!agreed) + return -EINVAL; + + list_for_each_entry(dpi, &host->devices, node) { + if (!dpi->enabled) + continue; + + dpi->next_format = *format; + } + + return 0; +} + +static int +atmel_hlcdc_dpi_best_format_non_exclusive(struct mipi_dpi_host *host, + enum video_bus_format *format) +{ + struct mipi_dpi_device *dpi; + int best_format_index = 0; + + list_for_each_entry(dpi, &host->devices, node) { + int i, j; + + if (!dpi->enabled) + continue; + + for (i = 0; i < host->num_supported_formats; i++) { + enum video_bus_format hfmt = host->supported_formats[i]; + for (j = 0; j < dpi->num_supported_formats; j++) { + if (hfmt == dpi->supported_formats[j]) + break; + } + + if (j < dpi->num_supported_formats) { + dpi->next_format = hfmt; + break; + } + } + + if (i > best_format_index) + best_format_index = i; + } + + *format = host->supported_formats[best_format_index]; + + return 0; +} + +static int atmel_hlcdc_dpi_set_format(struct mipi_dpi_host *h, + enum video_bus_format fmt) +{ + struct atmel_hlcdc_dpi_host *host = to_atmel_hlcdc_dpi_host(h); + unsigned int cfg; + + switch (fmt) { + case VIDEO_BUS_FMT_RGB888_1X24: + cfg = ATMEL_HLCDC_OUTPUT_FMT_RGB888; + break; + case VIDEO_BUS_FMT_RGB666_1X18: + cfg = ATMEL_HLCDC_OUTPUT_FMT_RGB666; + break; + case VIDEO_BUS_FMT_RGB565_1X16: + cfg = ATMEL_HLCDC_OUTPUT_FMT_RGB565; + break; + case VIDEO_BUS_FMT_RGB444_1X12: + cfg = ATMEL_HLCDC_OUTPUT_FMT_RGB444; + break; + default: + return -EINVAL; + } + + regmap_update_bits(host->dc->hlcdc->regmap, ATMEL_HLCDC_CFG(5), + ATMEL_HLCDC_MODE_MASK, + cfg << 8); + + return 0; +} + +static const struct mipi_dpi_host_ops atmel_hlcdc_dpi_host_ops = { + .attach = atmel_hlcdc_dpi_attach, + .detach = atmel_hlcdc_dpi_detach, + .best_format = atmel_hlcdc_dpi_best_format_exclusive, + .set_format = atmel_hlcdc_dpi_set_format, +}; + +static const enum video_bus_format atmel_hlcdc_dpi_supported_formats[] = { + VIDEO_BUS_FMT_RGB888_1X24, + VIDEO_BUS_FMT_RGB666_1X18, + VIDEO_BUS_FMT_RGB565_1X16, + VIDEO_BUS_FMT_RGB444_1X12, +}; + +int atmel_hlcdc_dpi_create(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_dpi_host *dpi; + int ret; + + dpi = devm_kzalloc(dev->dev, sizeof(*dpi), GFP_KERNEL); + if (!dpi) + return -ENOMEM; + + mipi_dpi_host_init(&dpi->base); + + dpi->dc = dc; + dpi->base.ddev = dev; + dpi->base.dev = dev->dev; + dpi->base.supported_formats = atmel_hlcdc_dpi_supported_formats; + dpi->base.num_supported_formats = + ARRAY_SIZE(atmel_hlcdc_dpi_supported_formats); + dpi->base.ops = &atmel_hlcdc_dpi_host_ops; + dpi->base.of_node = of_get_child_by_name(dev->dev->of_node, "dpi"); + dpi->base.possible_crtcs = 0x1; + + ret = mipi_dpi_host_register(&dpi->base); + if (ret) + return ret; + + dc->dpi = &dpi->base; + + return 0; +}
Replace atmel output implementation with the DPI host implementation, which will prevent adding support for new kind of slave devices (encoders, panels, ...).
Call mipi_dpi_host_apply_format in CRTC commit to apply the newly selected video bus format to use on the DPI bus.
Signed-off-by: Boris Brezillon boris.brezillon@free-electrons.com --- drivers/gpu/drm/atmel-hlcdc/Makefile | 1 - drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 11 +- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 12 +- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 6 +- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c | 443 ----------------------- 5 files changed, 17 insertions(+), 456 deletions(-) delete mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
diff --git a/drivers/gpu/drm/atmel-hlcdc/Makefile b/drivers/gpu/drm/atmel-hlcdc/Makefile index 979e431..83bdc37 100644 --- a/drivers/gpu/drm/atmel-hlcdc/Makefile +++ b/drivers/gpu/drm/atmel-hlcdc/Makefile @@ -2,7 +2,6 @@ atmel-hlcdc-dc-y := atmel_hlcdc_crtc.o \ atmel_hlcdc_dc.o \ atmel_hlcdc_dpi.o \ atmel_hlcdc_layer.o \ - atmel_hlcdc_output.o \ atmel_hlcdc_plane.o
obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc-dc.o diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c index 9f2a056..dda4e7e 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c @@ -218,14 +218,17 @@ int atmel_hlcdc_crtc_mode_set_base(struct drm_crtc *c, int x, int y, mode->vdisplay << 16); }
-static void atmel_hlcdc_crtc_prepare(struct drm_crtc *crtc) +static void atmel_hlcdc_crtc_prepare(struct drm_crtc *c) { - atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); + atmel_hlcdc_crtc_dpms(c, DRM_MODE_DPMS_OFF); }
-static void atmel_hlcdc_crtc_commit(struct drm_crtc *crtc) +static void atmel_hlcdc_crtc_commit(struct drm_crtc *c) { - atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON); + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + + mipi_dpi_host_apply_format(crtc->dc->dpi); + atmel_hlcdc_crtc_dpms(c, DRM_MODE_DPMS_ON); }
static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *crtc, diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c index 70c8808..1e799b8 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c @@ -233,12 +233,6 @@ static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev)
drm_mode_config_init(dev);
- ret = atmel_hlcdc_create_outputs(dev); - if (ret) { - dev_err(dev->dev, "failed to create panel: %d\n", ret); - return ret; - } - planes = atmel_hlcdc_create_planes(dev); if (IS_ERR(planes)) { dev_err(dev->dev, "failed to create planes\n"); @@ -258,6 +252,12 @@ static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev) dc->layers[planes->overlays[i]->layer.desc->id] = &planes->overlays[i]->layer;
+ ret = atmel_hlcdc_dpi_create(dev); + if (ret) { + dev_err(dev->dev, "failed to create DPI host\n"); + return ret; + } + ret = atmel_hlcdc_crtc_create(dev); if (ret) { dev_err(dev->dev, "failed to create crtc\n"); diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h index 8194152..24a4ee7 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h @@ -30,6 +30,7 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_cma_helper.h> +#include <drm/drm_mipi_dpi.h> #include <drm/drm_panel.h> #include <drm/drmP.h>
@@ -176,6 +177,7 @@ struct atmel_hlcdc_dc { struct atmel_hlcdc *hlcdc; struct drm_fbdev_cma *fbdev; struct drm_crtc *crtc; + struct mipi_dpi_host *dpi; struct atmel_hlcdc_planes *planes; struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS]; struct workqueue_struct *wq; @@ -196,6 +198,8 @@ int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p, int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p, struct atmel_hlcdc_plane_update_req *req);
+int atmel_hlcdc_dpi_create(struct drm_device *dev); + void atmel_hlcdc_crtc_irq(struct drm_crtc *c);
void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, @@ -203,8 +207,6 @@ void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *crtc,
int atmel_hlcdc_crtc_create(struct drm_device *dev);
-int atmel_hlcdc_create_outputs(struct drm_device *dev); - struct atmel_hlcdc_pwm_chip *atmel_hlcdc_pwm_create(struct drm_device *dev, struct clk *slow_clk, struct clk *sys_clk, diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c deleted file mode 100644 index 8d3a5cb..0000000 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright (C) 2014 Traphandler - * Copyright (C) 2014 Free Electrons - * Copyright (C) 2014 Atmel - * - * Author: Jean-Jacques Hiblot jjhiblot@traphandler.com - * Author: Boris BREZILLON boris.brezillon@free-electrons.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. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see http://www.gnu.org/licenses/. - */ - -#include <linux/of_graph.h> - -#include <drm/drmP.h> -#include <drm/drm_panel.h> - -#include "atmel_hlcdc_dc.h" - -/** - * Atmel HLCDC RGB output mode - */ -enum atmel_hlcdc_connector_rgb_mode { - ATMEL_HLCDC_CONNECTOR_RGB444, - ATMEL_HLCDC_CONNECTOR_RGB565, - ATMEL_HLCDC_CONNECTOR_RGB666, - ATMEL_HLCDC_CONNECTOR_RGB888, -}; - -struct atmel_hlcdc_slave; - -/** - * Atmel HLCDC Slave device operations structure - * - * This structure defines an abstraction to be implemented by each slave - * device type (panel, convertors, ...). - * - * @enable: Enable the slave device - * @disable: Disable the slave device - * @get_modes: retrieve modes supported by the slave device - * @destroy: detroy the slave device and all associated data - */ -struct atmel_hlcdc_slave_ops { - int (*enable)(struct atmel_hlcdc_slave *slave); - int (*disable)(struct atmel_hlcdc_slave *slave); - int (*get_modes)(struct atmel_hlcdc_slave *slave); - int (*mode_valid)(struct atmel_hlcdc_slave *slave, - struct drm_display_mode *mode); - void (*destroy)(struct atmel_hlcdc_slave *slave); -}; - -/** - * Atmel HLCDC Slave device structure - * - * This structure is the base slave device structure to be overloaded by - * each slave device implementation. - * - * @ops: slave device operations - */ -struct atmel_hlcdc_slave { - const struct atmel_hlcdc_slave_ops *ops; -}; - -/** - * Atmel HLCDC Panel device structure - * - * This structure is specialization of the slave device structure to - * interface with drm panels. - * - * @slave: base slave device fields - * @panel: drm panel attached to this slave device - */ -struct atmel_hlcdc_panel { - struct atmel_hlcdc_slave slave; - struct drm_panel *panel; -}; - -static inline struct atmel_hlcdc_panel * -atmel_hlcdc_slave_to_panel(struct atmel_hlcdc_slave *slave) -{ - return container_of(slave, struct atmel_hlcdc_panel, slave); -} - -/** - * Atmel HLCDC RGB connector structure - * - * This structure stores informations about an DRM panel connected through - * the RGB connector. - * - * @connector: DRM connector - * @encoder: DRM encoder - * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device - * @slave: slave device connected to this output - * @endpoint: DT endpoint representing this output - * @dpms: current DPMS mode - */ -struct atmel_hlcdc_rgb_output { - struct drm_connector connector; - struct drm_encoder encoder; - struct atmel_hlcdc_dc *dc; - struct atmel_hlcdc_slave *slave; - struct of_endpoint endpoint; - int dpms; -}; - -static inline struct atmel_hlcdc_rgb_output * -drm_connector_to_atmel_hlcdc_rgb_output(struct drm_connector *connector) -{ - return container_of(connector, struct atmel_hlcdc_rgb_output, - connector); -} - -static inline struct atmel_hlcdc_rgb_output * -drm_encoder_to_atmel_hlcdc_rgb_output(struct drm_encoder *encoder) -{ - return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder); -} - -static int atmel_hlcdc_panel_enable(struct atmel_hlcdc_slave *slave) -{ - struct atmel_hlcdc_panel *panel = atmel_hlcdc_slave_to_panel(slave); - - return drm_panel_enable(panel->panel); -} - -static int atmel_hlcdc_panel_disable(struct atmel_hlcdc_slave *slave) -{ - struct atmel_hlcdc_panel *panel = atmel_hlcdc_slave_to_panel(slave); - - return drm_panel_disable(panel->panel); -} - -static int atmel_hlcdc_panel_get_modes(struct atmel_hlcdc_slave *slave) -{ - struct atmel_hlcdc_panel *panel = atmel_hlcdc_slave_to_panel(slave); - - return panel->panel->funcs->get_modes(panel->panel); -} - -static int atmel_hlcdc_panel_mode_valid(struct atmel_hlcdc_slave *slave, - struct drm_display_mode *mode) -{ - return MODE_OK; -} - -static void atmel_hlcdc_panel_destroy(struct atmel_hlcdc_slave *slave) -{ - struct atmel_hlcdc_panel *panel = atmel_hlcdc_slave_to_panel(slave); - - drm_panel_detach(panel->panel); - kfree(panel); -} - -static const struct atmel_hlcdc_slave_ops atmel_hlcdc_panel_ops = { - .enable = atmel_hlcdc_panel_enable, - .disable = atmel_hlcdc_panel_disable, - .get_modes = atmel_hlcdc_panel_get_modes, - .mode_valid = atmel_hlcdc_panel_mode_valid, - .destroy = atmel_hlcdc_panel_destroy, -}; - -static struct atmel_hlcdc_slave * -atmel_hlcdc_panel_detect(struct atmel_hlcdc_rgb_output *rgb) -{ - struct device_node *np; - struct drm_panel *p = NULL; - struct atmel_hlcdc_panel *panel; - - np = of_graph_get_remote_port_parent(rgb->endpoint.local_node); - if (!np) - return NULL; - - p = of_drm_find_panel(np); - of_node_put(np); - - if (p) { - panel = kzalloc(sizeof(*panel), GFP_KERNEL); - if (!panel) - return NULL; - - drm_panel_attach(p, &rgb->connector); - panel->panel = p; - panel->slave.ops = &atmel_hlcdc_panel_ops; - return &panel->slave; - } - - return NULL; -} - -static void atmel_hlcdc_rgb_encoder_dpms(struct drm_encoder *encoder, - int mode) -{ - struct atmel_hlcdc_rgb_output *rgb = - drm_encoder_to_atmel_hlcdc_rgb_output(encoder); - - if (mode != DRM_MODE_DPMS_ON) - mode = DRM_MODE_DPMS_OFF; - - if (mode == rgb->dpms) - return; - - if (mode != DRM_MODE_DPMS_ON) - rgb->slave->ops->disable(rgb->slave); - else - rgb->slave->ops->enable(rgb->slave); - - rgb->dpms = mode; -} - -static bool -atmel_hlcdc_rgb_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - return true; -} - -static void atmel_hlcdc_rgb_encoder_prepare(struct drm_encoder *encoder) -{ - atmel_hlcdc_rgb_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); -} - -static void atmel_hlcdc_rgb_encoder_commit(struct drm_encoder *encoder) -{ - atmel_hlcdc_rgb_encoder_dpms(encoder, DRM_MODE_DPMS_ON); -} - -static void -atmel_hlcdc_rgb_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - struct atmel_hlcdc_rgb_output *rgb = - drm_encoder_to_atmel_hlcdc_rgb_output(encoder); - struct drm_display_info *info = &rgb->connector.display_info; - unsigned int cfg; - - cfg = 0; - - if (info->num_bus_formats) { - switch (info->bus_formats[0]) { - case VIDEO_BUS_FMT_RGB565_1X16: - cfg |= ATMEL_HLCDC_CONNECTOR_RGB565 << 8; - break; - case VIDEO_BUS_FMT_RGB666_1X18: - cfg |= ATMEL_HLCDC_CONNECTOR_RGB666 << 8; - break; - case VIDEO_BUS_FMT_RGB888_1X24: - cfg |= ATMEL_HLCDC_CONNECTOR_RGB888 << 8; - break; - case VIDEO_BUS_FMT_RGB444_1X12: - default: - break; - } - } - - regmap_update_bits(rgb->dc->hlcdc->regmap, ATMEL_HLCDC_CFG(5), - ATMEL_HLCDC_MODE_MASK, - cfg); -} - -static struct drm_encoder_helper_funcs atmel_hlcdc_rgb_encoder_helper_funcs = { - .dpms = atmel_hlcdc_rgb_encoder_dpms, - .mode_fixup = atmel_hlcdc_rgb_encoder_mode_fixup, - .prepare = atmel_hlcdc_rgb_encoder_prepare, - .commit = atmel_hlcdc_rgb_encoder_commit, - .mode_set = atmel_hlcdc_rgb_encoder_mode_set, -}; - -static void atmel_hlcdc_rgb_encoder_destroy(struct drm_encoder *encoder) -{ - drm_encoder_cleanup(encoder); - memset(encoder, 0, sizeof(*encoder)); -} - -static const struct drm_encoder_funcs atmel_hlcdc_rgb_encoder_funcs = { - .destroy = atmel_hlcdc_rgb_encoder_destroy, -}; - -static int atmel_hlcdc_rgb_get_modes(struct drm_connector *connector) -{ - struct atmel_hlcdc_rgb_output *rgb = - drm_connector_to_atmel_hlcdc_rgb_output(connector); - - return rgb->slave->ops->get_modes(rgb->slave); -} - -static int atmel_hlcdc_rgb_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct atmel_hlcdc_rgb_output *rgb = - drm_connector_to_atmel_hlcdc_rgb_output(connector); - int ret; - - ret = atmel_hlcdc_dc_mode_valid(rgb->dc, mode); - if (ret != MODE_OK) - return ret; - - return rgb->slave->ops->mode_valid(rgb->slave, mode); -} - -static struct drm_encoder * -atmel_hlcdc_rgb_best_encoder(struct drm_connector *connector) -{ - struct atmel_hlcdc_rgb_output *rgb = - drm_connector_to_atmel_hlcdc_rgb_output(connector); - - return &rgb->encoder; -} - -static struct drm_connector_helper_funcs atmel_hlcdc_rgb_connector_helper_funcs = { - .get_modes = atmel_hlcdc_rgb_get_modes, - .mode_valid = atmel_hlcdc_rgb_mode_valid, - .best_encoder = atmel_hlcdc_rgb_best_encoder, -}; - -static enum drm_connector_status -atmel_hlcdc_rgb_connector_detect(struct drm_connector *connector, bool force) -{ - struct atmel_hlcdc_rgb_output *rgb = - drm_connector_to_atmel_hlcdc_rgb_output(connector); - - if (!rgb->slave) { - /* At the moment we only support panel devices */ - rgb->slave = atmel_hlcdc_panel_detect(rgb); - } - - if (rgb->slave) - return connector_status_connected; - - return connector_status_unknown; -} - -static void -atmel_hlcdc_rgb_connector_destroy(struct drm_connector *connector) -{ - struct atmel_hlcdc_rgb_output *rgb = - drm_connector_to_atmel_hlcdc_rgb_output(connector); - - if (rgb->slave && rgb->slave->ops->destroy) - rgb->slave->ops->destroy(rgb->slave); - - drm_connector_unregister(&rgb->connector); - drm_connector_cleanup(connector); -} - -static const struct drm_connector_funcs atmel_hlcdc_rgb_connector_funcs = { - .dpms = drm_helper_connector_dpms, - .detect = atmel_hlcdc_rgb_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = atmel_hlcdc_rgb_connector_destroy, -}; - -static int atmel_hlcdc_create_output(struct drm_device *dev, - struct of_endpoint *ep) -{ - struct atmel_hlcdc_dc *dc = dev->dev_private; - struct atmel_hlcdc_rgb_output *rgb; - int ret; - - rgb = devm_kzalloc(dev->dev, sizeof(*rgb), GFP_KERNEL); - if (!rgb) - return -ENOMEM; - - rgb->endpoint = *ep; - - rgb->dpms = DRM_MODE_DPMS_OFF; - - rgb->dc = dc; - - drm_encoder_helper_add(&rgb->encoder, - &atmel_hlcdc_rgb_encoder_helper_funcs); - ret = drm_encoder_init(dev, &rgb->encoder, - &atmel_hlcdc_rgb_encoder_funcs, - DRM_MODE_ENCODER_LVDS); - if (ret) - return ret; - - rgb->connector.dpms = DRM_MODE_DPMS_OFF; - rgb->connector.polled = DRM_CONNECTOR_POLL_CONNECT; - drm_connector_helper_add(&rgb->connector, - &atmel_hlcdc_rgb_connector_helper_funcs); - ret = drm_connector_init(dev, &rgb->connector, - &atmel_hlcdc_rgb_connector_funcs, - DRM_MODE_CONNECTOR_LVDS); - if (ret) - goto err_encoder_cleanup; - - drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder); - - ret = drm_connector_register(&rgb->connector); - if (ret) - goto err_connector_cleanup; - - rgb->encoder.possible_crtcs = 0x1; - - return 0; - -err_connector_cleanup: - drm_connector_cleanup(&rgb->connector); -err_encoder_cleanup: - drm_encoder_cleanup(&rgb->encoder); - - return ret; -} - -int atmel_hlcdc_create_outputs(struct drm_device *dev) -{ - struct device_node *port_np, *np; - struct of_endpoint ep; - int ret; - - port_np = of_get_child_by_name(dev->dev->of_node, "port"); - if (!port_np) - return -EINVAL; - - np = of_get_child_by_name(port_np, "endpoint"); - of_node_put(port_np); - - if (!np) - return -EINVAL; - - ret = of_graph_parse_endpoint(np, &ep); - of_node_put(port_np); - - if (ret) - return ret; - - ret = atmel_hlcdc_create_output(dev, &ep); - if (ret) - return ret; - - return 0; -}
Replace old panel node with a new one relying on DPI bus support.
Signed-off-by: Boris Brezillon boris.brezillon@free-electrons.com --- arch/arm/boot/dts/sama5d36ek.dts | 12 ++++++++---- arch/arm/boot/dts/sama5d3_lcd.dtsi | 6 +++--- arch/arm/boot/dts/sama5d3xdm.dtsi | 28 ++++++---------------------- 3 files changed, 17 insertions(+), 29 deletions(-)
diff --git a/arch/arm/boot/dts/sama5d36ek.dts b/arch/arm/boot/dts/sama5d36ek.dts index 1c65741..c83b846 100644 --- a/arch/arm/boot/dts/sama5d36ek.dts +++ b/arch/arm/boot/dts/sama5d36ek.dts @@ -43,6 +43,14 @@
hlcdc: hlcdc@f0030000 { status = "okay"; + + hlcdc-display-controller { + dpi { + panel@0 { + status = "okay"; + }; + }; + }; };
macb1: ethernet@f802c000 { @@ -63,10 +71,6 @@ status = "okay"; };
- panel: panel { - status = "okay"; - }; - sound { status = "okay"; }; diff --git a/arch/arm/boot/dts/sama5d3_lcd.dtsi b/arch/arm/boot/dts/sama5d3_lcd.dtsi index 611ff8a..7c516c3 100644 --- a/arch/arm/boot/dts/sama5d3_lcd.dtsi +++ b/arch/arm/boot/dts/sama5d3_lcd.dtsi @@ -179,10 +179,10 @@ #address-cells = <1>; #size-cells = <0>;
- port@0 { - #address-cells = <1>; + dpi { + compatible = "atmel,hlcdc-dpi-host"; #size-cells = <0>; - reg = <0>; + #address-cells = <1>; }; };
diff --git a/arch/arm/boot/dts/sama5d3xdm.dtsi b/arch/arm/boot/dts/sama5d3xdm.dtsi index 91975eb..39aba49 100644 --- a/arch/arm/boot/dts/sama5d3xdm.dtsi +++ b/arch/arm/boot/dts/sama5d3xdm.dtsi @@ -42,10 +42,13 @@ pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888_alt>;
- port@0 { - hlcdc_panel_output: endpoint@0 { + dpi { + panel@0 { + compatible = "foxlink,fl500wvr00-a0t"; reg = <0>; - remote-endpoint = <&panel_input>; + backlight = <&backlight>; + power-supply = <&panel_reg>; + status = "disabled"; }; }; }; @@ -77,23 +80,4 @@ power-supply = <&bl_reg>; status = "disabled"; }; - - panel: panel { - compatible = "foxlink,fl500wvr00-a0t", "simple-panel"; - backlight = <&backlight>; - power-supply = <&panel_reg>; - #address-cells = <1>; - #size-cells = <0>; - status = "disabled"; - - port@0 { - #address-cells = <1>; - #size-cells = <0>; - - panel_input: endpoint@0 { - reg = <0>; - remote-endpoint = <&hlcdc_panel_output>; - }; - }; - }; };
dri-devel@lists.freedesktop.org