Hello,
This patch series adds support for Atmel HLCDC (HLCD Controller) available on some Atmel SoCs (i.e. the sama5d3 family).
The HLCDC actually provides a Display Controller and a PWM device, hence I decided to declare an MFD device exposing 2 subdevices: a display controller and a PWM chip. This also solves a circular dependency issue preventing HLCDC driver from unloading. The HLCDC request a drm_panel device, which request a backlight device (a PWM backlight), which depends on a PWM which is provided by the HLCDC driver (hlcdc -> panel -> backlight -> hlcdc (pwm part)).
The current implementation only supports sama5d3 SoCs but other SoCs should be easily ported by defining new compatible strings and adding HLCDC description structures for these SoCs.
The drivers supports basic CRTC functionalities, several overlays and an hardware cursor.
At the moment, it only supports connection to LCD panels through an RGB connector (defined as an LVDS connector in my implementation), though connection to other kind of devices (like DRM bridges) could be added later.
It also supports several RGB formats on all planes and some YUV formats on the HEO overlay plane.
This series depends on 2 other series currently under review: [1] and [2].
Best Regards,
Boris
[1]http://lkml.iu.edu/hypermail/linux/kernel/1407.1/04171.html [2]http://www.spinics.net/lists/kernel/msg1791681.html
Changes since v3: - rework the layer code to simplify several parts (locking and layer disabling) - make use of the drm_flip_work infrastructure - rely on default HW cursor implementation using on the cursor plane - rework the display controller DT bindings (based on OF graph representation) - add rotation support - retrive RGB bus format from drm_display_info - drop the dynamic pinctrl state selection - rework HLCDC output handling (previously specialized to interface with LCD panels) - drop ".module = THIS_MODULE" lines - change display controller compatible string
Changes since v2: - fix coding style issues (macro indentation) - make use of GENMASK in several places - declare regmap config as a static structure - rework hlcdc plane update API - rework cursor handling to make use of the new plane update API - fix backporch config - do not use devm_regmap_init_mmio_clk to avoid extra clk_enable clk disable calls when accessing registers - explicitely include regmap and clk headers instead of relying on atmel-hlcdc.h inclusions - make the atmel-hlcdc driver depends on CONFIG_OF - separate DT bindings documentation from driver implementation - support several pin muxing for HLCDC pins on sama5d3 SoCs
Changes since v1: - replace the backlight driver by a PWM driver - make use of drm_panel infrastructure - split driver code in several subsystem: MFD, PWM and DRM - add support for overlays - add support for hardware cursor
Boris BREZILLON (11): mfd: add atmel-hlcdc driver mfd: add documentation for atmel-hlcdc DT bindings pwm: add support for atmel-hlcdc-pwm device pwm: add DT bindings documentation for atmel-hlcdc-pwm driver drm: add Atmel HLCDC Display Controller support drm: add DT bindings documentation for atmel-hlcdc-dc driver ARM: AT91/dt: split sama5d3 lcd pin definitions to match RGB mode configs ARM: AT91/dt: add alternative pin muxing for sama5d3 lcd pins ARM: at91/dt: define the HLCDC node available on sama5d3 SoCs ARM: at91/dt: add LCD panel description to sama5d3xdm.dtsi ARM: at91/dt: enable the LCD panel on sama5d3xek boards
.../devicetree/bindings/drm/atmel-hlcdc-dc.txt | 54 ++ .../devicetree/bindings/mfd/atmel-hlcdc.txt | 50 ++ .../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 55 ++ arch/arm/boot/dts/sama5d31ek.dts | 20 + arch/arm/boot/dts/sama5d33ek.dts | 20 + arch/arm/boot/dts/sama5d34ek.dts | 20 + arch/arm/boot/dts/sama5d36ek.dts | 20 + arch/arm/boot/dts/sama5d3_lcd.dtsi | 205 +++++- arch/arm/boot/dts/sama5d3xdm.dtsi | 58 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/atmel-hlcdc/Kconfig | 11 + drivers/gpu/drm/atmel-hlcdc/Makefile | 7 + drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 286 ++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 488 +++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 224 ++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 635 ++++++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 396 ++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c | 478 ++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 804 +++++++++++++++++++++ drivers/mfd/Kconfig | 12 + drivers/mfd/Makefile | 1 + drivers/mfd/atmel-hlcdc.c | 118 +++ drivers/pwm/Kconfig | 9 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-atmel-hlcdc.c | 229 ++++++ include/linux/mfd/atmel-hlcdc.h | 78 ++ 27 files changed, 4251 insertions(+), 31 deletions(-) create mode 100644 Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt create mode 100644 drivers/gpu/drm/atmel-hlcdc/Kconfig create mode 100644 drivers/gpu/drm/atmel-hlcdc/Makefile create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c create mode 100644 drivers/mfd/atmel-hlcdc.c create mode 100644 drivers/pwm/pwm-atmel-hlcdc.c create mode 100644 include/linux/mfd/atmel-hlcdc.h
The HLCDC IP available on some Atmel SoCs (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) exposes 2 subdevices: - a display controller (controlled by a DRM driver) - a PWM chip
The MFD device provides a regmap and several clocks (those connected to this hardware block) to its subdevices.
This way concurrent accesses to the iomem range are handled by the regmap framework, and each subdevice can safely access HLCDC registers.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com Acked-by: Lee Jones lee.jones@linaro.org --- drivers/mfd/Kconfig | 12 ++++ drivers/mfd/Makefile | 1 + drivers/mfd/atmel-hlcdc.c | 118 ++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/atmel-hlcdc.h | 78 ++++++++++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 drivers/mfd/atmel-hlcdc.c create mode 100644 include/linux/mfd/atmel-hlcdc.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6cc4b6a..c3c007e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -59,6 +59,18 @@ config MFD_AAT2870_CORE additional drivers must be enabled in order to use the functionality of the device.
+config MFD_ATMEL_HLCDC + tristate "Atmel HLCDC (HLCD Controller)" + select MFD_CORE + select REGMAP_MMIO + depends on OF + help + Choose this option if you have an ATMEL SoC with an HLCDC (HLCD + Controller) IP (i.e. at91sam9n12, at91sam9x5 family or sama5d3 + family). + This MFD device exposes two subdevices: a PWM chip and a Display + Controller. + config MFD_BCM590XX tristate "Broadcom BCM590xx PMUs" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 8afedba..5f25b0d 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -156,6 +156,7 @@ obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o obj-$(CONFIG_MFD_TPS65090) += tps65090.o obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o +obj-$(CONFIG_MFD_ATMEL_HLCDC) += atmel-hlcdc.o obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o obj-$(CONFIG_MFD_PALMAS) += palmas.o obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o diff --git a/drivers/mfd/atmel-hlcdc.c b/drivers/mfd/atmel-hlcdc.c new file mode 100644 index 0000000..aadb371 --- /dev/null +++ b/drivers/mfd/atmel-hlcdc.c @@ -0,0 +1,118 @@ +/* + * 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/clk.h> +#include <linux/mfd/atmel-hlcdc.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define ATMEL_HLCDC_REG_MAX (0x4000 - 0x4) + +static const struct mfd_cell atmel_hlcdc_cells[] = { + { + .name = "atmel-hlcdc-pwm", + .of_compatible = "atmel,hlcdc-pwm", + }, + { + .name = "atmel-hlcdc-dc", + .of_compatible = "atmel,hlcdc-display-controller", + }, +}; + +static const struct regmap_config atmel_hlcdc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = ATMEL_HLCDC_REG_MAX, +}; + +static int atmel_hlcdc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct atmel_hlcdc *hlcdc; + struct resource *res; + void __iomem *regs; + + hlcdc = devm_kzalloc(dev, sizeof(*hlcdc), GFP_KERNEL); + if (!hlcdc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + hlcdc->periph_clk = devm_clk_get(dev, "periph_clk"); + if (IS_ERR(hlcdc->periph_clk)) { + dev_err(dev, "failed to get peripheral clock\n"); + return PTR_ERR(hlcdc->periph_clk); + } + + hlcdc->sys_clk = devm_clk_get(dev, "sys_clk"); + if (IS_ERR(hlcdc->sys_clk)) { + dev_err(dev, "failed to get system clock\n"); + return PTR_ERR(hlcdc->sys_clk); + } + + hlcdc->slow_clk = devm_clk_get(dev, "slow_clk"); + if (IS_ERR(hlcdc->slow_clk)) { + dev_err(dev, "failed to get slow clock\n"); + return PTR_ERR(hlcdc->slow_clk); + } + + hlcdc->regmap = devm_regmap_init_mmio(dev, regs, + &atmel_hlcdc_regmap_config); + if (IS_ERR(hlcdc->regmap)) + return PTR_ERR(hlcdc->regmap); + + dev_set_drvdata(dev, hlcdc); + + return mfd_add_devices(dev, -1, atmel_hlcdc_cells, + ARRAY_SIZE(atmel_hlcdc_cells), + NULL, 0, NULL); +} + +static int atmel_hlcdc_remove(struct platform_device *pdev) +{ + mfd_remove_devices(&pdev->dev); + + return 0; +} + +static const struct of_device_id atmel_hlcdc_match[] = { + { .compatible = "atmel,sama5d3-hlcdc" }, + { /* sentinel */ }, +}; + +static struct platform_driver atmel_hlcdc_driver = { + .probe = atmel_hlcdc_probe, + .remove = atmel_hlcdc_remove, + .driver = { + .name = "atmel-hlcdc", + .of_match_table = atmel_hlcdc_match, + }, +}; +module_platform_driver(atmel_hlcdc_driver); + +MODULE_ALIAS("platform:atmel-hlcdc"); +MODULE_AUTHOR("Boris Brezillon boris.brezillon@free-electrons.com"); +MODULE_DESCRIPTION("Atmel HLCDC driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/atmel-hlcdc.h b/include/linux/mfd/atmel-hlcdc.h new file mode 100644 index 0000000..e9a503d --- /dev/null +++ b/include/linux/mfd/atmel-hlcdc.h @@ -0,0 +1,78 @@ +/* + * 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/. + */ + +#ifndef __LINUX_MFD_HLCDC_H +#define __LINUX_MFD_HLCDC_H + +#include <linux/clk.h> +#include <linux/regmap.h> + +#define ATMEL_HLCDC_CFG(i) ((i) * 0x4) +#define ATMEL_HLCDC_SIG_CFG LCDCFG(5) +#define ATMEL_HLCDC_HSPOL BIT(0) +#define ATMEL_HLCDC_VSPOL BIT(1) +#define ATMEL_HLCDC_VSPDLYS BIT(2) +#define ATMEL_HLCDC_VSPDLYE BIT(3) +#define ATMEL_HLCDC_DISPPOL BIT(4) +#define ATMEL_HLCDC_DITHER BIT(6) +#define ATMEL_HLCDC_DISPDLY BIT(7) +#define ATMEL_HLCDC_MODE_MASK GENMASK(9, 8) +#define ATMEL_HLCDC_PP BIT(10) +#define ATMEL_HLCDC_VSPSU BIT(12) +#define ATMEL_HLCDC_VSPHO BIT(13) +#define ATMEL_HLCDC_GUARDTIME_MASK GENMASK(20, 16) + +#define ATMEL_HLCDC_EN 0x20 +#define ATMEL_HLCDC_DIS 0x24 +#define ATMEL_HLCDC_SR 0x28 +#define ATMEL_HLCDC_IER 0x2c +#define ATMEL_HLCDC_IDR 0x30 +#define ATMEL_HLCDC_IMR 0x34 +#define ATMEL_HLCDC_ISR 0x38 + +#define ATMEL_HLCDC_CLKPOL BIT(0) +#define ATMEL_HLCDC_CLKSEL BIT(2) +#define ATMEL_HLCDC_CLKPWMSEL BIT(3) +#define ATMEL_HLCDC_CGDIS(i) BIT(8 + (i)) +#define ATMEL_HLCDC_CLKDIV_SHFT 16 +#define ATMEL_HLCDC_CLKDIV_MASK GENMASK(23, 16) +#define ATMEL_HLCDC_CLKDIV(div) ((div - 2) << ATMEL_HLCDC_CLKDIV_SHFT) + +#define ATMEL_HLCDC_PIXEL_CLK BIT(0) +#define ATMEL_HLCDC_SYNC BIT(1) +#define ATMEL_HLCDC_DISP BIT(2) +#define ATMEL_HLCDC_PWM BIT(3) +#define ATMEL_HLCDC_SIP BIT(4) + +/** + * Structure shared by the MFD device and its subdevices. + * + * @regmap: register map used to access HLCDC IP registers + * @periph_clk: the hlcdc peripheral clock + * @sys_clk: the hlcdc system clock + * @slow_clk: the system slow clk + */ +struct atmel_hlcdc { + struct regmap *regmap; + struct clk *periph_clk; + struct clk *sys_clk; + struct clk *slow_clk; +}; + +#endif /* __LINUX_MFD_HLCDC_H */
The HLCDC IP available on some Atmel SoCs (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) exposes 2 subdevices: - a display controller (controlled by a DRM driver) - a PWM chip
This patch adds documentation for atmel-hlcdc DT bindings.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- .../devicetree/bindings/mfd/atmel-hlcdc.txt | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
diff --git a/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt new file mode 100644 index 0000000..e9cc1b2 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt @@ -0,0 +1,50 @@ +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) MFD driver + +Required properties: + - compatible: value should be one of the following: + "atmel,sama5d3-hlcdc" + - reg: base address and size of the HLCDC device registers. + - clock-names: the name of the 3 clocks requested by the HLCDC device. + Should contain "periph_clk", "sys_clk" and "slow_clk". + - clocks: should contain the 3 clocks requested by the HLCDC device. + +The HLCDC IP exposes two subdevices: + - a PWM chip: see ../pwm/atmel-hlcdc-pwm.txt + - a Display Controller: see ../drm/atmel-hlcdc-dc.txt + +Example: + + hlcdc: hlcdc@f0030000 { + compatible = "atmel,sama5d3-hlcdc"; + reg = <0xf0030000 0x2000>; + clocks = <&lcdc_clk>, <&lcdck>, <&clk32k>; + clock-names = "periph_clk","sys_clk", "slow_clk"; + status = "disabled"; + + hlcdc-display-controller { + compatible = "atmel,hlcdc-display-controller"; + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>; + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + hlcdc_panel_output: endpoint@0 { + reg = <0>; + remote-endpoint = <&panel_input>; + }; + }; + }; + + hlcdc_pwm: hlcdc-pwm { + compatible = "atmel,hlcdc-pwm"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd_pwm>; + #pwm-cells = <3>; + }; + };
On Tuesday 22 July 2014 06:41 PM, Boris BREZILLON wrote:
The HLCDC IP available on some Atmel SoCs (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) exposes 2 subdevices:
- a display controller (controlled by a DRM driver)
- a PWM chip
This patch adds documentation for atmel-hlcdc DT bindings.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com
.../devicetree/bindings/mfd/atmel-hlcdc.txt | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
diff --git a/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt new file mode 100644 index 0000000..e9cc1b2 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt @@ -0,0 +1,50 @@ +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) MFD driver
+Required properties:
- compatible: value should be one of the following:
- "atmel,sama5d3-hlcdc"
- reg: base address and size of the HLCDC device registers.
- clock-names: the name of the 3 clocks requested by the HLCDC device.
- Should contain "periph_clk", "sys_clk" and "slow_clk".
- clocks: should contain the 3 clocks requested by the HLCDC device.
These bindings not clearly readable. It would be readable if
Required properties: - compatible : value should be one of the following:"atmel,sama5d3-hlcdc" - reg : base address and size of the HLCDC device registers. - clock-names : the name of the 3 clocks requested by the HLCDC device. Should contain "periph_clk", "sys_clk" and "slow_clk". - clocks : should contain the 3 clocks requested by the HLCDC device.
......
The HLCDC IP available in some Atmel SoCs (i.e. sam9x5i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) provide a PWM device.
This driver add support for a PWM chip exposing a single PWM device (which will most likely be used to drive a backlight device).
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- drivers/pwm/Kconfig | 9 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-atmel-hlcdc.c | 229 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 drivers/pwm/pwm-atmel-hlcdc.c
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 4ad7b89..3c8b7d0 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -50,6 +50,15 @@ config PWM_ATMEL To compile this driver as a module, choose M here: the module will be called pwm-atmel.
+config PWM_ATMEL_HLCDC_PWM + tristate "Atmel HLCDC PWM support" + depends on MFD_ATMEL_HLCDC + help + Generic PWM framework driver for Atmel HLCDC PWM. + + To compile this driver as a module, choose M here: the module + will be called pwm-atmel. + config PWM_ATMEL_TCB tristate "Atmel TC Block PWM support" depends on ATMEL_TCLIB && OF diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 5c86a19..26ae965 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_SYSFS) += sysfs.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o +obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c new file mode 100644 index 0000000..7f25197 --- /dev/null +++ b/drivers/pwm/pwm-atmel-hlcdc.c @@ -0,0 +1,229 @@ +/* + * 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/clk.h> +#include <linux/mfd/atmel-hlcdc.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> + +#define ATMEL_HLCDC_PWMCVAL_MASK GENMASK(15, 8) +#define ATMEL_HLCDC_PWMCVAL(x) ((x << 8) & ATMEL_HLCDC_PWMCVAL_MASK) +#define ATMEL_HLCDC_PWMPOL BIT(4) +#define ATMEL_HLCDC_PWMPS_MASK GENMASK(2, 0) +#define ATMEL_HLCDC_PWMPS_MAX 0x6 +#define ATMEL_HLCDC_PWMPS(x) ((x) & ATMEL_HLCDC_PWMPS_MASK) + +struct atmel_hlcdc_pwm_chip { + struct pwm_chip chip; + struct atmel_hlcdc *hlcdc; + struct clk *cur_clk; +}; + +static inline struct atmel_hlcdc_pwm_chip * +pwm_chip_to_atmel_hlcdc_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct atmel_hlcdc_pwm_chip, chip); +} + +static int atmel_hlcdc_pwm_config(struct pwm_chip *c, + struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct atmel_hlcdc_pwm_chip *chip = + pwm_chip_to_atmel_hlcdc_pwm_chip(c); + struct atmel_hlcdc *hlcdc = chip->hlcdc; + struct clk *new_clk = hlcdc->slow_clk; + u64 pwmcval = duty_ns * 256; + unsigned long clk_freq; + u64 clk_period_ns; + u32 pwmcfg; + int pres; + + clk_freq = clk_get_rate(new_clk); + clk_period_ns = 1000000000; + clk_period_ns *= 256; + do_div(clk_period_ns, clk_freq); + + if (clk_period_ns > period_ns) { + new_clk = hlcdc->sys_clk; + clk_freq = clk_get_rate(new_clk); + clk_period_ns = 1000000000; + clk_period_ns *= 256; + do_div(clk_period_ns, clk_freq); + } + + for (pres = ATMEL_HLCDC_PWMPS_MAX; pres >= 0; pres--) { + if ((clk_period_ns << pres) <= period_ns) + break; + } + + if (pres > ATMEL_HLCDC_PWMPS_MAX) + return -EINVAL; + + pwmcfg = ATMEL_HLCDC_PWMPS(pres); + + if (new_clk != chip->cur_clk) { + u32 gencfg = 0; + + clk_prepare_enable(new_clk); + clk_disable_unprepare(chip->cur_clk); + chip->cur_clk = new_clk; + + if (new_clk != hlcdc->slow_clk) + gencfg = ATMEL_HLCDC_CLKPWMSEL; + regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(0), + ATMEL_HLCDC_CLKPWMSEL, gencfg); + } + + do_div(pwmcval, period_ns); + if (pwmcval > 255) + pwmcval = 255; + + pwmcfg |= ATMEL_HLCDC_PWMCVAL(pwmcval); + + regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6), + ATMEL_HLCDC_PWMCVAL_MASK | ATMEL_HLCDC_PWMPS_MASK, + pwmcfg); + + return 0; +} + +static int atmel_hlcdc_pwm_set_polarity(struct pwm_chip *c, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct atmel_hlcdc_pwm_chip *chip = + pwm_chip_to_atmel_hlcdc_pwm_chip(c); + struct atmel_hlcdc *hlcdc = chip->hlcdc; + u32 cfg = 0; + + if (polarity == PWM_POLARITY_NORMAL) + cfg = ATMEL_HLCDC_PWMPOL; + + regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6), + ATMEL_HLCDC_PWMPOL, cfg); + + return 0; +} + +static int atmel_hlcdc_pwm_enable(struct pwm_chip *c, + struct pwm_device *pwm) +{ + struct atmel_hlcdc_pwm_chip *chip = + pwm_chip_to_atmel_hlcdc_pwm_chip(c); + struct atmel_hlcdc *hlcdc = chip->hlcdc; + u32 status; + + regmap_write(hlcdc->regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_PWM); + while (!regmap_read(hlcdc->regmap, ATMEL_HLCDC_SR, &status) && + !(status & ATMEL_HLCDC_PWM)) + ; + + return 0; +} + +static void atmel_hlcdc_pwm_disable(struct pwm_chip *c, + struct pwm_device *pwm) +{ + struct atmel_hlcdc_pwm_chip *chip = + pwm_chip_to_atmel_hlcdc_pwm_chip(c); + struct atmel_hlcdc *hlcdc = chip->hlcdc; + u32 status; + + regmap_write(hlcdc->regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_PWM); + while (!regmap_read(hlcdc->regmap, ATMEL_HLCDC_SR, &status) && + (status & ATMEL_HLCDC_PWM)) + ; +} + +static const struct pwm_ops atmel_hlcdc_pwm_ops = { + .config = atmel_hlcdc_pwm_config, + .set_polarity = atmel_hlcdc_pwm_set_polarity, + .enable = atmel_hlcdc_pwm_enable, + .disable = atmel_hlcdc_pwm_disable, + .owner = THIS_MODULE, +}; + +static int atmel_hlcdc_pwm_probe(struct platform_device *pdev) +{ + struct atmel_hlcdc_pwm_chip *chip; + struct device *dev = &pdev->dev; + struct atmel_hlcdc *hlcdc; + int ret; + + hlcdc = dev_get_drvdata(dev->parent); + if (!hlcdc) + return -EINVAL; + + ret = clk_prepare_enable(hlcdc->periph_clk); + if (ret) + return ret; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->hlcdc = hlcdc; + chip->chip.ops = &atmel_hlcdc_pwm_ops; + chip->chip.dev = dev; + chip->chip.base = -1; + chip->chip.npwm = 1; + chip->chip.of_xlate = of_pwm_xlate_with_flags; + chip->chip.of_pwm_n_cells = 3; + chip->chip.can_sleep = 1; + + ret = pwmchip_add(&chip->chip); + if (ret) + return ret; + + platform_set_drvdata(pdev, chip); + + return 0; +} + +static int atmel_hlcdc_pwm_remove(struct platform_device *pdev) +{ + struct atmel_hlcdc_pwm_chip *chip = platform_get_drvdata(pdev); + + clk_disable_unprepare(chip->hlcdc->periph_clk); + + return pwmchip_remove(&chip->chip); +} + +static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = { + { .compatible = "atmel,hlcdc-pwm" }, + { /* sentinel */ }, +}; + +static struct platform_driver atmel_hlcdc_pwm_driver = { + .driver = { + .name = "atmel-hlcdc-pwm", + .of_match_table = atmel_hlcdc_pwm_dt_ids, + }, + .probe = atmel_hlcdc_pwm_probe, + .remove = atmel_hlcdc_pwm_remove, +}; +module_platform_driver(atmel_hlcdc_pwm_driver); + +MODULE_ALIAS("platform:atmel-hlcdc-pwm"); +MODULE_AUTHOR("Boris Brezillon boris.brezillon@free-electrons.com"); +MODULE_DESCRIPTION("Atmel HLCDC PWM driver"); +MODULE_LICENSE("GPL");
The HLCDC IP available in some Atmel SoCs (i.e. sam9x5i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) provide a PWM device.
The DT bindings used for this PWM device is following the default 3 cells bindings described in Documentation/devicetree/bindings/pwm/pwm.txt.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- .../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
diff --git a/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt new file mode 100644 index 0000000..86ad3e2 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt @@ -0,0 +1,55 @@ +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) PWM driver + +The Atmel HLCDC PWM is subdevice of the HLCDC MFD device. +See ../mfd/atmel-hlcdc.txt for more details. + +Required properties: + - compatible: value should be one of the following: + "atmel,hlcdc-pwm" + - pinctr-names: the pin control state names. Should contain "default". + - pinctrl-0: should contain the pinctrl states described by pinctrl + default. + - #pwm-cells: should be set to 3. This PWM chip use the default 3 cells + bindings defined in Documentation/devicetree/bindings/pwm/pwm.txt. + The first cell encodes the PWM id (0 is the only acceptable value here, + because the chip only provide one PWM). + The second cell encodes the PWM period in nanoseconds. + The third cell encodes the PWM flags (the only supported flag is + PWM_POLARITY_INVERTED) + +Example: + + hlcdc: hlcdc@f0030000 { + compatible = "atmel,sama5d3-hlcdc"; + reg = <0xf0030000 0x2000>; + clocks = <&lcdc_clk>, <&lcdck>, <&clk32k>; + clock-names = "periph_clk","sys_clk", "slow_clk"; + status = "disabled"; + + hlcdc-display-controller { + compatible = "atmel,hlcdc-display-controller"; + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>; + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + hlcdc_panel_output: endpoint@0 { + reg = <0>; + remote-endpoint = <&panel_input>; + }; + }; + }; + + hlcdc_pwm: hlcdc-pwm { + compatible = "atmel,hlcdc-pwm"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd_pwm>; + #pwm-cells = <3>; + }; + };
On Tuesday 22 July 2014 06:41 PM, Boris BREZILLON wrote:
The HLCDC IP available in some Atmel SoCs (i.e. sam9x5i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) provide a PWM device.
The DT bindings used for this PWM device is following the default 3 cells bindings described in Documentation/devicetree/bindings/pwm/pwm.txt.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com
.../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
diff --git a/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt new file mode 100644 index 0000000..86ad3e2 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt @@ -0,0 +1,55 @@ +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) PWM driver
+The Atmel HLCDC PWM is subdevice of the HLCDC MFD device. +See ../mfd/atmel-hlcdc.txt for more details.
+Required properties:
- compatible: value should be one of the following:
- "atmel,hlcdc-pwm"
- pinctr-names: the pin control state names. Should contain "default".
- pinctrl-0: should contain the pinctrl states described by pinctrl
- default.
- #pwm-cells: should be set to 3. This PWM chip use the default 3 cells
- bindings defined in Documentation/devicetree/bindings/pwm/pwm.txt.
- The first cell encodes the PWM id (0 is the only acceptable value here,
- because the chip only provide one PWM).
- The second cell encodes the PWM period in nanoseconds.
- The third cell encodes the PWM flags (the only supported flag is
- PWM_POLARITY_INVERTED)
It will be readable if: Required properties: - compatible : value should be one of the following: "atmel,hlcdc-pwm" - pinctr-names : the pin control state names. Should contain "default". - pinctrl-0 : should contain the pinctrl states described by pinctrl default. - #pwm-cells : should be set to 3. This PWM chip use the default 3 cells bindings defined in Documentation/devicetree/bindings/pwm/pwm.txt. The first cell encodes the PWM id (0 is the only acceptable value here, because the chip only provide one PWM). The second cell encodes the PWM period in nanoseconds. The third cell encodes the PWM flags (the only supported flag is PWM_POLARITY_INVERTED) ....
The Atmel HLCDC (HLCD Controller) IP available on some Atmel SoCs (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display controller device.
This display controller supports at least one primary plane and might provide several overlays and an hardware cursor depending on the IP version.
At the moment, this driver only implements an RGB connector to interface with LCD panels, but support for other kind of external devices might be added later.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/atmel-hlcdc/Kconfig | 11 + drivers/gpu/drm/atmel-hlcdc/Makefile | 7 + drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 286 ++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 488 ++++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 224 +++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 635 ++++++++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 396 +++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c | 478 ++++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 804 +++++++++++++++++++++++ 11 files changed, 3332 insertions(+) create mode 100644 drivers/gpu/drm/atmel-hlcdc/Kconfig create mode 100644 drivers/gpu/drm/atmel-hlcdc/Makefile create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index f512004..9183a78 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -184,6 +184,8 @@ source "drivers/gpu/drm/cirrus/Kconfig"
source "drivers/gpu/drm/armada/Kconfig"
+source "drivers/gpu/drm/atmel-hlcdc/Kconfig" + source "drivers/gpu/drm/rcar-du/Kconfig"
source "drivers/gpu/drm/shmobile/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index af9a609..07d388c 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_DRM_GMA500) += gma500/ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ obj-$(CONFIG_DRM_ARMADA) += armada/ +obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/ obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-$(CONFIG_DRM_OMAP) += omapdrm/ diff --git a/drivers/gpu/drm/atmel-hlcdc/Kconfig b/drivers/gpu/drm/atmel-hlcdc/Kconfig new file mode 100644 index 0000000..bc07315 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/Kconfig @@ -0,0 +1,11 @@ +config DRM_ATMEL_HLCDC + tristate "DRM Support for ATMEL HLCDC Display Controller" + depends on DRM && OF && MFD_ATMEL_HLCDC && COMMON_CLK + select DRM_GEM_CMA_HELPER + select DRM_KMS_HELPER + select DRM_KMS_FB_HELPER + select DRM_KMS_CMA_HELPER + select DRM_PANEL + help + Choose this option if you have an ATMEL SoC with an HLCDC display + controller (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family). diff --git a/drivers/gpu/drm/atmel-hlcdc/Makefile b/drivers/gpu/drm/atmel-hlcdc/Makefile new file mode 100644 index 0000000..10ae426 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/Makefile @@ -0,0 +1,7 @@ +atmel-hlcdc-dc-y := atmel_hlcdc_crtc.o \ + atmel_hlcdc_dc.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 new file mode 100644 index 0000000..2186830 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2014 Traphandler + * Copyright (C) 2014 Free Electrons + * + * 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/clk.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drmP.h> + +#include <video/videomode.h> + +#include "atmel_hlcdc_dc.h" + +/** + * Atmel HLCDC CRTC structure + * + * @base: base DRM CRTC structure + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device + * @event: pointer to the current page flip event + * @id: CRTC id (returned by drm_crtc_index) + * @dpms: DPMS mode + */ +struct atmel_hlcdc_crtc { + struct drm_crtc base; + struct atmel_hlcdc *hlcdc; + struct drm_pending_vblank_event *event; + int id; + int dpms; +}; + +static inline struct atmel_hlcdc_crtc * +drm_crtc_to_atmel_hlcdc_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct atmel_hlcdc_crtc, base); +} + + +static void atmel_hlcdc_crtc_dpms(struct drm_crtc *c, int mode) +{ + struct drm_device *dev = c->dev; + + if (mode != DRM_MODE_DPMS_ON) + mode = DRM_MODE_DPMS_OFF; + + pm_runtime_get_sync(dev->dev); + + if (mode == DRM_MODE_DPMS_ON) + pm_runtime_forbid(dev->dev); + else + pm_runtime_allow(dev->dev); + + pm_runtime_put_sync(dev->dev); +} + +static int atmel_hlcdc_crtc_mode_set(struct drm_crtc *c, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + struct regmap *regmap = crtc->hlcdc->regmap; + struct drm_plane *plane = c->primary; + struct drm_framebuffer *fb; + struct videomode vm; + + vm.vfront_porch = mode->vsync_start - mode->vdisplay; + vm.vback_porch = mode->vtotal - mode->vsync_end; + vm.vsync_len = mode->vsync_end - mode->vsync_start; + vm.hfront_porch = mode->hsync_start - mode->hdisplay; + vm.hback_porch = mode->htotal - mode->hsync_end; + vm.hsync_len = mode->hsync_end - mode->hsync_start; + + if (vm.hsync_len > 0x40 || vm.hsync_len < 1 || + vm.vsync_len > 0x40 || vm.vsync_len < 1 || + vm.vfront_porch > 0x40 || vm.vfront_porch < 1 || + vm.vback_porch > 0x40 || vm.vback_porch < 0 || + vm.hfront_porch > 0x200 || vm.hfront_porch < 1 || + vm.hback_porch > 0x200 || vm.hback_porch < 1 || + mode->hdisplay > 2048 || mode->hdisplay < 1 || + mode->vdisplay > 2048 || mode->vdisplay < 1) + return -EINVAL; + + regmap_write(regmap, ATMEL_HLCDC_CFG(1), + (vm.hsync_len - 1) | ((vm.vsync_len - 1) << 16)); + + regmap_write(regmap, ATMEL_HLCDC_CFG(2), + (vm.vfront_porch - 1) | (vm.vback_porch << 16)); + + regmap_write(regmap, ATMEL_HLCDC_CFG(3), + (vm.hfront_porch - 1) | ((vm.hback_porch - 1) << 16)); + + regmap_write(regmap, ATMEL_HLCDC_CFG(4), + (mode->hdisplay - 1) | ((mode->vdisplay - 1) << 16)); + + fb = plane->fb; + plane->fb = old_fb; + + return plane->funcs->update_plane(plane, c, fb, + 0, 0, + mode->hdisplay, mode->vdisplay, + c->x << 16, c->y << 16, + mode->hdisplay << 16, + mode->vdisplay << 16); +} + +static void atmel_hlcdc_crtc_prepare(struct drm_crtc *crtc) +{ + atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void atmel_hlcdc_crtc_commit(struct drm_crtc *crtc) +{ + atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + + +static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = { + + .mode_fixup = atmel_hlcdc_crtc_mode_fixup, + .dpms = atmel_hlcdc_crtc_dpms, + .mode_set = atmel_hlcdc_crtc_mode_set, + .prepare = atmel_hlcdc_crtc_prepare, + .commit = atmel_hlcdc_crtc_commit, +}; + +static void atmel_hlcdc_crtc_destroy(struct drm_crtc *c) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + + drm_crtc_cleanup(c); + kfree(crtc); +} + +void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *c, + struct drm_file *file) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + struct drm_pending_vblank_event *event; + struct drm_device *dev = c->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = crtc->event; + if (event && event->base.file_priv == file) { + event->base.destroy(&event->base); + drm_vblank_put(dev, crtc->id); + crtc->event = NULL; + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static void atmel_hlcdc_crtc_finish_page_flip(void *data) +{ + struct atmel_hlcdc_crtc *crtc = data; + struct drm_device *dev = crtc->base.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (crtc->event) { + drm_send_vblank_event(dev, crtc->id, crtc->event); + drm_vblank_put(dev, crtc->id); + crtc->event = NULL; + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static int atmel_hlcdc_crtc_page_flip(struct drm_crtc *c, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t page_flip_flags) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + struct atmel_hlcdc_plane_update_req req; + struct drm_plane *plane = c->primary; + int ret; + + if (crtc->event) + return -EBUSY; + + memset(&req, 0, sizeof(req)); + req.crtc_x = 0; + req.crtc_y = 0; + req.crtc_h = c->mode.crtc_vdisplay; + req.crtc_w = c->mode.crtc_hdisplay; + req.src_x = c->x << 16; + req.src_y = c->y << 16; + req.src_w = req.crtc_w << 16; + req.src_h = req.crtc_h << 16; + req.fb = fb; + req.crtc = c; + req.finished = atmel_hlcdc_crtc_finish_page_flip; + req.finished_data = crtc; + + ret = atmel_hlcdc_plane_prepare_update_req(plane, &req); + if (ret) + return ret; + + if (event) { + crtc->event = event; + drm_vblank_get(c->dev, crtc->id); + } + + ret = atmel_hlcdc_plane_apply_update_req(plane, &req); + if (ret) { + crtc->event = NULL; + drm_vblank_put(c->dev, crtc->id); + } else { + plane->fb = fb; + } + + return ret; +} + +static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = { + .page_flip = atmel_hlcdc_crtc_page_flip, + .set_config = drm_crtc_helper_set_config, + .destroy = atmel_hlcdc_crtc_destroy, +}; + +int atmel_hlcdc_crtc_create(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_planes *planes = dc->planes; + struct atmel_hlcdc_crtc *crtc; + int ret; + int i; + + crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); + if (!crtc) { + dev_err(dev->dev, "allocation failed\n"); + return -ENOMEM; + } + + crtc->hlcdc = dc->hlcdc; + + ret = drm_crtc_init_with_planes(dev, &crtc->base, + &planes->primary->base, + planes->cursor ? &planes->cursor->base : NULL, + &atmel_hlcdc_crtc_funcs); + if (ret < 0) + goto fail; + + crtc->id = drm_crtc_index(&crtc->base); + + if (planes->cursor) + planes->cursor->base.possible_crtcs = 1 << crtc->id; + + for (i = 0; i < planes->noverlays; i++) + planes->overlays[i]->base.possible_crtcs = 1 << crtc->id; + + drm_crtc_helper_add(&crtc->base, &lcdc_crtc_helper_funcs); + + return 0; + +fail: + atmel_hlcdc_crtc_destroy(&crtc->base); + return ret; +} + diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c new file mode 100644 index 0000000..9581977 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c @@ -0,0 +1,488 @@ +/* + * 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/clk.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> + +#include "atmel_hlcdc_dc.h" + +#define ATMEL_HLCDC_LAYER_IRQS_OFFSET 8 + +static const struct atmel_hlcdc_layer_desc atmel_hlcdc_sama5d3_layers[] = { + { + .name = "base", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x40, + .id = 0, + .type = ATMEL_HLCDC_BASE_LAYER, + .nconfigs = 7, + .layout = { + .xstride = { 2 }, + .default_color = 3, + .general_config = 4, + .disc_pos = 5, + .disc_size = 6, + }, + }, + { + .name = "overlay1", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x140, + .id = 1, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .nconfigs = 10, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + }, + { + .name = "overlay2", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x240, + .id = 2, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .nconfigs = 10, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + }, + { + .name = "high-end-overlay", + .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, + .regs_offset = 0x340, + .id = 3, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .nconfigs = 42, + .layout = { + .pos = 2, + .size = 3, + .memsize = 4, + .xstride = { 5, 7 }, + .pstride = { 6, 8 }, + .default_color = 9, + .chroma_key = 10, + .chroma_key_mask = 11, + .general_config = 12, + .csc = 14, + }, + }, + { + .name = "cursor", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x440, + .id = 4, + .type = ATMEL_HLCDC_CURSOR_LAYER, + .nconfigs = 10, + .max_width = 128, + .max_height = 128, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + }, +}; + +static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = { + .min_width = 0, + .min_height = 0, + .max_width = 2048, + .max_height = 2048, + .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers), + .layers = atmel_hlcdc_sama5d3_layers, +}; + +static const struct of_device_id atmel_hlcdc_of_match[] = { + { + .compatible = "atmel,sama5d3-hlcdc", + .data = &atmel_hlcdc_dc_sama5d3, + }, + { /* sentinel */ }, +}; + +static irqreturn_t atmel_hlcdc_dc_irq_handler(int irq, void *data) +{ + struct drm_device *dev = data; + struct atmel_hlcdc_dc *dc = dev->dev_private; + unsigned long status; + unsigned int imr, isr; + int bit; + + regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_IMR, &imr); + regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr); + status = imr & isr; + if (!status) + return IRQ_NONE; + + bit = ATMEL_HLCDC_LAYER_IRQS_OFFSET; + for_each_set_bit_from(bit, &status, ATMEL_HLCDC_LAYER_IRQS_OFFSET + + ATMEL_HLCDC_MAX_LAYERS) { + int layerid = bit - ATMEL_HLCDC_LAYER_IRQS_OFFSET; + struct atmel_hlcdc_layer *layer = dc->layers[layerid]; + + if (layer) + atmel_hlcdc_layer_irq(layer); + } + + return IRQ_HANDLED; +} + +static struct drm_framebuffer *atmel_hlcdc_fb_create(struct drm_device *dev, + struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd) +{ + return drm_fb_cma_create(dev, file_priv, mode_cmd); +} + +static void atmel_hlcdc_fb_output_poll_changed(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + + if (dc->fbdev) { + drm_fbdev_cma_hotplug_event(dc->fbdev); + } else { + dc->fbdev = drm_fbdev_cma_init(dev, 24, + dev->mode_config.num_crtc, + dev->mode_config.num_connector); + if (IS_ERR(dc->fbdev)) + dc->fbdev = NULL; + } +} + +static const struct drm_mode_config_funcs mode_config_funcs = { + .fb_create = atmel_hlcdc_fb_create, + .output_poll_changed = atmel_hlcdc_fb_output_poll_changed, +}; + +static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_planes *planes; + int ret; + int i; + + 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"); + return PTR_ERR(planes); + } + + dc->planes = planes; + + dc->layers[planes->primary->layer.desc->id] = + &planes->primary->layer; + + if (planes->cursor) + dc->layers[planes->cursor->layer.desc->id] = + &planes->cursor->layer; + + for (i = 0; i < planes->noverlays; i++) + dc->layers[planes->overlays[i]->layer.desc->id] = + &planes->overlays[i]->layer; + + ret = atmel_hlcdc_crtc_create(dev); + if (ret) { + dev_err(dev->dev, "failed to create crtc\n"); + return ret; + } + + dev->mode_config.min_width = dc->desc->min_width; + dev->mode_config.min_height = dc->desc->min_height; + dev->mode_config.max_width = dc->desc->max_width; + dev->mode_config.max_height = dc->desc->max_height; + dev->mode_config.funcs = &mode_config_funcs; + + return 0; +} + +static int atmel_hlcdc_dc_load(struct drm_device *dev, unsigned long flags) +{ + struct platform_device *pdev = dev->platformdev; + const struct of_device_id *match; + struct atmel_hlcdc_dc *dc; + int irq; + int ret; + + match = of_match_node(atmel_hlcdc_of_match, dev->dev->parent->of_node); + if (!match) { + dev_err(&pdev->dev, "invalid compatible string\n"); + return -ENODEV; + } + + if (!match->data) { + dev_err(&pdev->dev, "invalid hlcdc description\n"); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL); + if (!dc) { + dev_err(dev->dev, "failed to allocate private data\n"); + return -ENOMEM; + } + + dc->wq = alloc_ordered_workqueue("atmel-hlcdc-dc", 0); + if (!dc->wq) + return -ENOMEM; + + dc->desc = match->data; + dc->hlcdc = dev_get_drvdata(dev->dev->parent); + dev->dev_private = dc; + + ret = clk_prepare_enable(dc->hlcdc->periph_clk); + if (ret) { + dev_err(dev->dev, "failed to enable periph_clk\n"); + goto err_destroy_wq; + } + + pm_runtime_enable(dev->dev); + + pm_runtime_put_sync(dev->dev); + + ret = atmel_hlcdc_dc_modeset_init(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to initialize mode setting\n"); + goto err_periph_clk_disable; + } + + ret = drm_vblank_init(dev, 1); + if (ret < 0) { + dev_err(dev->dev, "failed to initialize vblank\n"); + goto err_periph_clk_disable; + } + + pm_runtime_get_sync(dev->dev); + ret = drm_irq_install(dev, irq); + pm_runtime_put_sync(dev->dev); + if (ret < 0) { + dev_err(dev->dev, "failed to install IRQ handler\n"); + goto err_periph_clk_disable; + } + + platform_set_drvdata(pdev, dev); + + drm_kms_helper_poll_init(dev); + + /* force connectors detection */ + drm_helper_hpd_irq_event(dev); + + return 0; + +err_periph_clk_disable: + clk_disable_unprepare(dc->hlcdc->periph_clk); + +err_destroy_wq: + destroy_workqueue(dc->wq); + + return ret; +} + +static int atmel_hlcdc_dc_unload(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + + drm_kms_helper_poll_fini(dev); + drm_mode_config_cleanup(dev); + drm_vblank_cleanup(dev); + + pm_runtime_get_sync(dev->dev); + drm_irq_uninstall(dev); + pm_runtime_put_sync(dev->dev); + + dev->dev_private = NULL; + + pm_runtime_disable(dev->dev); + clk_disable_unprepare(dc->hlcdc->periph_clk); + + flush_workqueue(dc->wq); + destroy_workqueue(dc->wq); + + return 0; +} + +static void atmel_hlcdc_dc_preclose(struct drm_device *dev, + struct drm_file *file) +{ + struct drm_crtc *crtc; + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + atmel_hlcdc_crtc_cancel_page_flip(crtc, file); +} + +static void atmel_hlcdc_dc_lastclose(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + + drm_fbdev_cma_restore_mode(dc->fbdev); +} + +static void atmel_hlcdc_dc_irq_preinstall(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + unsigned int isr; + + regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, 0xffffffff); + regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr); +} + +static int atmel_hlcdc_dc_irq_postinstall(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + int i; + + /* Enable interrupts on activated layers */ + for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { + if (dc->layers[i]) + regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, + BIT(i + 8)); + } + + return 0; +} + +static void atmel_hlcdc_dc_irq_uninstall(struct drm_device *dev) +{ + +} + +static int atmel_hlcdc_dc_enable_vblank(struct drm_device *dev, int crtc) +{ + return 0; +} + +static void atmel_hlcdc_dc_disable_vblank(struct drm_device *dev, int crtc) +{ +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct drm_driver atmel_hlcdc_dc_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET, + .load = atmel_hlcdc_dc_load, + .unload = atmel_hlcdc_dc_unload, + .preclose = atmel_hlcdc_dc_preclose, + .lastclose = atmel_hlcdc_dc_lastclose, + .irq_handler = atmel_hlcdc_dc_irq_handler, + .irq_preinstall = atmel_hlcdc_dc_irq_preinstall, + .irq_postinstall = atmel_hlcdc_dc_irq_postinstall, + .irq_uninstall = atmel_hlcdc_dc_irq_uninstall, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = atmel_hlcdc_dc_enable_vblank, + .disable_vblank = atmel_hlcdc_dc_disable_vblank, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .fops = &fops, + .name = "atmel-hlcdc", + .desc = "Atmel HLCD Controller DRM", + .date = "20141504", + .major = 1, + .minor = 0, +}; + +static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) +{ + int ret; + + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + ret = drm_platform_init(&atmel_hlcdc_dc_driver, pdev); + if (ret) + return ret; + + return 0; +} + +static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev) +{ + drm_put_dev(platform_get_drvdata(pdev)); + + return 0; +} + +static const struct of_device_id atmel_hlcdc_dc_of_match[] = { + { .compatible = "atmel,hlcdc-display-controller" }, + { }, +}; + +static struct platform_driver atmel_hlcdc_dc_platform_driver = { + .probe = atmel_hlcdc_dc_drm_probe, + .remove = atmel_hlcdc_dc_drm_remove, + .driver = { + .name = "atmel-hlcdc-display-controller", + .of_match_table = atmel_hlcdc_dc_of_match, + }, +}; +module_platform_driver(atmel_hlcdc_dc_platform_driver); + +MODULE_AUTHOR("Jean-Jacques Hiblot jjhiblot@traphandler.com"); +MODULE_AUTHOR("Boris BREZILLON boris.brezillon@free-electrons.com"); +MODULE_DESCRIPTION("Atmel HLCDC Display Controller DRM Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:atmel-hlcdc-dc"); diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h new file mode 100644 index 0000000..a67df13 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h @@ -0,0 +1,224 @@ +/* + * 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/. + */ + +#ifndef DRM_ATMEL_HLCDC_H +#define DRM_ATMEL_HLCDC_H + +#include <linux/clk.h> +#include <linux/irqdomain.h> +#include <linux/pwm.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_panel.h> +#include <drm/drmP.h> + +#include "atmel_hlcdc_layer.h" + +#define ATMEL_HLCDC_MAX_LAYERS 5 + +/** + * Atmel HLCDC Display Controller description structure. + * + * This structure describe the HLCDC IP capabilities and depends on the + * HLCDC IP version (or Atmel SoC family). + * + * @min_width: minimum width supported by the Display Controller + * @min_height: minimum height supported by the Display Controller + * @max_width: maximum width supported by the Display Controller + * @max_height: maximum height supported by the Display Controller + * @layer: a layer description table describing available layers + * @nlayers: layer description table size + */ +struct atmel_hlcdc_dc_desc { + int min_width; + int min_height; + int max_width; + int max_height; + const struct atmel_hlcdc_layer_desc *layers; + int nlayers; +}; + +/** + * Atmel HLCDC Plane properties. + * + * This structure stores plane property definitions. + * + * @alpha: alpha blending (or transparency) property + * @csc: YUV to RGB conversion factors property + */ +struct atmel_hlcdc_plane_properties { + struct drm_property *alpha; + struct drm_property *rotation; +}; + +/** + * Atmel HLCDC plane rotation enum + * + * TODO: export DRM_ROTATE_XX macros defined by omap driver and use them + * instead of defining this enum. + */ +enum atmel_hlcdc_plane_rotation { + ATMEL_HLCDC_PLANE_NO_ROTATION, + ATMEL_HLCDC_PLANE_90DEG_ROTATION, + ATMEL_HLCDC_PLANE_180DEG_ROTATION, + ATMEL_HLCDC_PLANE_270DEG_ROTATION, +}; + +/** + * Atmel HLCDC Plane. + * + * @base: base DRM plane structure + * @layer: HLCDC layer structure + * @properties: pointer to the property definitions structure + * @alpha: current alpha blending (or transparency) status + */ +struct atmel_hlcdc_plane { + struct drm_plane base; + struct atmel_hlcdc_layer layer; + struct atmel_hlcdc_plane_properties *properties; + enum atmel_hlcdc_plane_rotation rotation; +}; + +static inline struct atmel_hlcdc_plane * +drm_plane_to_atmel_hlcdc_plane(struct drm_plane *p) +{ + return container_of(p, struct atmel_hlcdc_plane, base); +} + +static inline struct atmel_hlcdc_plane * +atmel_hlcdc_layer_to_plane(struct atmel_hlcdc_layer *l) +{ + return container_of(l, struct atmel_hlcdc_plane, layer); +} + +/** + * Atmel HLCDC Plane update request structure. + * + * @crtc_x: x position of the plane relative to the CRTC + * @crtc_y: y position of the plane relative to the CRTC + * @crtc_w: visible width of the plane + * @crtc_h: visible height of the plane + * @src_x: x buffer position + * @src_y: y buffer position + * @src_w: buffer width + * @src_h: buffer height + * @pixel_format: pixel format + * @gems: GEM object object containing image buffers + * @offsets: offsets to apply to the GEM buffers + * @pitches: line size in bytes + * @crtc: crtc to display on + * @finished: finished callback + * @finished_data: data passed to the finished callback + * @bpp: bytes per pixel deduced from pixel_format + * @xstride: value to add to the pixel pointer between each line + * @pstride: value to add to the pixel pointer between each pixel + * @nplanes: number of planes (deduced from pixel_format) + */ +struct atmel_hlcdc_plane_update_req { + int crtc_x; + int crtc_y; + unsigned int crtc_w; + unsigned int crtc_h; + uint32_t src_x; + uint32_t src_y; + uint32_t src_w; + uint32_t src_h; + struct drm_framebuffer *fb; + struct drm_crtc *crtc; + void (*finished)(void *data); + void *finished_data; + + /* These fields are private and should not be touched */ + int bpp[ATMEL_HLCDC_MAX_PLANES]; + unsigned int offsets[ATMEL_HLCDC_MAX_PLANES]; + int xstride[ATMEL_HLCDC_MAX_PLANES]; + int pstride[ATMEL_HLCDC_MAX_PLANES]; + int nplanes; +}; + +/** + * Atmel HLCDC Planes. + * + * This structure stores the instantiated HLCDC Planes and can be accessed by + * the HLCDC Display Controller or the HLCDC CRTC. + * + * @primary: primary plane + * @cursor: hardware cursor plane + * @overlays: overlay plane table + * @noverlays: number of overlay planes + */ +struct atmel_hlcdc_planes { + struct atmel_hlcdc_plane *primary; + struct atmel_hlcdc_plane *cursor; + struct atmel_hlcdc_plane **overlays; + int noverlays; +}; + +/** + * Atmel HLCDC Display Controller. + * + * @desc: HLCDC Display Controller description + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device + * @fbdev: framebuffer device attached to the Display Controller + * @planes: instantiated planes + * @layers: active HLCDC layer + * @wq: display controller workqueue + */ +struct atmel_hlcdc_dc { + const struct atmel_hlcdc_dc_desc *desc; + struct atmel_hlcdc *hlcdc; + struct drm_fbdev_cma *fbdev; + struct atmel_hlcdc_planes *planes; + struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS]; + struct workqueue_struct *wq; +}; + +extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats; +extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats; + +struct atmel_hlcdc_planes * +atmel_hlcdc_create_planes(struct drm_device *dev); + +int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p, + struct atmel_hlcdc_plane_update_req *req); + +int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p, + struct atmel_hlcdc_plane_update_req *req); + +void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, + struct drm_file *file); + +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, + void __iomem *regs); + +int atmel_hlcdc_pwm_destroy(struct drm_device *dev, + struct atmel_hlcdc_pwm_chip *chip); + +#endif /* DRM_ATMEL_HLCDC_H */ diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c new file mode 100644 index 0000000..d31c2e4 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c @@ -0,0 +1,635 @@ +/* + * 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/dma-mapping.h> +#include <linux/interrupt.h> + +#include "atmel_hlcdc_dc.h" + +static void +atmel_hlcdc_layer_fb_flip_release(struct drm_flip_work *work, void *val) +{ + struct atmel_hlcdc_layer_fb_flip *flip = val; + + if (flip->fb) + drm_framebuffer_unreference(flip->fb); + kfree(flip); +} + +static void +atmel_hlcdc_layer_fb_flip_destroy(struct atmel_hlcdc_layer_fb_flip *flip) +{ + if (flip->fb) + drm_framebuffer_unreference(flip->fb); + kfree(flip->task); + kfree(flip); +} + +static void +atmel_hlcdc_layer_fb_flip_release_queue(struct atmel_hlcdc_layer *layer, + struct atmel_hlcdc_layer_fb_flip *flip) +{ + int i; + + if (!flip) + return; + + for (i = 0; i < layer->max_planes; i++) { + if (!flip->dscrs[i]) + break; + + flip->dscrs[i]->status = 0; + flip->dscrs[i] = NULL; + } + + drm_flip_work_queue_task(&layer->gc, flip->task); + drm_flip_work_commit(&layer->gc, layer->wq); +} + +static void atmel_hlcdc_layer_update_reset(struct atmel_hlcdc_layer *layer, + int id) +{ + struct atmel_hlcdc_layer_update *upd = &layer->update; + struct atmel_hlcdc_layer_update_slot *slot; + + if (id < 0 || id > 1) + return; + + slot = &upd->slots[id]; + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs); + memset(slot->configs, 0, + sizeof(*slot->configs) * layer->desc->nconfigs); + + if (slot->fb_flip) { + atmel_hlcdc_layer_fb_flip_release_queue(layer, slot->fb_flip); + slot->fb_flip = NULL; + } +} + +static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer) +{ + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma; + const struct atmel_hlcdc_layer_desc *desc = layer->desc; + struct atmel_hlcdc_layer_update *upd = &layer->update; + struct regmap *regmap = layer->hlcdc->regmap; + struct atmel_hlcdc_layer_update_slot *slot; + struct atmel_hlcdc_layer_fb_flip *fb_flip; + struct atmel_hlcdc_dma_channel_dscr *dscr; + unsigned int cfg; + u32 action = 0; + int i = 0; + + if (upd->pending < 0 || upd->pending > 1 || + dma->status == ATMEL_HLCDC_LAYER_DISABLING) + return; + + slot = &upd->slots[upd->pending]; + + for_each_set_bit(cfg, slot->updated_configs, layer->desc->nconfigs) { + regmap_write(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_CFG(layer, cfg), + slot->configs[cfg]); + action |= ATMEL_HLCDC_LAYER_UPDATE; + } + + fb_flip = slot->fb_flip; + + if (!fb_flip->fb) + goto apply; + + if (dma->status == ATMEL_HLCDC_LAYER_DISABLED) { + for (i = 0; i < fb_flip->ngems; i++) { + dscr = fb_flip->dscrs[i]; + dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH | + ATMEL_HLCDC_LAYER_DMA_IRQ | + ATMEL_HLCDC_LAYER_ADD_IRQ | + ATMEL_HLCDC_LAYER_DONE_IRQ; + + regmap_write(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_PLANE_ADDR(i), + dscr->addr); + regmap_write(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_PLANE_CTRL(i), + dscr->ctrl); + regmap_write(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_PLANE_NEXT(i), + dscr->next); + } + + action |= ATMEL_HLCDC_LAYER_DMA_CHAN; + dma->status = ATMEL_HLCDC_LAYER_ENABLED; + } else { + for (i = 0; i < fb_flip->ngems; i++) { + dscr = fb_flip->dscrs[i]; + dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH | + ATMEL_HLCDC_LAYER_DMA_IRQ | + ATMEL_HLCDC_LAYER_DSCR_IRQ | + ATMEL_HLCDC_LAYER_DONE_IRQ; + + regmap_write(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_PLANE_HEAD(i), + dscr->next); + } + + action |= ATMEL_HLCDC_LAYER_A2Q; + } + + /* Release unneeded descriptors */ + for (i = fb_flip->ngems; i < layer->max_planes; i++) { + fb_flip->dscrs[i]->status = 0; + fb_flip->dscrs[i] = NULL; + } + + dma->queue = fb_flip; + slot->fb_flip = NULL; + +apply: + if (action) + regmap_write(regmap, + desc->regs_offset + ATMEL_HLCDC_LAYER_CHER, + action); + + atmel_hlcdc_layer_update_reset(layer, upd->pending); + + upd->pending = -1; +} + +void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer) +{ + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma; + const struct atmel_hlcdc_layer_desc *desc = layer->desc; + struct regmap *regmap = layer->hlcdc->regmap; + struct atmel_hlcdc_layer_fb_flip *flip; + unsigned long flags; + unsigned int isr, imr; + unsigned int status; + unsigned int plane_status; + u32 flip_status; + + int i; + + regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IMR, &imr); + regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR, &isr); + status = imr & isr; + if (!status) + return; + + spin_lock_irqsave(&layer->lock, flags); + + flip = dma->queue ? dma->queue : dma->cur; + + if (!flip) { + spin_unlock_irqrestore(&layer->lock, flags); + return; + } + + flip_status = 0; + for (i = 0; i < flip->ngems; i++) { + plane_status = (status >> (8 * i)); + + if (plane_status & + (ATMEL_HLCDC_LAYER_ADD_IRQ | + ATMEL_HLCDC_LAYER_DSCR_IRQ) & + ~flip->dscrs[i]->ctrl) { + flip->dscrs[i]->status |= + ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED; + flip->dscrs[i]->ctrl |= + ATMEL_HLCDC_LAYER_ADD_IRQ | + ATMEL_HLCDC_LAYER_DSCR_IRQ; + } + + if (plane_status & + ATMEL_HLCDC_LAYER_DONE_IRQ & + ~flip->dscrs[i]->ctrl) { + flip->dscrs[i]->status |= + ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE; + flip->dscrs[i]->ctrl |= + ATMEL_HLCDC_LAYER_DONE_IRQ; + } + + flip_status |= flip->dscrs[i]->status; + } + + /* Get changed bits */ + flip_status ^= flip->status; + + if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED) { + atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur); + dma->cur = dma->queue; + dma->queue = NULL; + } + + if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE) { + atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur); + dma->cur = NULL; + } + + flip->status |= flip_status; + + if (!dma->queue) { + atmel_hlcdc_layer_update_apply(layer); + + if (!dma->cur) + dma->status = ATMEL_HLCDC_LAYER_DISABLED; + } + + spin_unlock_irqrestore(&layer->lock, flags); +} + +int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer) +{ + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma; + struct atmel_hlcdc_layer_update *upd = &layer->update; + struct atmel_hlcdc_layer_fb_flip *flip; + unsigned long flags; + int i; + + spin_lock_irqsave(&layer->lock, flags); + + /* + * First disable DMA transfers. If a DMA transfer has been queued + * we're stopping this one instead of the current one because we + * can't know for sure if queued transfer has been started or not. + */ + flip = dma->queue ? dma->queue : dma->cur; + if (flip) { + for (i = 0; i < flip->ngems; i++) + flip->dscrs[i]->ctrl &= ~(ATMEL_HLCDC_LAYER_DFETCH | + ATMEL_HLCDC_LAYER_DONE_IRQ); + + dma->status = ATMEL_HLCDC_LAYER_DISABLING; + } + + /* + * Then discard the pending update request (if any) to prevent + * DMA irq handler from restarting the DMA channel after it has + * been disabled. + */ + if (upd->pending >= 0) { + atmel_hlcdc_layer_update_reset(layer, upd->pending); + upd->pending = -1; + } + + spin_unlock_irqrestore(&layer->lock, flags); + + return 0; +} + +int atmel_hlcdc_layer_update_start(struct atmel_hlcdc_layer *layer) +{ + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma; + struct atmel_hlcdc_layer_update *upd = &layer->update; + struct regmap *regmap = layer->hlcdc->regmap; + struct atmel_hlcdc_layer_fb_flip *fb_flip; + struct atmel_hlcdc_layer_update_slot *slot; + unsigned long flags; + int i, j = 0; + + fb_flip = kzalloc(sizeof(*fb_flip), GFP_KERNEL); + if (!fb_flip) + return -ENOMEM; + + fb_flip->task = drm_flip_work_allocate_task(fb_flip, GFP_KERNEL); + if (!fb_flip->task) { + kfree(fb_flip); + return -ENOMEM; + } + + spin_lock_irqsave(&layer->lock, flags); + + upd->next = upd->pending ? 0 : 1; + + slot = &upd->slots[upd->next]; + + for (i = 0; i < layer->max_planes * 4; i++) { + if (!dma->dscrs[i].status) { + fb_flip->dscrs[j++] = &dma->dscrs[i]; + dma->dscrs[i].status = + ATMEL_HLCDC_DMA_CHANNEL_DSCR_RESERVED; + if (j == layer->max_planes) + break; + } + } + + if (j < layer->max_planes) { + for (i = 0; i < j; i++) + fb_flip->dscrs[i]->status = 0; + } + + if (j < layer->max_planes) { + spin_unlock_irqrestore(&layer->lock, flags); + atmel_hlcdc_layer_fb_flip_destroy(fb_flip); + return -EBUSY; + } + + slot->fb_flip = fb_flip; + + if (upd->pending >= 0) { + memcpy(slot->configs, + upd->slots[upd->pending].configs, + layer->desc->nconfigs * sizeof(u32)); + memcpy(slot->updated_configs, + upd->slots[upd->pending].updated_configs, + DIV_ROUND_UP(layer->desc->nconfigs, + BITS_PER_BYTE * sizeof(unsigned long)) * + sizeof(unsigned long)); + slot->fb_flip->fb = upd->slots[upd->pending].fb_flip->fb; + if (upd->slots[upd->pending].fb_flip->fb) { + slot->fb_flip->fb = + upd->slots[upd->pending].fb_flip->fb; + slot->fb_flip->ngems = + upd->slots[upd->pending].fb_flip->ngems; + drm_framebuffer_reference(slot->fb_flip->fb); + } + } else { + regmap_bulk_read(regmap, + layer->desc->regs_offset + + ATMEL_HLCDC_LAYER_CFG(layer, 0), + upd->slots[upd->next].configs, + layer->desc->nconfigs); + } + + spin_unlock_irqrestore(&layer->lock, flags); + + return 0; +} + +void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer) +{ + struct atmel_hlcdc_layer_update *upd = &layer->update; + + atmel_hlcdc_layer_update_reset(layer, upd->next); + upd->next = -1; +} + +void atmel_hlcdc_layer_update_set_fb(struct atmel_hlcdc_layer *layer, + struct drm_framebuffer *fb, + unsigned int *offsets) +{ + struct atmel_hlcdc_layer_update *upd = &layer->update; + struct atmel_hlcdc_layer_fb_flip *fb_flip; + struct atmel_hlcdc_layer_update_slot *slot; + struct atmel_hlcdc_dma_channel_dscr *dscr; + struct drm_framebuffer *old_fb; + int nplanes = 0; + int i; + + if (upd->next < 0 || upd->next > 1) + return; + + if (fb) + nplanes = drm_format_num_planes(fb->pixel_format); + + if (nplanes > layer->max_planes) + return; + + slot = &upd->slots[upd->next]; + + fb_flip = slot->fb_flip; + old_fb = slot->fb_flip->fb; + + for (i = 0; i < nplanes; i++) { + struct drm_gem_cma_object *gem; + + dscr = slot->fb_flip->dscrs[i]; + gem = drm_fb_cma_get_gem_obj(fb, i); + dscr->addr = gem->paddr + offsets[i]; + } + + fb_flip->ngems = nplanes; + fb_flip->fb = fb; + + if (fb) + drm_framebuffer_reference(fb); + + if (old_fb) + drm_framebuffer_unreference(old_fb); +} + +void atmel_hlcdc_layer_update_set_finished(struct atmel_hlcdc_layer *layer, + void (*finished)(void *data), + void *finished_data) +{ + struct atmel_hlcdc_layer_update *upd = &layer->update; + struct atmel_hlcdc_layer_update_slot *slot; + + if (upd->next < 0 || upd->next > 1) + return; + + slot = &upd->slots[upd->next]; + + slot->fb_flip->finished = finished; + slot->fb_flip->finished_data = finished_data; +} + +void atmel_hlcdc_layer_update_cfg(struct atmel_hlcdc_layer *layer, int cfg, + u32 mask, u32 val) +{ + struct atmel_hlcdc_layer_update *upd = &layer->update; + struct atmel_hlcdc_layer_update_slot *slot; + + if (upd->next < 0 || upd->next > 1) + return; + + if (cfg >= layer->desc->nconfigs) + return; + + slot = &upd->slots[upd->next]; + slot->configs[cfg] &= ~mask; + slot->configs[cfg] |= (val & mask); + set_bit(cfg, slot->updated_configs); +} + +void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer) +{ + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma; + struct atmel_hlcdc_layer_update *upd = &layer->update; + struct atmel_hlcdc_layer_update_slot *slot; + unsigned long flags; + + if (upd->next < 0 || upd->next > 1) + return; + + slot = &upd->slots[upd->next]; + + spin_lock_irqsave(&layer->lock, flags); + + /* + * Release pending update request and replace it by the new one. + */ + if (upd->pending >= 0) + atmel_hlcdc_layer_update_reset(layer, upd->pending); + + upd->pending = upd->next; + upd->next = -1; + + if (!dma->queue) + atmel_hlcdc_layer_update_apply(layer); + + spin_unlock_irqrestore(&layer->lock, flags); + + + upd->next = -1; +} + +static int atmel_hlcdc_layer_dma_init(struct drm_device *dev, + struct atmel_hlcdc_layer *layer) +{ + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma; + dma_addr_t dma_addr; + int i; + + dma->dscrs = dma_alloc_coherent(dev->dev, + layer->max_planes * 4 * + sizeof(*dma->dscrs), + &dma_addr, GFP_KERNEL); + if (!dma->dscrs) + return -ENOMEM; + + for (i = 0; i < layer->max_planes * 4; i++) { + struct atmel_hlcdc_dma_channel_dscr *dscr = &dma->dscrs[i]; + + dscr->next = dma_addr + (i * sizeof(*dscr)); + } + + return 0; +} + +static void atmel_hlcdc_layer_dma_cleanup(struct drm_device *dev, + struct atmel_hlcdc_layer *layer) +{ + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma; + int i; + + for (i = 0; i < layer->max_planes * 4; i++) { + struct atmel_hlcdc_dma_channel_dscr *dscr = &dma->dscrs[i]; + + dscr->status = 0; + } + + dma_free_coherent(dev->dev, layer->max_planes * 4 * + sizeof(*dma->dscrs), dma->dscrs, + dma->dscrs[0].next); +} + +static int atmel_hlcdc_layer_update_init(struct drm_device *dev, + struct atmel_hlcdc_layer *layer, + const struct atmel_hlcdc_layer_desc *desc) +{ + struct atmel_hlcdc_layer_update *upd = &layer->update; + int updated_size; + void *buffer; + int i; + + updated_size = DIV_ROUND_UP(desc->nconfigs, + BITS_PER_BYTE * + sizeof(unsigned long)); + + buffer = devm_kzalloc(dev->dev, + ((desc->nconfigs * sizeof(u32)) + + (updated_size * sizeof(unsigned long))) * 2, + GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + for (i = 0; i < 2; i++) { + upd->slots[i].updated_configs = buffer; + buffer += updated_size * sizeof(unsigned long); + upd->slots[i].configs = buffer; + buffer += desc->nconfigs * sizeof(u32); + } + + upd->pending = -1; + upd->next = -1; + + return 0; +} + +int atmel_hlcdc_layer_init(struct drm_device *dev, + struct atmel_hlcdc_layer *layer, + const struct atmel_hlcdc_layer_desc *desc) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct regmap *regmap = dc->hlcdc->regmap; + unsigned int tmp; + int ret; + int i; + + layer->hlcdc = dc->hlcdc; + layer->wq = dc->wq; + layer->desc = desc; + + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR, + ATMEL_HLCDC_LAYER_RST); + for (i = 0; i < desc->formats->nformats; i++) { + int nplanes = drm_format_num_planes(desc->formats->formats[i]); + + if (nplanes > layer->max_planes) + layer->max_planes = nplanes; + } + + spin_lock_init(&layer->lock); + drm_flip_work_init(&layer->gc, desc->name, + atmel_hlcdc_layer_fb_flip_release); + ret = atmel_hlcdc_layer_dma_init(dev, layer); + if (ret) + return ret; + + ret = atmel_hlcdc_layer_update_init(dev, layer, desc); + if (ret) + return ret; + + /* Flush Status Register */ + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IDR, + 0xffffffff); + regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR, + &tmp); + + tmp = 0; + for (i = 0; i < layer->max_planes; i++) + tmp |= (ATMEL_HLCDC_LAYER_DMA_IRQ | + ATMEL_HLCDC_LAYER_DSCR_IRQ | + ATMEL_HLCDC_LAYER_ADD_IRQ | + ATMEL_HLCDC_LAYER_DONE_IRQ | + ATMEL_HLCDC_LAYER_OVR_IRQ) << (8 * i); + + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER, tmp); + + return 0; +} + +void atmel_hlcdc_layer_cleanup(struct drm_device *dev, + struct atmel_hlcdc_layer *layer) +{ + const struct atmel_hlcdc_layer_desc *desc = layer->desc; + struct regmap *regmap = layer->hlcdc->regmap; + + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IDR, + 0xffffffff); + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR, + ATMEL_HLCDC_LAYER_RST); + + atmel_hlcdc_layer_dma_cleanup(dev, layer); + drm_flip_work_cleanup(&layer->gc); +} diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h new file mode 100644 index 0000000..885b57a --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h @@ -0,0 +1,396 @@ +/* + * 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/. + */ + +#ifndef DRM_ATMEL_HLCDC_LAYER_H +#define DRM_ATMEL_HLCDC_LAYER_H + +#include <linux/mfd/atmel-hlcdc.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_flip_work.h> +#include <drm/drmP.h> + +#define ATMEL_HLCDC_LAYER_CHER 0x0 +#define ATMEL_HLCDC_LAYER_CHDR 0x4 +#define ATMEL_HLCDC_LAYER_CHSR 0x8 +#define ATMEL_HLCDC_LAYER_DMA_CHAN BIT(0) +#define ATMEL_HLCDC_LAYER_UPDATE BIT(1) +#define ATMEL_HLCDC_LAYER_A2Q BIT(2) +#define ATMEL_HLCDC_LAYER_RST BIT(8) + +#define ATMEL_HLCDC_LAYER_IER 0xc +#define ATMEL_HLCDC_LAYER_IDR 0x10 +#define ATMEL_HLCDC_LAYER_IMR 0x14 +#define ATMEL_HLCDC_LAYER_ISR 0x18 +#define ATMEL_HLCDC_LAYER_DFETCH BIT(0) +#define ATMEL_HLCDC_LAYER_LFETCH BIT(1) +#define ATMEL_HLCDC_LAYER_DMA_IRQ BIT(2) +#define ATMEL_HLCDC_LAYER_DSCR_IRQ BIT(3) +#define ATMEL_HLCDC_LAYER_ADD_IRQ BIT(4) +#define ATMEL_HLCDC_LAYER_DONE_IRQ BIT(5) +#define ATMEL_HLCDC_LAYER_OVR_IRQ BIT(6) + +#define ATMEL_HLCDC_LAYER_PLANE_HEAD(n) (((n) * 0x10) + 0x1c) +#define ATMEL_HLCDC_LAYER_PLANE_ADDR(n) (((n) * 0x10) + 0x20) +#define ATMEL_HLCDC_LAYER_PLANE_CTRL(n) (((n) * 0x10) + 0x24) +#define ATMEL_HLCDC_LAYER_PLANE_NEXT(n) (((n) * 0x10) + 0x28) +#define ATMEL_HLCDC_LAYER_CFG(p, c) (((c) * 4) + ((p)->max_planes * 0x10) + 0x1c) + +#define ATMEL_HLCDC_LAYER_DMA_CFG_ID 0 +#define ATMEL_HLCDC_LAYER_DMA_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, ATMEL_HLCDC_LAYER_DMA_CFG_ID) + +#define ATMEL_HLCDC_LAYER_FORMAT_CFG_ID 1 +#define ATMEL_HLCDC_LAYER_FORMAT_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, ATMEL_HLCDC_LAYER_FORMAT_CFG_ID) +#define ATMEL_HLCDC_LAYER_RGB (0 << 0) +#define ATMEL_HLCDC_LAYER_CLUT (1 << 0) +#define ATMEL_HLCDC_LAYER_YUV (2 << 0) +#define ATMEL_HLCDC_RGB_MODE(m) (((m) & 0xf) << 4) +#define ATMEL_HLCDC_CLUT_MODE(m) (((m) & 0x3) << 8) +#define ATMEL_HLCDC_YUV_MODE(m) (((m) & 0xf) << 12) +#define ATMEL_HLCDC_YUV422ROT (1 << 16) +#define ATMEL_HLCDC_YUV422SWP (1 << 17) +#define ATMEL_HLCDC_DSCALEOPT (1 << 20) + +#define ATMEL_HLCDC_XRGB4444_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(0)) +#define ATMEL_HLCDC_ARGB4444_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(1)) +#define ATMEL_HLCDC_RGBA4444_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(2)) +#define ATMEL_HLCDC_RGB565_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(3)) +#define ATMEL_HLCDC_ARGB1555_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(4)) +#define ATMEL_HLCDC_XRGB8888_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(9)) +#define ATMEL_HLCDC_RGB888_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(10)) +#define ATMEL_HLCDC_ARGB8888_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(12)) +#define ATMEL_HLCDC_RGBA8888_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(13)) + +#define ATMEL_HLCDC_AYUV_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(0)) +#define ATMEL_HLCDC_YUYV_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(1)) +#define ATMEL_HLCDC_UYVY_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(2)) +#define ATMEL_HLCDC_YVYU_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(3)) +#define ATMEL_HLCDC_VYUY_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(4)) +#define ATMEL_HLCDC_NV61_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(5)) +#define ATMEL_HLCDC_YUV422_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(6)) +#define ATMEL_HLCDC_NV21_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(7)) +#define ATMEL_HLCDC_YUV420_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(8)) + +#define ATMEL_HLCDC_LAYER_POS_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.pos) +#define ATMEL_HLCDC_LAYER_SIZE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.size) +#define ATMEL_HLCDC_LAYER_MEMSIZE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.memsize) +#define ATMEL_HLCDC_LAYER_XSTRIDE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.xstride) +#define ATMEL_HLCDC_LAYER_PSTRIDE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.pstride) +#define ATMEL_HLCDC_LAYER_DFLTCOLOR_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.default_color) +#define ATMEL_HLCDC_LAYER_CRKEY_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.chroma_key) +#define ATMEL_HLCDC_LAYER_CRKEY_MASK_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.chroma_key_mask) + +#define ATMEL_HLCDC_LAYER_GENERAL_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.general_config) +#define ATMEL_HLCDC_LAYER_CRKEY BIT(0) +#define ATMEL_HLCDC_LAYER_INV BIT(1) +#define ATMEL_HLCDC_LAYER_ITER2BL BIT(2) +#define ATMEL_HLCDC_LAYER_ITER BIT(3) +#define ATMEL_HLCDC_LAYER_REVALPHA BIT(4) +#define ATMEL_HLCDC_LAYER_GAEN BIT(5) +#define ATMEL_HLCDC_LAYER_LAEN BIT(6) +#define ATMEL_HLCDC_LAYER_OVR BIT(7) +#define ATMEL_HLCDC_LAYER_DMA BIT(8) +#define ATMEL_HLCDC_LAYER_REP BIT(9) +#define ATMEL_HLCDC_LAYER_DSTKEY BIT(10) +#define ATMEL_HLCDC_LAYER_GA_MASK GENMASK(23, 16) +#define ATMEL_HLCDC_LAYER_GA_SHIFT 16 + +#define ATMEL_HLCDC_LAYER_CSC_CFG(p, o) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.csc + o) + +#define ATMEL_HLCDC_LAYER_DISC_POS_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.disc_pos) + +#define ATMEL_HLCDC_LAYER_DISC_SIZE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.disc_size) + +#define ATMEL_HLCDC_MAX_PLANES 3 + +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_RESERVED BIT(0) +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED BIT(1) +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE BIT(2) + +/** + * Atmel HLCDC Layer registers layout structure + * + * Each HLCDC layer has its own register organization and a given register + * by be placed differently on 2 different layers depending on its + * capabilities. + * This structure stores common registers layout for a given layer and is + * used by HLCDC layer code to chose the appropriate register to write to + * or to read from. + * + * For all fields, a value of zero means "unsupported". + * + * See Atmel's datasheet for a detailled description of these registers. + * + * @xstride: xstride registers + * @pstride: pstride registers + * @pos: position register + * @size: displayed size register + * @memsize: memory size register + * @default_color: default color register + * @chroma_key: chroma key register + * @chroma_key_mask: chroma key mask register + * @general_config: general layer config register + * @disc_pos: discard area position register + * @disc_size: discard area size register + * @csc: color space conversion register + */ +struct atmel_hlcdc_layer_cfg_layout { + int xstride[ATMEL_HLCDC_MAX_PLANES]; + int pstride[ATMEL_HLCDC_MAX_PLANES]; + int pos; + int size; + int memsize; + int default_color; + int chroma_key; + int chroma_key_mask; + int general_config; + int disc_pos; + int disc_size; + int csc; +}; + +/** + * Atmel HLCDC framebuffer flip structure + * + * This structure is allocated when someone asked for a layer update (most + * likely a DRM plane update, either primary, overlay or cursor plane) and + * released when the layer do not need to reference the framebuffer object + * anymore (i.e. the layer was disabled or updated). + * + * @fb: the referenced framebuffer object. + * @refcnt: the number of GEM object still referenced by the layer. + * When no more objects are referenced the fb flip structure is + * added to the garbage collector. + * @ngems: number of GEM objects referenced by the fb element. + * @finished: finished callback, called when the layer framebuffer flip is + * finished. + * @finished_data: data passed to the finished callback. + */ +struct atmel_hlcdc_layer_fb_flip { + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES]; + struct drm_flip_task *task; + struct drm_framebuffer *fb; + int ngems; + u32 status; + void (*finished)(void *data); + void *finished_data; +}; + +/** + * Atmel HLCDC DMA descriptor structure + * + * This structure is used by the HLCDC DMA engine to schedule a DMA transfer. + * + * The structure fields must remain in this specific order, because they're + * used by the HLCDC DMA engine, which expect them in this order. + * + * @addr: buffer DMA address + * @ctrl: DMA transfer options + * @next: next DMA descriptor to fetch + * @gem_flip: the attached gem_flip operation + */ +struct atmel_hlcdc_dma_channel_dscr { + dma_addr_t addr; + u32 ctrl; + dma_addr_t next; + u32 status; +} __aligned(sizeof(u64)); + +/** + * Atmel HLCDC layer types + */ +enum atmel_hlcdc_layer_type { + ATMEL_HLCDC_BASE_LAYER, + ATMEL_HLCDC_OVERLAY_LAYER, + ATMEL_HLCDC_CURSOR_LAYER, + ATMEL_HLCDC_PP_LAYER, +}; + +/** + * Atmel HLCDC Supported formats structure + * + * This structure list all the formats supported by a given layer. + * + * @nformats: number of supported formats + * @formats: supported formats + */ +struct atmel_hlcdc_formats { + int nformats; + uint32_t *formats; +}; + +/** + * Atmel HLCDC Layer description structure + * + * This structure describe the capabilities provided by a given layer. + * + * @name: layer name + * @type: layer type + * @id: layer id + * @regs_offset: offset of the layer registers from the HLCDC registers base + * @nconfigs: number of config registers provided by this layer + * @layout: config registers layout + * @max_width: maximum width supported by this layer (0 means unlimited) + * @max_height: maximum height supported by this layer (0 means unlimited) + */ +struct atmel_hlcdc_layer_desc { + const char *name; + enum atmel_hlcdc_layer_type type; + int id; + int regs_offset; + int nconfigs; + struct atmel_hlcdc_formats *formats; + struct atmel_hlcdc_layer_cfg_layout layout; + int max_width; + int max_height; +}; + +/** + * Atmel HLCDC Layer Update Slot structure + * + * This structure stores layer update requests to be applied on next frame. + * This is the base structure behind the atomic layer update infrastructure. + * + * Atomic layer update provides a way to update all layer's parameters + * simultaneously. This is needed to avoid incompatible sequential updates + * like this one: + * 1) update layer format from RGB888 (1 plane/buffer) to YUV422 + * (2 planes/buffers) + * 2) the format update is applied but the DMA channel for the second + * plane/buffer is not enabled + * 3) enable the DMA channel for the second plane + * + * @dscrs: DMA channel descriptors + * @fb_flip: fb_flip object + * @updated_configs: bitmask used to record modified configs + * @configs: new config values + */ +struct atmel_hlcdc_layer_update_slot { + struct atmel_hlcdc_layer_fb_flip *fb_flip; + unsigned long *updated_configs; + u32 *configs; +}; + +/** + * Atmel HLCDC Layer Update structure + * + * This structure provides a way to queue layer update requests. + * + * At a given time there is at most: + * - one pending update request, which means the update request has been + * commited (or validated) and is waiting for the DMA channel(s) to be + * available + * - one request being prepared, which means someone started a layer update + * but has not commited it yet. There cannot be more than one started + * request, because the update lock is taken when starting a layer update + * and release when commiting or rolling back the request. + * + * @slots: update slots. One is used for pending request and the other one + * for started update request + * @pending: the pending slot index or -1 if no request is pending + * @next: the started update slot index or -1 no update has been started + */ +struct atmel_hlcdc_layer_update { + struct atmel_hlcdc_layer_update_slot slots[2]; + int pending; + int next; +}; + +enum atmel_hlcdc_layer_dma_channel_status { + ATMEL_HLCDC_LAYER_DISABLED, + ATMEL_HLCDC_LAYER_ENABLED, + ATMEL_HLCDC_LAYER_DISABLING, +}; + +/** + * Atmel HLCDC Layer DMA channel structure + * + * This structure stores informations on the DMA channel associated to a + * given layer. + * + * @status: DMA channel status + * @cur: current framebuffer + * @queue: next framebuffer + * @dscrs: allocated DMA descriptors + */ +struct atmel_hlcdc_layer_dma_channel { + enum atmel_hlcdc_layer_dma_channel_status status; + struct atmel_hlcdc_layer_fb_flip *cur; + struct atmel_hlcdc_layer_fb_flip *queue; + struct atmel_hlcdc_dma_channel_dscr *dscrs; +}; + +/** + * Atmel HLCDC Layer structure + * + * This structure stores information on the layer instance. + * + * @desc: layer description + * @max_planes: maximum planes/buffers that can be associated with this layer. + * This depends on the supported formats. + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device + * @dma: dma channel + * @gc: fb flip garbage collector + * @update: update handler + * @lock: layer lock + */ +struct atmel_hlcdc_layer { + const struct atmel_hlcdc_layer_desc *desc; + int max_planes; + struct atmel_hlcdc *hlcdc; + struct workqueue_struct *wq; + struct drm_flip_work gc; + struct atmel_hlcdc_layer_dma_channel dma; + struct atmel_hlcdc_layer_update update; + spinlock_t lock; +}; + +void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer); + +int atmel_hlcdc_layer_init(struct drm_device *dev, + struct atmel_hlcdc_layer *layer, + const struct atmel_hlcdc_layer_desc *desc); + +void atmel_hlcdc_layer_cleanup(struct drm_device *dev, + struct atmel_hlcdc_layer *layer); + +int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer); + +void atmel_hlcdc_layer_set_finished(struct atmel_hlcdc_layer *layer, + void (*finished)(void *data), + void *data); + +int atmel_hlcdc_layer_update_start(struct atmel_hlcdc_layer *layer); + +void atmel_hlcdc_layer_update_cfg(struct atmel_hlcdc_layer *layer, int cfg, + u32 mask, u32 val); + +void atmel_hlcdc_layer_update_set_fb(struct atmel_hlcdc_layer *layer, + struct drm_framebuffer *fb, + unsigned int *offsets); + +void atmel_hlcdc_layer_update_set_finished(struct atmel_hlcdc_layer *layer, + void (*finished)(void *data), + void *finished_data); + +void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer); + +void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer); + +#endif /* DRM_ATMEL_HLCDC_LAYER_H */ diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c new file mode 100644 index 0000000..de95eb7 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c @@ -0,0 +1,478 @@ +/* + * 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); + 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 *hlcdc; + 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 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, + .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); + struct regmap *regmap = rgb->hlcdc->regmap; + unsigned int status; + + if (mode != DRM_MODE_DPMS_ON) + mode = DRM_MODE_DPMS_OFF; + + if (mode == rgb->dpms) + return; + + if (mode != DRM_MODE_DPMS_ON) { + regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_DISP); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + (status & ATMEL_HLCDC_DISP)) + cpu_relax(); + + regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_SYNC); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + (status & ATMEL_HLCDC_SYNC)) + cpu_relax(); + + regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_PIXEL_CLK); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + (status & ATMEL_HLCDC_PIXEL_CLK)) + cpu_relax(); + + clk_disable_unprepare(rgb->hlcdc->sys_clk); + + rgb->slave->ops->disable(rgb->slave); + } else { + rgb->slave->ops->enable(rgb->slave); + + clk_prepare_enable(rgb->hlcdc->sys_clk); + + regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_PIXEL_CLK); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + !(status & ATMEL_HLCDC_PIXEL_CLK)) + cpu_relax(); + + + regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_SYNC); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + !(status & ATMEL_HLCDC_SYNC)) + cpu_relax(); + + regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_DISP); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + !(status & ATMEL_HLCDC_DISP)) + cpu_relax(); + } + + 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 long prate = clk_get_rate(rgb->hlcdc->sys_clk); + unsigned long mode_rate = mode->clock * 1000; + u32 cfg = 0; + int div; + + if ((prate / 2) < mode_rate) { + prate *= 2; + cfg |= ATMEL_HLCDC_CLKSEL; + } + + div = DIV_ROUND_UP(prate, mode_rate); + if (div < 2) + div = 2; + + cfg |= ATMEL_HLCDC_CLKDIV(div); + + if (mode->flags & DRM_MODE_FLAG_NCSYNC) + cfg |= ATMEL_HLCDC_CLKPOL; + + regmap_update_bits(rgb->hlcdc->regmap, ATMEL_HLCDC_CFG(0), + ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK, cfg); + + cfg = 0; + + if (info->nbus_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; + } + } + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + cfg |= ATMEL_HLCDC_VSPOL; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + cfg |= ATMEL_HLCDC_HSPOL; + + regmap_update_bits(rgb->hlcdc->regmap, ATMEL_HLCDC_CFG(5), + ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL | + ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE | + ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY | + ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO | + ATMEL_HLCDC_MODE_MASK | ATMEL_HLCDC_GUARDTIME_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) +{ + return MODE_OK; +} + +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_disconnected; +} + +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_sysfs_connector_remove(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; + + rgb = devm_kzalloc(dev->dev, sizeof(*rgb), GFP_KERNEL); + if (!rgb) + return -ENOMEM; + + rgb->endpoint = *ep; + + rgb->dpms = DRM_MODE_DPMS_OFF; + + rgb->hlcdc = dc->hlcdc; + + drm_connector_init(dev, &rgb->connector, + &atmel_hlcdc_rgb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_connector_helper_add(&rgb->connector, + &atmel_hlcdc_rgb_connector_helper_funcs); + rgb->connector.dpms = DRM_MODE_DPMS_OFF; + rgb->connector.polled = DRM_CONNECTOR_POLL_CONNECT; + + drm_encoder_init(dev, &rgb->encoder, &atmel_hlcdc_rgb_encoder_funcs, + DRM_MODE_ENCODER_LVDS); + drm_encoder_helper_add(&rgb->encoder, + &atmel_hlcdc_rgb_encoder_helper_funcs); + + drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder); + drm_sysfs_connector_add(&rgb->connector); + + rgb->encoder.possible_crtcs = 0x1; + + return 0; +} + +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; +} diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c new file mode 100644 index 0000000..d42cd73 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c @@ -0,0 +1,804 @@ +/* + * 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 "atmel_hlcdc_dc.h" + +#define SUBPIXEL_MASK 0xffff + +static uint32_t rgb_formats[] = { + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGBA8888, +}; + +struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats = { + .formats = rgb_formats, + .nformats = ARRAY_SIZE(rgb_formats), +}; + +static uint32_t rgb_and_yuv_formats[] = { + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_AYUV, + DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY, + DRM_FORMAT_YVYU, + DRM_FORMAT_VYUY, + DRM_FORMAT_NV21, + DRM_FORMAT_NV61, + DRM_FORMAT_YUV422, + DRM_FORMAT_YUV420, +}; + +struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats = { + .formats = rgb_and_yuv_formats, + .nformats = ARRAY_SIZE(rgb_and_yuv_formats), +}; + +static int atmel_hlcdc_format_to_plane_mode(u32 format, u32 *mode) +{ + switch (format) { + case DRM_FORMAT_XRGB4444: + *mode = ATMEL_HLCDC_XRGB4444_MODE; + break; + case DRM_FORMAT_ARGB4444: + *mode = ATMEL_HLCDC_ARGB4444_MODE; + break; + case DRM_FORMAT_RGBA4444: + *mode = ATMEL_HLCDC_RGBA4444_MODE; + break; + case DRM_FORMAT_RGB565: + *mode = ATMEL_HLCDC_RGB565_MODE; + break; + case DRM_FORMAT_RGB888: + *mode = ATMEL_HLCDC_RGB888_MODE; + break; + case DRM_FORMAT_ARGB1555: + *mode = ATMEL_HLCDC_ARGB1555_MODE; + break; + case DRM_FORMAT_XRGB8888: + *mode = ATMEL_HLCDC_XRGB8888_MODE; + break; + case DRM_FORMAT_ARGB8888: + *mode = ATMEL_HLCDC_ARGB8888_MODE; + break; + case DRM_FORMAT_RGBA8888: + *mode = ATMEL_HLCDC_RGBA8888_MODE; + break; + case DRM_FORMAT_AYUV: + *mode = ATMEL_HLCDC_AYUV_MODE; + break; + case DRM_FORMAT_YUYV: + *mode = ATMEL_HLCDC_YUYV_MODE; + break; + case DRM_FORMAT_UYVY: + *mode = ATMEL_HLCDC_UYVY_MODE; + break; + case DRM_FORMAT_YVYU: + *mode = ATMEL_HLCDC_YVYU_MODE; + break; + case DRM_FORMAT_VYUY: + *mode = ATMEL_HLCDC_VYUY_MODE; + break; + case DRM_FORMAT_NV21: + *mode = ATMEL_HLCDC_NV21_MODE; + break; + case DRM_FORMAT_NV61: + *mode = ATMEL_HLCDC_NV61_MODE; + break; + case DRM_FORMAT_YUV420: + *mode = ATMEL_HLCDC_YUV420_MODE; + break; + case DRM_FORMAT_YUV422: + *mode = ATMEL_HLCDC_YUV422_MODE; + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static bool atmel_hlcdc_format_embedds_alpha(u32 format) +{ + int i; + + for (i = 0; i < sizeof(format); i++) { + char tmp = (format >> (8 * i)) & 0xff; + + if (tmp == 'A') + return true; + } + + return false; +} + +static u32 heo_downscaling_xcoef[] = { + 0x11343311, + 0x000000f7, + 0x1635300c, + 0x000000f9, + 0x1b362c08, + 0x000000fb, + 0x1f372804, + 0x000000fe, + 0x24382400, + 0x00000000, + 0x28371ffe, + 0x00000004, + 0x2c361bfb, + 0x00000008, + 0x303516f9, + 0x0000000c, +}; + +static u32 heo_downscaling_ycoef[] = { + 0x00123737, + 0x00173732, + 0x001b382d, + 0x001f3928, + 0x00243824, + 0x0028391f, + 0x002d381b, + 0x00323717, +}; + +static u32 heo_upscaling_xcoef[] = { + 0xf74949f7, + 0x00000000, + 0xf55f33fb, + 0x000000fe, + 0xf5701efe, + 0x000000ff, + 0xf87c0dff, + 0x00000000, + 0x00800000, + 0x00000000, + 0x0d7cf800, + 0x000000ff, + 0x1e70f5ff, + 0x000000fe, + 0x335ff5fe, + 0x000000fb, +}; + +static u32 heo_upscaling_ycoef[] = { + 0x00004040, + 0x00075920, + 0x00056f0c, + 0x00027b03, + 0x00008000, + 0x00037b02, + 0x000c6f05, + 0x00205907, +}; + +static void +atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_update_req *req) +{ + const struct atmel_hlcdc_layer_cfg_layout *layout = + &plane->layer.desc->layout; + + if (layout->size) + atmel_hlcdc_layer_update_cfg(&plane->layer, + layout->size, + 0xffffffff, + (req->crtc_w - 1) | + ((req->crtc_h - 1) << 16)); + + if (layout->memsize) + atmel_hlcdc_layer_update_cfg(&plane->layer, + layout->memsize, + 0xffffffff, + (req->src_w - 1) | + ((req->src_h - 1) << 16)); + + if (layout->pos) + atmel_hlcdc_layer_update_cfg(&plane->layer, + layout->pos, + 0xffffffff, + req->crtc_x | + (req->crtc_y << 16)); + + /* TODO: rework the rescaling part */ + if (req->crtc_w != req->src_w || req->crtc_h != req->src_h) { + u32 factor_reg = 0; + + if (req->crtc_w != req->src_w) { + int i; + u32 factor; + u32 *coeff_tab = heo_upscaling_xcoef; + u32 max_memsize; + + if (req->crtc_w < req->src_w) + coeff_tab = heo_downscaling_xcoef; + for (i = 0; i < ARRAY_SIZE(heo_upscaling_xcoef); i++) + atmel_hlcdc_layer_update_cfg(&plane->layer, + 17 + i, + 0xffffffff, + coeff_tab[i]); + factor = ((8 * 256 * req->src_w) - (256 * 4)) / + req->crtc_w; + factor++; + max_memsize = ((factor * req->crtc_w) + (256 * 4)) / + 2048; + if (max_memsize > req->src_w) + factor--; + factor_reg |= factor | 0x80000000; + } + + if (req->crtc_h != req->src_h) { + int i; + u32 factor; + u32 *coeff_tab = heo_upscaling_ycoef; + u32 max_memsize; + + if (req->crtc_w < req->src_w) + coeff_tab = heo_downscaling_ycoef; + for (i = 0; i < ARRAY_SIZE(heo_upscaling_ycoef); i++) + atmel_hlcdc_layer_update_cfg(&plane->layer, + 33 + i, + 0xffffffff, + coeff_tab[i]); + factor = ((8 * 256 * req->src_w) - (256 * 4)) / + req->crtc_w; + factor++; + max_memsize = ((factor * req->crtc_w) + (256 * 4)) / + 2048; + if (max_memsize > req->src_w) + factor--; + factor_reg |= (factor << 16) | 0x80000000; + } + + atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff, + factor_reg); + } +} + +static void +atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_update_req *req) +{ + const struct atmel_hlcdc_layer_cfg_layout *layout = + &plane->layer.desc->layout; + unsigned int cfg = ATMEL_HLCDC_LAYER_DMA; + + if (plane->base.type != DRM_PLANE_TYPE_PRIMARY) { + cfg |= ATMEL_HLCDC_LAYER_OVR | ATMEL_HLCDC_LAYER_ITER2BL | + ATMEL_HLCDC_LAYER_ITER; + + if (atmel_hlcdc_format_embedds_alpha(req->fb->pixel_format)) + cfg |= ATMEL_HLCDC_LAYER_LAEN; + else + cfg |= ATMEL_HLCDC_LAYER_GAEN; + } + + atmel_hlcdc_layer_update_cfg(&plane->layer, layout->general_config, + ATMEL_HLCDC_LAYER_ITER2BL | + ATMEL_HLCDC_LAYER_ITER | + ATMEL_HLCDC_LAYER_GAEN | + ATMEL_HLCDC_LAYER_LAEN | + ATMEL_HLCDC_LAYER_OVR | + ATMEL_HLCDC_LAYER_DMA, cfg); +} + +static void atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_update_req *req) +{ + u32 mode; + int ret; + + ret = atmel_hlcdc_format_to_plane_mode(req->fb->pixel_format, &mode); + if (ret) + return; + + atmel_hlcdc_layer_update_cfg(&plane->layer, + ATMEL_HLCDC_LAYER_FORMAT_CFG_ID, + 0xffffffff, + mode); +} + +static void atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_update_req *req) +{ + struct atmel_hlcdc_layer *layer = &plane->layer; + const struct atmel_hlcdc_layer_cfg_layout *layout = + &layer->desc->layout; + int i; + + atmel_hlcdc_layer_update_set_fb(&plane->layer, req->fb, req->offsets); + + for (i = 0; i < req->nplanes; i++) { + if (layout->xstride[i]) { + atmel_hlcdc_layer_update_cfg(&plane->layer, + layout->xstride[i], + 0xffffffff, + req->xstride[i]); + } + + if (layout->pstride[i]) { + atmel_hlcdc_layer_update_cfg(&plane->layer, + layout->pstride[i], + 0xffffffff, + req->pstride[i]); + } + } +} + +static int atmel_hlcdc_plane_check_update_req(struct drm_plane *p, + struct atmel_hlcdc_plane_update_req *req) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + const struct atmel_hlcdc_layer_cfg_layout *layout = + &plane->layer.desc->layout; + + if (!layout->size && + (req->crtc->mode.crtc_hdisplay != req->crtc_w || + req->crtc->mode.crtc_vdisplay != req->crtc_h)) + return -EINVAL; + + if (plane->layer.desc->max_height && + req->crtc_h > plane->layer.desc->max_height) + return -EINVAL; + + if (plane->layer.desc->max_width && + req->crtc_w > plane->layer.desc->max_width) + return -EINVAL; + + if ((req->crtc_h != req->src_h || req->crtc_w != req->src_w) && + (!layout->memsize || + atmel_hlcdc_format_embedds_alpha(req->fb->pixel_format))) + return -EINVAL; + + return 0; +} + +int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p, + struct atmel_hlcdc_plane_update_req *req) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + unsigned int patched_crtc_w; + unsigned int patched_crtc_h; + unsigned int patched_src_w; + unsigned int patched_src_h; + unsigned int tmp; + int x_offset = 0; + int y_offset = 0; + int i; + + if ((req->src_x | req->src_y | req->src_w | req->src_h) & + SUBPIXEL_MASK) + return -EINVAL; + + req->src_x >>= 16; + req->src_y >>= 16; + req->src_w >>= 16; + req->src_h >>= 16; + + req->nplanes = drm_format_num_planes(req->fb->pixel_format); + if (req->nplanes > ATMEL_HLCDC_MAX_PLANES) + return -EINVAL; + + /* + * Swap width and size if in case of 90 or 270 degrees rotation + */ + if (plane->rotation == ATMEL_HLCDC_PLANE_90DEG_ROTATION || + plane->rotation == ATMEL_HLCDC_PLANE_270DEG_ROTATION) { + tmp = req->crtc_w; + req->crtc_w = req->crtc_h; + req->crtc_h = tmp; + tmp = req->src_w; + req->src_w = req->src_h; + req->src_h = tmp; + } + + if (req->crtc_x + req->crtc_w > req->crtc->mode.hdisplay) + patched_crtc_w = req->crtc->mode.hdisplay - req->crtc_x; + else + patched_crtc_w = req->crtc_w; + + if (req->crtc_x < 0) { + patched_crtc_w += req->crtc_x; + x_offset = -req->crtc_x; + req->crtc_x = 0; + } + + if (req->crtc_y + req->crtc_h > req->crtc->mode.vdisplay) + patched_crtc_h = req->crtc->mode.vdisplay - req->crtc_y; + else + patched_crtc_h = req->crtc_h; + + if (req->crtc_y < 0) { + patched_crtc_h += req->crtc_y; + y_offset = -req->crtc_y; + req->crtc_y = 0; + } + + patched_src_w = DIV_ROUND_CLOSEST(patched_crtc_w * req->src_w, + req->crtc_w); + patched_src_h = DIV_ROUND_CLOSEST(patched_crtc_h * req->src_h, + req->crtc_h); + + for (i = 0; i < req->nplanes; i++) { + unsigned int offset = 0; + + req->bpp[i] = drm_format_plane_cpp(req->fb->pixel_format, i); + if (!req->bpp[i]) + return -EINVAL; + + switch (plane->rotation) { + case ATMEL_HLCDC_PLANE_90DEG_ROTATION: + offset = (y_offset + req->src_y + patched_src_w - 1) * + req->fb->pitches[i]; + offset += (x_offset + req->src_x) * req->bpp[i]; + req->xstride[i] = req->fb->pitches[i] * + (patched_src_w - 1); + req->pstride[i] = -req->fb->pitches[i] - req->bpp[i]; + break; + case ATMEL_HLCDC_PLANE_180DEG_ROTATION: + offset = (y_offset + req->src_y + patched_src_h - 1) * + req->fb->pitches[i]; + offset += (x_offset + req->src_x + patched_src_w) * + req->bpp[i]; + req->xstride[i] = ((patched_src_w - 2) * req->bpp[i]) - + req->fb->pitches[i]; + req->pstride[i] = -2 * req->bpp[i]; + break; + case ATMEL_HLCDC_PLANE_270DEG_ROTATION: + offset = (y_offset + req->src_y) * req->fb->pitches[i]; + offset += (x_offset + req->src_x + patched_src_h) * + req->bpp[i]; + req->xstride[i] = -(req->fb->pitches[i] * + (patched_src_w - 1)) - + (2 * req->bpp[i]); + req->pstride[i] = req->fb->pitches[i] - req->bpp[i]; + break; + case ATMEL_HLCDC_PLANE_NO_ROTATION: + default: + offset = (y_offset + req->src_y) * req->fb->pitches[i]; + offset += (x_offset + req->src_x) * req->bpp[i]; + req->xstride[i] = req->fb->pitches[i] - + (patched_src_w * req->bpp[i]); + req->pstride[i] = 0; + break; + } + + req->offsets[i] = offset + req->fb->offsets[i]; + } + + req->src_w = patched_src_w; + req->src_h = patched_src_h; + req->crtc_w = patched_crtc_w; + req->crtc_h = patched_crtc_h; + + return atmel_hlcdc_plane_check_update_req(p, req); +} + +int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p, + struct atmel_hlcdc_plane_update_req *req) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + int ret; + + ret = atmel_hlcdc_layer_update_start(&plane->layer); + if (ret) + return ret; + + atmel_hlcdc_plane_update_pos_and_size(plane, req); + atmel_hlcdc_plane_update_general_settings(plane, req); + atmel_hlcdc_plane_update_format(plane, req); + atmel_hlcdc_plane_update_buffers(plane, req); + atmel_hlcdc_layer_update_set_finished(&plane->layer, req->finished, + req->finished_data); + + atmel_hlcdc_layer_update_commit(&plane->layer); + + return 0; +} + +static int atmel_hlcdc_plane_update(struct drm_plane *p, + struct drm_crtc *crtc, + struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + struct atmel_hlcdc_plane_update_req req; + int ret = 0; + + memset(&req, 0, sizeof(req)); + req.crtc_x = crtc_x; + req.crtc_y = crtc_y; + req.crtc_w = crtc_w; + req.crtc_h = crtc_h; + req.src_x = src_x; + req.src_y = src_y; + req.src_w = src_w; + req.src_h = src_h; + req.fb = fb; + req.crtc = crtc; + + ret = atmel_hlcdc_plane_prepare_update_req(&plane->base, &req); + if (ret) + return ret; + + if (!req.crtc_h || !req.crtc_w) + return atmel_hlcdc_layer_disable(&plane->layer); + + return atmel_hlcdc_plane_apply_update_req(&plane->base, &req); +} + +static int atmel_hlcdc_plane_disable(struct drm_plane *p) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + + return atmel_hlcdc_layer_disable(&plane->layer); +} + +static void atmel_hlcdc_plane_destroy(struct drm_plane *p) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + + if (plane->base.fb) + drm_framebuffer_unreference(plane->base.fb); + + atmel_hlcdc_layer_cleanup(p->dev, &plane->layer); + + drm_plane_cleanup(p); + devm_kfree(p->dev->dev, plane); +} + +static int atmel_hlcdc_plane_set_alpha(struct atmel_hlcdc_plane *plane, + u8 alpha) +{ + atmel_hlcdc_layer_update_start(&plane->layer); + atmel_hlcdc_layer_update_cfg(&plane->layer, + plane->layer.desc->layout.general_config, + ATMEL_HLCDC_LAYER_GA_MASK, + alpha << ATMEL_HLCDC_LAYER_GA_SHIFT); + atmel_hlcdc_layer_update_commit(&plane->layer); + + return 0; +} + +static int atmel_hlcdc_plane_set_rotation(struct atmel_hlcdc_plane *plane, + enum atmel_hlcdc_plane_rotation rot) +{ + plane->rotation = rot; + + return 0; +} + +static int atmel_hlcdc_plane_set_property(struct drm_plane *p, + struct drm_property *property, + uint64_t value) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + struct atmel_hlcdc_plane_properties *props = plane->properties; + + if (property == props->alpha) + atmel_hlcdc_plane_set_alpha(plane, value); + else if (property == props->rotation) + atmel_hlcdc_plane_set_rotation(plane, value); + else + return -EINVAL; + + return 0; +} + +static void atmel_hlcdc_plane_init_properties(struct atmel_hlcdc_plane *plane, + const struct atmel_hlcdc_layer_desc *desc, + struct atmel_hlcdc_plane_properties *props) +{ + struct regmap *regmap = plane->layer.hlcdc->regmap; + + if (desc->type == ATMEL_HLCDC_OVERLAY_LAYER || + desc->type == ATMEL_HLCDC_CURSOR_LAYER) { + drm_object_attach_property(&plane->base.base, + props->alpha, 255); + + /* Set default alpha value */ + regmap_update_bits(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_GENERAL_CFG(&plane->layer), + ATMEL_HLCDC_LAYER_GA_MASK, + ATMEL_HLCDC_LAYER_GA_MASK); + } + + if (desc->layout.xstride && desc->layout.pstride) + drm_object_attach_property(&plane->base.base, + props->rotation, + ATMEL_HLCDC_PLANE_NO_ROTATION); + + if (desc->layout.csc) { + /* + * TODO: decare a "yuv-to-rgb-conv-factors" property to let + * userspace modify these factors (using a BLOB property ?). + */ + regmap_write(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 0), + 0x4c900091); + regmap_write(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 1), + 0x7a5f5090); + regmap_write(regmap, + desc->regs_offset + + ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 2), + 0x40040890); + } +} + +static struct drm_plane_funcs layer_plane_funcs = { + .update_plane = atmel_hlcdc_plane_update, + .disable_plane = atmel_hlcdc_plane_disable, + .set_property = atmel_hlcdc_plane_set_property, + .destroy = atmel_hlcdc_plane_destroy, +}; + +static struct atmel_hlcdc_plane * +atmel_hlcdc_plane_create(struct drm_device *dev, + const struct atmel_hlcdc_layer_desc *desc, + struct atmel_hlcdc_plane_properties *props) +{ + struct atmel_hlcdc_plane *plane; + enum drm_plane_type type; + int ret; + + plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + ret = atmel_hlcdc_layer_init(dev, &plane->layer, desc); + if (ret) + return ERR_PTR(ret); + + if (desc->type == ATMEL_HLCDC_BASE_LAYER) + type = DRM_PLANE_TYPE_PRIMARY; + else if (desc->type == ATMEL_HLCDC_CURSOR_LAYER) + type = DRM_PLANE_TYPE_CURSOR; + else + type = DRM_PLANE_TYPE_OVERLAY; + + ret = drm_universal_plane_init(dev, &plane->base, 0, + &layer_plane_funcs, + desc->formats->formats, + desc->formats->nformats, type); + if (ret) + return ERR_PTR(ret); + + /* Set default property values*/ + atmel_hlcdc_plane_init_properties(plane, desc, props); + + return plane; +} + +static struct atmel_hlcdc_plane_properties * +atmel_hlcdc_plane_create_properties(struct drm_device *dev) +{ + struct atmel_hlcdc_plane_properties *props; + const struct drm_prop_enum_list rotations[] = { + { ATMEL_HLCDC_PLANE_NO_ROTATION, "rotate-0" }, + { ATMEL_HLCDC_PLANE_90DEG_ROTATION, "rotate-90" }, + { ATMEL_HLCDC_PLANE_180DEG_ROTATION, "rotate-180" }, + { ATMEL_HLCDC_PLANE_270DEG_ROTATION, "rotate-270" }, + }; + + props = devm_kzalloc(dev->dev, sizeof(*props), GFP_KERNEL); + if (!props) + return ERR_PTR(-ENOMEM); + + props->alpha = drm_property_create_range(dev, 0, "alpha", 0, 255); + if (!props->alpha) + return ERR_PTR(-ENOMEM); + + props->rotation = drm_property_create_enum(dev, 0, "rotation", + rotations, + ARRAY_SIZE(rotations)); + return props; +} + +struct atmel_hlcdc_planes * +atmel_hlcdc_create_planes(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_plane_properties *props; + struct atmel_hlcdc_planes *planes; + const struct atmel_hlcdc_layer_desc *descs = dc->desc->layers; + int nlayers = dc->desc->nlayers; + int i; + + planes = devm_kzalloc(dev->dev, sizeof(*planes), GFP_KERNEL); + if (!planes) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < nlayers; i++) { + if (descs[i].type == ATMEL_HLCDC_OVERLAY_LAYER) + planes->noverlays++; + } + + if (planes->noverlays) { + planes->overlays = devm_kzalloc(dev->dev, + planes->noverlays * + sizeof(*planes->overlays), + GFP_KERNEL); + if (!planes->overlays) + return ERR_PTR(-ENOMEM); + } + + props = atmel_hlcdc_plane_create_properties(dev); + if (IS_ERR(props)) + return ERR_CAST(props); + + planes->noverlays = 0; + for (i = 0; i < nlayers; i++) { + struct atmel_hlcdc_plane *plane; + + if (descs[i].type == ATMEL_HLCDC_PP_LAYER) + continue; + + plane = atmel_hlcdc_plane_create(dev, &descs[i], props); + if (IS_ERR(plane)) + return ERR_CAST(plane); + + plane->properties = props; + + switch (descs[i].type) { + case ATMEL_HLCDC_BASE_LAYER: + if (planes->primary) + return ERR_PTR(-EINVAL); + planes->primary = plane; + break; + + case ATMEL_HLCDC_OVERLAY_LAYER: + planes->overlays[planes->noverlays++] = plane; + break; + + case ATMEL_HLCDC_CURSOR_LAYER: + if (planes->cursor) + return ERR_PTR(-EINVAL); + planes->cursor = plane; + break; + + default: + break; + } + } + + return planes; +}
The Atmel HLCDC (HLCD Controller) IP available on some Atmel SoCs (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display controller device.
The HLCDC block provides a single RGB output port, and only supports LCD panels connection to LCD panels for now.
The atmel,panel property link the HLCDC RGB output with the LCD panel connected on this port (note that the HLCDC RGB connector implementation makes use of the DRM panel framework).
Connection to other external devices (DRM bridges) might be added later by mean of a new atmel,xxx (atmel,bridge) property.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- .../devicetree/bindings/drm/atmel-hlcdc-dc.txt | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt
diff --git a/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt b/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt new file mode 100644 index 0000000..73b9860 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt @@ -0,0 +1,54 @@ +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) DRM driver + +The Atmel HLCDC Display Controller is subdevice of the HLCDC MFD device. +See ../mfd/atmel-hlcdc.txt for more details. + +Required properties: + - compatible: value should be "atmel,hlcdc-display-controller" + - interrupts: the HLCDC interrupt definition + - pinctrl-names: the pin control state names. Should contain "default". + - pinctrl-0: should contain the default pinctrl states. + - #address-cells: should be set to 1. + - #size-cells: should be set to 0. + +Required children nodes: + Children nodes are encoding available output ports and their connections + to external devices using the OF graph reprensentation (see ../graph.txt). + At least one port node is required. + +Example: + + hlcdc: hlcdc@f0030000 { + compatible = "atmel,sama5d3-hlcdc"; + reg = <0xf0030000 0x2000>; + clocks = <&lcdc_clk>, <&lcdck>, <&clk32k>; + clock-names = "periph_clk","sys_clk", "slow_clk"; + status = "disabled"; + + hlcdc-display-controller { + compatible = "atmel,hlcdc-display-controller"; + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>; + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + hlcdc_panel_output: endpoint@0 { + reg = <0>; + remote-endpoint = <&panel_input>; + }; + }; + }; + + hlcdc_pwm: hlcdc-pwm { + compatible = "atmel,hlcdc-pwm"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd_pwm>; + #pwm-cells = <3>; + }; + };
The HLCDC (HLCD Controller) IP supports 4 different output mode (RGB444, RGB565, RGB666 and RGB888) and the pin muxing will depend on the chosen RGB mode.
Split pin definitions to be able to set pin config according to the selected mode.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- arch/arm/boot/dts/sama5d3_lcd.dtsi | 127 ++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 31 deletions(-)
diff --git a/arch/arm/boot/dts/sama5d3_lcd.dtsi b/arch/arm/boot/dts/sama5d3_lcd.dtsi index 85d3027..2186b89 100644 --- a/arch/arm/boot/dts/sama5d3_lcd.dtsi +++ b/arch/arm/boot/dts/sama5d3_lcd.dtsi @@ -15,38 +15,103 @@ apb { pinctrl@fffff200 { lcd { - pinctrl_lcd: lcd-0 { + pinctrl_lcd_pwm: lcd-pwm-0 { + atmel,pins = <AT91_PIOA 24 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* LCDPWM */ + }; + + pinctrl_lcd_base: lcd-base-0 { + atmel,pins = + <AT91_PIOA 26 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDVSYNC */ + AT91_PIOA 27 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDHSYNC */ + AT91_PIOA 25 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDDISP */ + AT91_PIOA 29 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDDEN */ + AT91_PIOA 28 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* LCDPCK */ + }; + + pinctrl_lcd_rgb444: lcd-rgb-0 { + atmel,pins = + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD0 pin */ + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD1 pin */ + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD2 pin */ + AT91_PIOA 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD3 pin */ + AT91_PIOA 4 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD4 pin */ + AT91_PIOA 5 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD5 pin */ + AT91_PIOA 6 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD6 pin */ + AT91_PIOA 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD7 pin */ + AT91_PIOA 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD8 pin */ + AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD9 pin */ + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD10 pin */ + AT91_PIOA 11 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* LCDD11 pin */ + }; + + pinctrl_lcd_rgb565: lcd-rgb-1 { + atmel,pins = + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD0 pin */ + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD1 pin */ + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD2 pin */ + AT91_PIOA 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD3 pin */ + AT91_PIOA 4 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD4 pin */ + AT91_PIOA 5 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD5 pin */ + AT91_PIOA 6 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD6 pin */ + AT91_PIOA 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD7 pin */ + AT91_PIOA 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD8 pin */ + AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD9 pin */ + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD10 pin */ + AT91_PIOA 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD11 pin */ + AT91_PIOA 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD12 pin */ + AT91_PIOA 13 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD13 pin */ + AT91_PIOA 14 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD14 pin */ + AT91_PIOA 15 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* LCDD15 pin */ + }; + + pinctrl_lcd_rgb666: lcd-rgb-2 { + atmel,pins = + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD0 pin */ + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD1 pin */ + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD2 pin */ + AT91_PIOA 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD3 pin */ + AT91_PIOA 4 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD4 pin */ + AT91_PIOA 5 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD5 pin */ + AT91_PIOA 6 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD6 pin */ + AT91_PIOA 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD7 pin */ + AT91_PIOA 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD8 pin */ + AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD9 pin */ + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD10 pin */ + AT91_PIOA 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD11 pin */ + AT91_PIOA 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD12 pin */ + AT91_PIOA 13 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD13 pin */ + AT91_PIOA 14 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD14 pin */ + AT91_PIOA 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD15 pin */ + AT91_PIOC 14 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD16 pin */ + AT91_PIOC 13 AT91_PERIPH_C AT91_PINCTRL_NONE>; /* LCDD17 pin */ + }; + + pinctrl_lcd_rgb888: lcd-rgb-3 { atmel,pins = - <AT91_PIOA 24 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA24 periph A LCDPWM */ - AT91_PIOA 26 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA26 periph A LCDVSYNC */ - AT91_PIOA 27 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA27 periph A LCDHSYNC */ - AT91_PIOA 25 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA25 periph A LCDDISP */ - AT91_PIOA 29 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA29 periph A LCDDEN */ - AT91_PIOA 28 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA28 periph A LCDPCK */ - AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA0 periph A LCDD0 pin */ - AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA1 periph A LCDD1 pin */ - AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA2 periph A LCDD2 pin */ - AT91_PIOA 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA3 periph A LCDD3 pin */ - AT91_PIOA 4 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA4 periph A LCDD4 pin */ - AT91_PIOA 5 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA5 periph A LCDD5 pin */ - AT91_PIOA 6 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA6 periph A LCDD6 pin */ - AT91_PIOA 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA7 periph A LCDD7 pin */ - AT91_PIOA 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA8 periph A LCDD8 pin */ - AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA9 periph A LCDD9 pin */ - AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA10 periph A LCDD10 pin */ - AT91_PIOA 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA11 periph A LCDD11 pin */ - AT91_PIOA 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA12 periph A LCDD12 pin */ - AT91_PIOA 13 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA13 periph A LCDD13 pin */ - AT91_PIOA 14 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA14 periph A LCDD14 pin */ - AT91_PIOA 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* PA15 periph A LCDD15 pin */ - AT91_PIOC 14 AT91_PERIPH_C AT91_PINCTRL_NONE /* PC14 periph C LCDD16 pin */ - AT91_PIOC 13 AT91_PERIPH_C AT91_PINCTRL_NONE /* PC13 periph C LCDD17 pin */ - AT91_PIOC 12 AT91_PERIPH_C AT91_PINCTRL_NONE /* PC12 periph C LCDD18 pin */ - AT91_PIOC 11 AT91_PERIPH_C AT91_PINCTRL_NONE /* PC11 periph C LCDD19 pin */ - AT91_PIOC 10 AT91_PERIPH_C AT91_PINCTRL_NONE /* PC10 periph C LCDD20 pin */ - AT91_PIOC 15 AT91_PERIPH_C AT91_PINCTRL_NONE /* PC15 periph C LCDD21 pin */ - AT91_PIOE 27 AT91_PERIPH_C AT91_PINCTRL_NONE /* PE27 periph C LCDD22 pin */ - AT91_PIOE 28 AT91_PERIPH_C AT91_PINCTRL_NONE>; /* PE28 periph C LCDD23 pin */ + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD0 pin */ + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD1 pin */ + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD2 pin */ + AT91_PIOA 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD3 pin */ + AT91_PIOA 4 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD4 pin */ + AT91_PIOA 5 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD5 pin */ + AT91_PIOA 6 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD6 pin */ + AT91_PIOA 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD7 pin */ + AT91_PIOA 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD8 pin */ + AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD9 pin */ + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD10 pin */ + AT91_PIOA 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD11 pin */ + AT91_PIOA 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD12 pin */ + AT91_PIOA 13 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD13 pin */ + AT91_PIOA 14 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD14 pin */ + AT91_PIOA 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD15 pin */ + AT91_PIOC 14 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD16 pin */ + AT91_PIOC 13 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD17 pin */ + AT91_PIOC 12 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD18 pin */ + AT91_PIOC 11 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD19 pin */ + AT91_PIOC 10 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD20 pin */ + AT91_PIOC 15 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD21 pin */ + AT91_PIOE 27 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD22 pin */ + AT91_PIOE 28 AT91_PERIPH_C AT91_PINCTRL_NONE>; /* LCDD23 pin */ }; }; };
Define alternative pin muxing for the LCDC pins.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- arch/arm/boot/dts/sama5d3_lcd.dtsi | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d3_lcd.dtsi b/arch/arm/boot/dts/sama5d3_lcd.dtsi index 2186b89..e7581f6 100644 --- a/arch/arm/boot/dts/sama5d3_lcd.dtsi +++ b/arch/arm/boot/dts/sama5d3_lcd.dtsi @@ -82,6 +82,28 @@ AT91_PIOA 13 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD13 pin */ AT91_PIOA 14 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD14 pin */ AT91_PIOA 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD15 pin */ + AT91_PIOA 16 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD16 pin */ + AT91_PIOA 17 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* LCDD17 pin */ + }; + + pinctrl_lcd_rgb666_alt: lcd-rgb-2-alt { + atmel,pins = + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD0 pin */ + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD1 pin */ + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD2 pin */ + AT91_PIOA 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD3 pin */ + AT91_PIOA 4 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD4 pin */ + AT91_PIOA 5 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD5 pin */ + AT91_PIOA 6 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD6 pin */ + AT91_PIOA 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD7 pin */ + AT91_PIOA 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD8 pin */ + AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD9 pin */ + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD10 pin */ + AT91_PIOA 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD11 pin */ + AT91_PIOA 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD12 pin */ + AT91_PIOA 13 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD13 pin */ + AT91_PIOA 14 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD14 pin */ + AT91_PIOA 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD15 pin */ AT91_PIOC 14 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD16 pin */ AT91_PIOC 13 AT91_PERIPH_C AT91_PINCTRL_NONE>; /* LCDD17 pin */ }; @@ -104,6 +126,34 @@ AT91_PIOA 13 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD13 pin */ AT91_PIOA 14 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD14 pin */ AT91_PIOA 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD15 pin */ + AT91_PIOA 16 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD16 pin */ + AT91_PIOA 17 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD17 pin */ + AT91_PIOA 18 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD18 pin */ + AT91_PIOA 19 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD19 pin */ + AT91_PIOA 20 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD20 pin */ + AT91_PIOA 21 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD21 pin */ + AT91_PIOA 22 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD22 pin */ + AT91_PIOA 23 AT91_PERIPH_A AT91_PINCTRL_NONE>; /* LCDD23 pin */ + }; + + pinctrl_lcd_rgb888_alt: lcd-rgb-3-alt { + atmel,pins = + <AT91_PIOA 0 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD0 pin */ + AT91_PIOA 1 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD1 pin */ + AT91_PIOA 2 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD2 pin */ + AT91_PIOA 3 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD3 pin */ + AT91_PIOA 4 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD4 pin */ + AT91_PIOA 5 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD5 pin */ + AT91_PIOA 6 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD6 pin */ + AT91_PIOA 7 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD7 pin */ + AT91_PIOA 8 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD8 pin */ + AT91_PIOA 9 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD9 pin */ + AT91_PIOA 10 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD10 pin */ + AT91_PIOA 11 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD11 pin */ + AT91_PIOA 12 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD12 pin */ + AT91_PIOA 13 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD13 pin */ + AT91_PIOA 14 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD14 pin */ + AT91_PIOA 15 AT91_PERIPH_A AT91_PINCTRL_NONE /* LCDD15 pin */ AT91_PIOC 14 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD16 pin */ AT91_PIOC 13 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD17 pin */ AT91_PIOC 12 AT91_PERIPH_C AT91_PINCTRL_NONE /* LCDD18 pin */
Define the HLCDC (HLCD Controller) IP available on some sama5d3 SoCs (i.e. sama5d31, sama5d33, sama5d34 and sama5d36) in sama5d3 dtsi file.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- arch/arm/boot/dts/sama5d3_lcd.dtsi | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d3_lcd.dtsi b/arch/arm/boot/dts/sama5d3_lcd.dtsi index e7581f6..1874cf7 100644 --- a/arch/arm/boot/dts/sama5d3_lcd.dtsi +++ b/arch/arm/boot/dts/sama5d3_lcd.dtsi @@ -166,6 +166,34 @@ }; };
+ hlcdc: hlcdc@f0030000 { + compatible = "atmel,sama5d3-hlcdc"; + reg = <0xf0030000 0x2000>; + clocks = <&lcdc_clk>, <&lcdck>, <&clk32k>; + clock-names = "periph_clk","sys_clk", "slow_clk"; + status = "disabled"; + + hlcdc-display-controller { + compatible = "atmel,hlcdc-display-controller"; + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>; + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + }; + }; + + hlcdc_pwm: hlcdc-pwm { + compatible = "atmel,hlcdc-pwm"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd_pwm>; + #pwm-cells = <3>; + }; + }; + pmc: pmc@fffffc00 { periphck { lcdc_clk: lcdc_clk {
Add LCD panel related nodes (backlight, regulators and panel) to sama5d3 Display Module dtsi.
Reference LCD pin muxing used by sama5d3xek boards.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- arch/arm/boot/dts/sama5d3xdm.dtsi | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d3xdm.dtsi b/arch/arm/boot/dts/sama5d3xdm.dtsi index 035ab72..91975eb 100644 --- a/arch/arm/boot/dts/sama5d3xdm.dtsi +++ b/arch/arm/boot/dts/sama5d3xdm.dtsi @@ -36,6 +36,64 @@ }; }; }; + + hlcdc: hlcdc@f0030000 { + hlcdc-display-controller { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888_alt>; + + port@0 { + hlcdc_panel_output: endpoint@0 { + reg = <0>; + remote-endpoint = <&panel_input>; + }; + }; + }; + }; + }; + }; + + bl_reg: backlight_regulator { + compatible = "regulator-fixed"; + regulator-name = "backlight-power-supply"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + status = "disabled"; + }; + + panel_reg: panel_regulator { + compatible = "regulator-fixed"; + regulator-name = "panel-power-supply"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + status = "disabled"; + }; + + backlight: backlight { + compatible = "pwm-backlight"; + pwms = <&hlcdc_pwm 0 50000 0>; + brightness-levels = <0 4 8 16 32 64 128 255>; + default-brightness-level = <6>; + 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>; + }; }; }; };
Enable LCD related nodes.
Signed-off-by: Boris BREZILLON boris.brezillon@free-electrons.com --- arch/arm/boot/dts/sama5d31ek.dts | 20 ++++++++++++++++++++ arch/arm/boot/dts/sama5d33ek.dts | 20 ++++++++++++++++++++ arch/arm/boot/dts/sama5d34ek.dts | 20 ++++++++++++++++++++ arch/arm/boot/dts/sama5d36ek.dts | 20 ++++++++++++++++++++ 4 files changed, 80 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d31ek.dts b/arch/arm/boot/dts/sama5d31ek.dts index 04eec0d..6e605fe 100644 --- a/arch/arm/boot/dts/sama5d31ek.dts +++ b/arch/arm/boot/dts/sama5d31ek.dts @@ -33,6 +33,10 @@ status = "okay"; };
+ hlcdc: hlcdc@f0030000 { + status = "okay"; + }; + macb1: ethernet@f802c000 { status = "okay"; }; @@ -46,6 +50,22 @@ }; };
+ bl_reg: backlight_regulator { + status = "okay"; + }; + + panel_reg: panel_regulator { + status = "okay"; + }; + + backlight: backlight { + status = "okay"; + }; + + panel: panel { + status = "okay"; + }; + sound { status = "okay"; }; diff --git a/arch/arm/boot/dts/sama5d33ek.dts b/arch/arm/boot/dts/sama5d33ek.dts index cbd6a3f..0400641 100644 --- a/arch/arm/boot/dts/sama5d33ek.dts +++ b/arch/arm/boot/dts/sama5d33ek.dts @@ -36,9 +36,29 @@ macb0: ethernet@f0028000 { status = "okay"; }; + + hlcdc: hlcdc@f0030000 { + status = "okay"; + }; }; };
+ bl_reg: backlight_regulator { + status = "okay"; + }; + + panel_reg: panel_regulator { + status = "okay"; + }; + + backlight: backlight { + status = "okay"; + }; + + panel: panel { + status = "okay"; + }; + sound { status = "okay"; }; diff --git a/arch/arm/boot/dts/sama5d34ek.dts b/arch/arm/boot/dts/sama5d34ek.dts index 878aa16..9cf473e 100644 --- a/arch/arm/boot/dts/sama5d34ek.dts +++ b/arch/arm/boot/dts/sama5d34ek.dts @@ -46,6 +46,10 @@ macb0: ethernet@f0028000 { status = "okay"; }; + + hlcdc: hlcdc@f0030000 { + status = "okay"; + }; }; };
@@ -56,6 +60,22 @@ }; };
+ bl_reg: backlight_regulator { + status = "okay"; + }; + + panel_reg: panel_regulator { + status = "okay"; + }; + + backlight: backlight { + status = "okay"; + }; + + panel: panel { + status = "okay"; + }; + sound { status = "okay"; }; diff --git a/arch/arm/boot/dts/sama5d36ek.dts b/arch/arm/boot/dts/sama5d36ek.dts index 59576c6..1c65741 100644 --- a/arch/arm/boot/dts/sama5d36ek.dts +++ b/arch/arm/boot/dts/sama5d36ek.dts @@ -41,12 +41,32 @@ status = "okay"; };
+ hlcdc: hlcdc@f0030000 { + status = "okay"; + }; + macb1: ethernet@f802c000 { status = "okay"; }; }; };
+ bl_reg: backlight_regulator { + status = "okay"; + }; + + panel_reg: panel_regulator { + status = "okay"; + }; + + backlight: backlight { + status = "okay"; + }; + + panel: panel { + status = "okay"; + }; + sound { status = "okay"; };
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Regards
Ludovic
On Tue, Jul 22, 2014 at 03:11:24PM +0200, Boris BREZILLON wrote:
Hello,
This patch series adds support for Atmel HLCDC (HLCD Controller) available on some Atmel SoCs (i.e. the sama5d3 family).
The HLCDC actually provides a Display Controller and a PWM device, hence I decided to declare an MFD device exposing 2 subdevices: a display controller and a PWM chip. This also solves a circular dependency issue preventing HLCDC driver from unloading. The HLCDC request a drm_panel device, which request a backlight device (a PWM backlight), which depends on a PWM which is provided by the HLCDC driver (hlcdc -> panel -> backlight -> hlcdc (pwm part)).
The current implementation only supports sama5d3 SoCs but other SoCs should be easily ported by defining new compatible strings and adding HLCDC description structures for these SoCs.
The drivers supports basic CRTC functionalities, several overlays and an hardware cursor.
At the moment, it only supports connection to LCD panels through an RGB connector (defined as an LVDS connector in my implementation), though connection to other kind of devices (like DRM bridges) could be added later.
It also supports several RGB formats on all planes and some YUV formats on the HEO overlay plane.
This series depends on 2 other series currently under review: [1] and [2].
Best Regards,
Boris
[1]http://lkml.iu.edu/hypermail/linux/kernel/1407.1/04171.html [2]http://www.spinics.net/lists/kernel/msg1791681.html
Changes since v3:
- rework the layer code to simplify several parts (locking and layer disabling)
- make use of the drm_flip_work infrastructure
- rely on default HW cursor implementation using on the cursor plane
- rework the display controller DT bindings (based on OF graph representation)
- add rotation support
- retrive RGB bus format from drm_display_info
- drop the dynamic pinctrl state selection
- rework HLCDC output handling (previously specialized to interface with LCD panels)
- drop ".module = THIS_MODULE" lines
- change display controller compatible string
Changes since v2:
- fix coding style issues (macro indentation)
- make use of GENMASK in several places
- declare regmap config as a static structure
- rework hlcdc plane update API
- rework cursor handling to make use of the new plane update API
- fix backporch config
- do not use devm_regmap_init_mmio_clk to avoid extra clk_enable clk disable calls when accessing registers
- explicitely include regmap and clk headers instead of relying on atmel-hlcdc.h inclusions
- make the atmel-hlcdc driver depends on CONFIG_OF
- separate DT bindings documentation from driver implementation
- support several pin muxing for HLCDC pins on sama5d3 SoCs
Changes since v1:
- replace the backlight driver by a PWM driver
- make use of drm_panel infrastructure
- split driver code in several subsystem: MFD, PWM and DRM
- add support for overlays
- add support for hardware cursor
Boris BREZILLON (11): mfd: add atmel-hlcdc driver mfd: add documentation for atmel-hlcdc DT bindings pwm: add support for atmel-hlcdc-pwm device pwm: add DT bindings documentation for atmel-hlcdc-pwm driver drm: add Atmel HLCDC Display Controller support drm: add DT bindings documentation for atmel-hlcdc-dc driver ARM: AT91/dt: split sama5d3 lcd pin definitions to match RGB mode configs ARM: AT91/dt: add alternative pin muxing for sama5d3 lcd pins ARM: at91/dt: define the HLCDC node available on sama5d3 SoCs ARM: at91/dt: add LCD panel description to sama5d3xdm.dtsi ARM: at91/dt: enable the LCD panel on sama5d3xek boards
.../devicetree/bindings/drm/atmel-hlcdc-dc.txt | 54 ++ .../devicetree/bindings/mfd/atmel-hlcdc.txt | 50 ++ .../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 55 ++ arch/arm/boot/dts/sama5d31ek.dts | 20 + arch/arm/boot/dts/sama5d33ek.dts | 20 + arch/arm/boot/dts/sama5d34ek.dts | 20 + arch/arm/boot/dts/sama5d36ek.dts | 20 + arch/arm/boot/dts/sama5d3_lcd.dtsi | 205 +++++- arch/arm/boot/dts/sama5d3xdm.dtsi | 58 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/atmel-hlcdc/Kconfig | 11 + drivers/gpu/drm/atmel-hlcdc/Makefile | 7 + drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 286 ++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 488 +++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 224 ++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 635 ++++++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 396 ++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c | 478 ++++++++++++ drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 804 +++++++++++++++++++++ drivers/mfd/Kconfig | 12 + drivers/mfd/Makefile | 1 + drivers/mfd/atmel-hlcdc.c | 118 +++ drivers/pwm/Kconfig | 9 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-atmel-hlcdc.c | 229 ++++++ include/linux/mfd/atmel-hlcdc.h | 78 ++ 27 files changed, 4251 insertions(+), 31 deletions(-) create mode 100644 Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt create mode 100644 drivers/gpu/drm/atmel-hlcdc/Kconfig create mode 100644 drivers/gpu/drm/atmel-hlcdc/Makefile create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c create mode 100644 drivers/mfd/atmel-hlcdc.c create mode 100644 drivers/pwm/pwm-atmel-hlcdc.c create mode 100644 include/linux/mfd/atmel-hlcdc.h
-- 1.8.3.2
linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Best Regards,
Boris
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed. That will still cause some delay before everything gets set up, but hopefully less than what you're seeing now. There's also another thread where this is being discussed because deferred probing is causing "unacceptable" delays as well.
Thierry
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
That will still cause some delay before everything gets set up, but hopefully less than what you're seeing now. There's also another thread where this is being discussed because deferred probing is causing "unacceptable" delays as well.
Could you point this thread out to me please ?
Best Regards,
Boris
On Thu, 21 Aug 2014 11:41:59 +0200 Boris BREZILLON boris.brezillon@free-electrons.com wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
That will still cause some delay before everything gets set up, but hopefully less than what you're seeing now. There's also another thread where this is being discussed because deferred probing is causing "unacceptable" delays as well.
Could you point this thread out to me please ?
Sorry, I didn't check my emails before asking this :-).
Best Regards,
Boris
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread). You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
That will still cause some delay before everything gets set up, but hopefully less than what you're seeing now. There's also another thread where this is being discussed because deferred probing is causing "unacceptable" delays as well.
Could you point this thread out to me please ?
I Cc'ed you on it.
Thierry
On 08/21/2014 11:52 AM, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread). You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
I have tested panel as a module in exynos-dsi + panel-s6e8aa0 configuration, everything works. There is a workaround for fb console not being reconfigurable, but it does not make thing worse than before. And I do not see a problem with phandles, ie in DT they point both ways, according to binding advices at the time, but in the code it is display controller/encoder which is looking for the panel.
Regards Andrzej
That will still cause some delay before everything gets set up, but hopefully less than what you're seeing now. There's also another thread where this is being discussed because deferred probing is causing "unacceptable" delays as well.
Could you point this thread out to me please ?
I Cc'ed you on it.
Thierry
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Aug 21, 2014 at 12:32:43PM +0200, Andrzej Hajda wrote:
On 08/21/2014 11:52 AM, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread). You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
I have tested panel as a module in exynos-dsi + panel-s6e8aa0 configuration, everything works. There is a workaround for fb console not being reconfigurable, but it does not make thing worse than before. And I do not see a problem with phandles, ie in DT they point both ways, according to binding advices at the time, but in the code it is display controller/encoder which is looking for the panel.
That works because it's DSI. And we have attach/detach callbacks for DSI. We don't have those for regular panels, so we'd need to find a way to add that.
The way that this currently works is that an encoder/connector driver looks up the panel and attaches it to itself. If you allow panels to be hotpluggable, then they have no knowledge about what they are connected to, so there needs to be a way to inject that knowledge so that they can attach to a connector.
Thierry
On 08/21/2014 03:21 PM, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 12:32:43PM +0200, Andrzej Hajda wrote:
On 08/21/2014 11:52 AM, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
> Hi Boris, > > You can add > > Tested-by: Ludovic Desroches ludovic.desroches@atmel.com Thanks for testing this driver.
> Only one issue but not related to your patches, you can't display > quickly the bootup logo since the panel detection takes too much > time. Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread). You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
I have tested panel as a module in exynos-dsi + panel-s6e8aa0 configuration, everything works. There is a workaround for fb console not being reconfigurable, but it does not make thing worse than before. And I do not see a problem with phandles, ie in DT they point both ways, according to binding advices at the time, but in the code it is display controller/encoder which is looking for the panel.
That works because it's DSI. And we have attach/detach callbacks for DSI. We don't have those for regular panels, so we'd need to find a way to add that.
Maybe I have misread your answer, but you showed it as very difficult/painful process: "hotplugging panel dance", "fix a bunch of things in DRM". In fact we are missing here only good notifications about panel appearance.
The way that this currently works is that an encoder/connector driver looks up the panel and attaches it to itself. If you allow panels to be hotpluggable, then they have no knowledge about what they are connected to, so there needs to be a way to inject that knowledge so that they can attach to a connector.
I do not understand that. Currently it is the connector who looks for the panel and attaches it. So the scenario, after adding panel tracking, could be: - encoder parses its phandle to panel, and start tracking appearance of the panel identified by this phandle, - when panel appears encoder callback is called, and encoder attaches the panel, - when panel wants to disappear encoder callback is called, encoder detaches the panel.
All this I have already presented together with generic interface tracker [1].
Regards Andrzej
[1]: https://lkml.org/lkml/2014/4/30/345
Thierry
On Thu, 21 Aug 2014 17:04:34 +0200 Andrzej Hajda a.hajda@samsung.com wrote:
On 08/21/2014 03:21 PM, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 12:32:43PM +0200, Andrzej Hajda wrote:
On 08/21/2014 11:52 AM, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote: > Hi Ludovic, > > On Thu, 21 Aug 2014 10:16:19 +0200 > Ludovic Desroches ludovic.desroches@atmel.com wrote: > >> Hi Boris, >> >> You can add >> >> Tested-by: Ludovic Desroches ludovic.desroches@atmel.com > Thanks for testing this driver. > >> Only one issue but not related to your patches, you can't display >> quickly the bootup logo since the panel detection takes too much >> time. > Yes, actually this is related to the device probe order: the > hlcdc-display-controller device is probed before the simple-panel, thus > nothing is detected on the RGB connector (I use of_drm_find_panel to > check for panel availability) when the display controller is > instantiated. I rely on the default polling infrastructure provided by > the DRM/KMS framework which polls for a new connector every 10s, and > this is far more than you kernel boot time. > > Do anyone see a solution to reduce this delay (without changing the > polling interval). I thought we could add a notifier infrastructure to > the DRM panel framework, but I'm not sure this is how you want things > done... Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread). You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
I have tested panel as a module in exynos-dsi + panel-s6e8aa0 configuration, everything works. There is a workaround for fb console not being reconfigurable, but it does not make thing worse than before. And I do not see a problem with phandles, ie in DT they point both ways, according to binding advices at the time, but in the code it is display controller/encoder which is looking for the panel.
That works because it's DSI. And we have attach/detach callbacks for DSI. We don't have those for regular panels, so we'd need to find a way to add that.
Maybe I have misread your answer, but you showed it as very difficult/painful process: "hotplugging panel dance", "fix a bunch of things in DRM". In fact we are missing here only good notifications about panel appearance.
The way that this currently works is that an encoder/connector driver looks up the panel and attaches it to itself. If you allow panels to be hotpluggable, then they have no knowledge about what they are connected to, so there needs to be a way to inject that knowledge so that they can attach to a connector.
I do not understand that. Currently it is the connector who looks for the panel and attaches it. So the scenario, after adding panel tracking, could be:
- encoder parses its phandle to panel, and start tracking appearance of
the panel identified by this phandle,
- when panel appears encoder callback is called, and encoder attaches
the panel,
- when panel wants to disappear encoder callback is called, encoder
detaches the panel.
All this I have already presented together with generic interface tracker [1].
Well, your attempt at doing a generic tracker framework sounds interesting, but given the answer you've got from greg-kh and Russel, I'd say this patch series is in a dead-end (unless there are other versions I haven't seen yet).
How about implementing a specific notifier interface for the drm_panel framework first, and move to your generic implementation if it gets accepted.
These are the two proposal I sent to Thierry:
http://code.bulix.org/scq4g3-86804 (v1)
and
http://code.bulix.org/7urh8v-86806 (v2)
Feel free to propose any alternative to those implementations.
Best Regards,
Boris
On 08/21/2014 05:30 PM, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 17:04:34 +0200 Andrzej Hajda a.hajda@samsung.com wrote:
On 08/21/2014 03:21 PM, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 12:32:43PM +0200, Andrzej Hajda wrote:
On 08/21/2014 11:52 AM, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
> On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote: >> Hi Ludovic, >> >> On Thu, 21 Aug 2014 10:16:19 +0200 >> Ludovic Desroches ludovic.desroches@atmel.com wrote: >> >>> Hi Boris, >>> >>> You can add >>> >>> Tested-by: Ludovic Desroches ludovic.desroches@atmel.com >> Thanks for testing this driver. >> >>> Only one issue but not related to your patches, you can't display >>> quickly the bootup logo since the panel detection takes too much >>> time. >> Yes, actually this is related to the device probe order: the >> hlcdc-display-controller device is probed before the simple-panel, thus >> nothing is detected on the RGB connector (I use of_drm_find_panel to >> check for panel availability) when the display controller is >> instantiated. I rely on the default polling infrastructure provided by >> the DRM/KMS framework which polls for a new connector every 10s, and >> this is far more than you kernel boot time. >> >> Do anyone see a solution to reduce this delay (without changing the >> polling interval). I thought we could add a notifier infrastructure to >> the DRM panel framework, but I'm not sure this is how you want things >> done... > Other drivers return -EPROBE_DEFER when a panel hasn't been registered > yet. This will automatically take care of ordering things in a way that > DRM/KMS will only be initialized after the panel has been probed. Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread). You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
I have tested panel as a module in exynos-dsi + panel-s6e8aa0 configuration, everything works. There is a workaround for fb console not being reconfigurable, but it does not make thing worse than before. And I do not see a problem with phandles, ie in DT they point both ways, according to binding advices at the time, but in the code it is display controller/encoder which is looking for the panel.
That works because it's DSI. And we have attach/detach callbacks for DSI. We don't have those for regular panels, so we'd need to find a way to add that.
Maybe I have misread your answer, but you showed it as very difficult/painful process: "hotplugging panel dance", "fix a bunch of things in DRM". In fact we are missing here only good notifications about panel appearance.
The way that this currently works is that an encoder/connector driver looks up the panel and attaches it to itself. If you allow panels to be hotpluggable, then they have no knowledge about what they are connected to, so there needs to be a way to inject that knowledge so that they can attach to a connector.
I do not understand that. Currently it is the connector who looks for the panel and attaches it. So the scenario, after adding panel tracking, could be:
- encoder parses its phandle to panel, and start tracking appearance of
the panel identified by this phandle,
- when panel appears encoder callback is called, and encoder attaches
the panel,
- when panel wants to disappear encoder callback is called, encoder
detaches the panel.
All this I have already presented together with generic interface tracker [1].
Well, your attempt at doing a generic tracker framework sounds interesting, but given the answer you've got from greg-kh and Russel, I'd say this patch series is in a dead-end (unless there are other versions I haven't seen yet).
As I remember I have positively answered Russel's concerns. Greg had only concern in style 'why another framework'.
How about implementing a specific notifier interface for the drm_panel framework first, and move to your generic implementation if it gets accepted.
The interface tracker can be also used for that as well, ie it can be added to drm_panel framework, until it wont be accepted for wider set of clients.
These are the two proposal I sent to Thierry:
http://code.bulix.org/scq4g3-86804 (v1)
and
http://code.bulix.org/7urh8v-86806 (v2)
Feel free to propose any alternative to those implementations.
Best Regards,
Boris
After quick look at the patches I guess they will not work correctly if panel will be added before the observer registration. Btw in case of other proposals could you include them in the mail, my company firewall does not like http://code.bulix.org/.
Regards Andrzej
On Thu, 21 Aug 2014 11:52:03 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Let me know if you want me to submit a proper patch series...
Best Regards,
Boris
[1]http://code.bulix.org/scq4g3-86804 [2]http://code.bulix.org/7dg501-86805
On Thu, Aug 21, 2014 at 03:06:00PM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:52:03 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Yeah, I guess that's one way to do it. But it's tricky to get right when you have several outputs. Which one should be considered the primary and trigger fbdev creation?
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Let me know if you want me to submit a proper patch series...
Best Regards,
Boris
[1]http://code.bulix.org/scq4g3-86804 [2]http://code.bulix.org/7dg501-86805
Those look interesting. Any chance you could look into how to do the same without resorting to notifiers?
Thierry
On Thu, 21 Aug 2014 15:16:08 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 03:06:00PM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:52:03 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
> Hi Boris, > > You can add > > Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
> > Only one issue but not related to your patches, you can't display > quickly the bootup logo since the panel detection takes too much > time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Yeah, I guess that's one way to do it. But it's tricky to get right when you have several outputs. Which one should be considered the primary and trigger fbdev creation?
I take the first valid one :D (which indeed is not really reliable when you have several output devices). I guess marking one output as the primary output in the DT is not an acceptable solution either because it describes a configuration rather than an HW capability.
Still opened to any suggestions regarding this issue ;-).
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Let me know if you want me to submit a proper patch series...
Best Regards,
Boris
[1]http://code.bulix.org/scq4g3-86804 [2]http://code.bulix.org/7dg501-86805
Those look interesting. Any chance you could look into how to do the same without resorting to notifiers?
Sure, it should be pretty easy too.
I also had a look at the component framework, but I'm not sure it applies in this context. AFAIU it is designed to delay a master device registration until all it's slaves are ready to act upon.
On Thu, 21 Aug 2014 15:16:08 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 03:06:00PM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:52:03 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
> Hi Boris, > > You can add > > Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
> > Only one issue but not related to your patches, you can't display > quickly the bootup logo since the panel detection takes too much > time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Yeah, I guess that's one way to do it. But it's tricky to get right when you have several outputs. Which one should be considered the primary and trigger fbdev creation?
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Let me know if you want me to submit a proper patch series...
Best Regards,
Boris
[1]http://code.bulix.org/scq4g3-86804 [2]http://code.bulix.org/7dg501-86805
Those look interesting. Any chance you could look into how to do the same without resorting to notifiers?
A new version without notifiers usage: http://code.bulix.org/7urh8v-86806
On Thu, Aug 21, 2014 at 03:16:08PM +0200, Thierry Reding wrote:
On Thu, Aug 21, 2014 at 03:06:00PM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:52:03 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
> Hi Boris, > > You can add > > Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
> > Only one issue but not related to your patches, you can't display > quickly the bootup logo since the panel detection takes too much > time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Yeah, I guess that's one way to do it. But it's tricky to get right when you have several outputs. Which one should be considered the primary and trigger fbdev creation?
We could just reallocate the fbdev backing storage (probably only increase it for safety since fbdev is bonghits) when new outputs show up. There has been (and maybe still is) some provisions in the fbdev helper library to do just that.
Mostly it would mean to split out drm_fb_helper_single_fb_probe so that drivers could call it from their hotplug work. And then adjust the ->fb_probe callback of drivers which do this to reallocate the fbdev buffer if it's only a resize. Overall this shouldn't be too much fuzz to get going. Of course only as an opt-in, but imo that's the only sane way to do this anyway. -Daniel
Hi Boris,
On Thursday 21 August 2014 15:06:00 Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:52:03 +0200 Thierry Reding wrote:
On Thu, Aug 21, 2014 at 11:41:59AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Given the move to multiplatform kernels we need to aim for as few modules compiled in as possible. I'd say this includes HDMI encoders, panels and display controllers.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Do you create a new drm_encoder at runtime for the HDMI encoder when it appears ? I thought the DRM core and API were not able to correctly cope with that.
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Is there a way we could use the component framework for that ? I know that partial notification isn't supported at the moment, but Russell agreed it was a real use case that should be implemented at some point.
Let me know if you want me to submit a proper patch series...
Best Regards,
Boris
[1]http://code.bulix.org/scq4g3-86804 [2]http://code.bulix.org/7dg501-86805
Hi Laurent,
On Thu, 21 Aug 2014 19:08:53 +0200 Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
[...]
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Given the move to multiplatform kernels we need to aim for as few modules compiled in as possible. I'd say this includes HDMI encoders, panels and display controllers.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Do you create a new drm_encoder at runtime for the HDMI encoder when it appears ? I thought the DRM core and API were not able to correctly cope with that.
I haven't started to work on the HDMI encoder yet, and ATM I only have a single connector (which is true from an HW POV), which is then bound to an LCD panel (the only type of remote endpoint I currently support).
BTW, I wonder how my use case should be represented in the DRM subsystem. As I said, from an HW POV I only have one RGB (or whatever name you choose for it) connector. But on such kind of connectors you can connect several output devices (panels, encoders, ...). And in my case I have 2 devices on the same RGB connector: a panel and an RGB to HDMI converter.
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Is there a way we could use the component framework for that ? I know that partial notification isn't supported at the moment, but Russell agreed it was a real use case that should be implemented at some point.
I'll give it a try.
Best Regards,
Boris
Hi Boris,
On Thursday 21 August 2014 19:26:33 Boris BREZILLON wrote:
On Thu, 21 Aug 2014 19:08:53 +0200 Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
[...]
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Given the move to multiplatform kernels we need to aim for as few modules compiled in as possible. I'd say this includes HDMI encoders, panels and display controllers.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Do you create a new drm_encoder at runtime for the HDMI encoder when it appears ? I thought the DRM core and API were not able to correctly cope with that.
I haven't started to work on the HDMI encoder yet, and ATM I only have a single connector (which is true from an HW POV), which is then bound to an LCD panel (the only type of remote endpoint I currently support).
BTW, I wonder how my use case should be represented in the DRM subsystem. As I said, from an HW POV I only have one RGB (or whatever name you choose for it) connector. But on such kind of connectors you can connect several output devices (panels, encoders, ...). And in my case I have 2 devices on the same RGB connector: a panel and an RGB to HDMI converter.
The DRM connector object was initially meant to model a physical user- accessible connector on a board (VGA, DVI, HDMI, ...) and the properties of the monitor plugged into it. It has then been (ab)used to represent panels, as they're similar to monitors.
In your case the VGA and HDMI connectors should be modeled as DRM connectors, the RGB to HDMI encoder as a DRM encoder, and the LCDC as a DRM CRTC.
As DRM hardcodes the pipeline model to CRTC -> encoder -> connector, you will also need a DRM encoder in the VGA path. I suppose your board has a VGA DAC, that's the component you should expose as a DRM encoder (even if it can't be controlled and doesn't limit the valid modes).
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Is there a way we could use the component framework for that ? I know that partial notification isn't supported at the moment, but Russell agreed it was a real use case that should be implemented at some point.
I'll give it a try.
Hi Laurent,
On Tue, 26 Aug 2014 01:39:21 +0200 Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
Hi Boris,
On Thursday 21 August 2014 19:26:33 Boris BREZILLON wrote:
On Thu, 21 Aug 2014 19:08:53 +0200 Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
[...]
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Given the move to multiplatform kernels we need to aim for as few modules compiled in as possible. I'd say this includes HDMI encoders, panels and display controllers.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Do you create a new drm_encoder at runtime for the HDMI encoder when it appears ? I thought the DRM core and API were not able to correctly cope with that.
I haven't started to work on the HDMI encoder yet, and ATM I only have a single connector (which is true from an HW POV), which is then bound to an LCD panel (the only type of remote endpoint I currently support).
BTW, I wonder how my use case should be represented in the DRM subsystem. As I said, from an HW POV I only have one RGB (or whatever name you choose for it) connector. But on such kind of connectors you can connect several output devices (panels, encoders, ...). And in my case I have 2 devices on the same RGB connector: a panel and an RGB to HDMI converter.
The DRM connector object was initially meant to model a physical user- accessible connector on a board (VGA, DVI, HDMI, ...) and the properties of the monitor plugged into it. It has then been (ab)used to represent panels, as they're similar to monitors.
In your case the VGA and HDMI connectors should be modeled as DRM connectors, the RGB to HDMI encoder as a DRM encoder, and the LCDC as a DRM CRTC.
I don't have any VGA connector (or I'm missing something :-)), but I have an LCD panel and an RGB to HDMI encoder connected on the same RGB connector.
As DRM hardcodes the pipeline model to CRTC -> encoder -> connector, you will also need a DRM encoder in the VGA path. I suppose your board has a VGA DAC, that's the component you should expose as a DRM encoder (even if it can't be controlled and doesn't limit the valid modes).
Actually, my problem is that both devices are connected on the same RGB connector, and thus share the same display mode (resolution, HSYNC, VSYNC, RGB output mode, ...). This means that all remote devices have to agree on a specific mode if we want to mirror the display on several output devices, otherwise we must disable one of the output devices.
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Is there a way we could use the component framework for that ? I know that partial notification isn't supported at the moment, but Russell agreed it was a real use case that should be implemented at some point.
I'll give it a try.
Hi Boris,
On Wednesday 27 August 2014 09:52:35 Boris BREZILLON wrote:
On Tue, 26 Aug 2014 01:39:21 +0200 Laurent Pinchart wrote:
On Thursday 21 August 2014 19:26:33 Boris BREZILLON wrote:
On Thu, 21 Aug 2014 19:08:53 +0200 Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
[...]
> While this could be acceptable when all drivers are statically > linked in the kernel, it might be problematic when you're using > modules, meaning that you won't be able to display anything on your > LCD panel until your HDMI bridge module has been loaded.
No. HDMI should be using proper hotplugging anyway, hence it should be always be loaded anyway. You're in for a world of pain if you think you can run DRM with a driver that's composed of separate kernel modules.
I was talking about the external RGB to HDMI encoder, should the driver for this encoder (which is not on On Chip block) be compiled statically too ?
Given the move to multiplatform kernels we need to aim for as few modules compiled in as possible. I'd say this includes HDMI encoders, panels and display controllers.
Also if you don't want to use deferred probe, then you're in for the full hotplugging panel dance and that implies that you need to fix a bunch of things in DRM (one being the framebuffer console instantiation that I referred to in the other thread).
For now, I wait until there is a device connected on the RGB connector (connector status set to connector_status_connected) before creating an fbdev. It might not be the cleanest way to solve this issue, but it works :-).
Do you create a new drm_encoder at runtime for the HDMI encoder when it appears ? I thought the DRM core and API were not able to correctly cope with that.
I haven't started to work on the HDMI encoder yet, and ATM I only have a single connector (which is true from an HW POV), which is then bound to an LCD panel (the only type of remote endpoint I currently support).
BTW, I wonder how my use case should be represented in the DRM subsystem. As I said, from an HW POV I only have one RGB (or whatever name you choose for it) connector. But on such kind of connectors you can connect several output devices (panels, encoders, ...). And in my case I have 2 devices on the same RGB connector: a panel and an RGB to HDMI converter.
The DRM connector object was initially meant to model a physical user- accessible connector on a board (VGA, DVI, HDMI, ...) and the properties of the monitor plugged into it. It has then been (ab)used to represent panels, as they're similar to monitors.
In your case the VGA and HDMI connectors should be modeled as DRM connectors, the RGB to HDMI encoder as a DRM encoder, and the LCDC as a DRM CRTC.
I don't have any VGA connector (or I'm missing something :-)),
My bad.
but I have an LCD panel and an RGB to HDMI encoder connected on the same RGB connector.
There's no such thing as an RGB connector in DRM. Your SoC has a parallel RGB video output (I assume it's a DPI bus). From a DRM point of view, that bus corresponds to the output of the CRTC.
As DRM hardcodes the pipeline model to CRTC -> encoder -> connector, you will also need a DRM encoder in the VGA path. I suppose your board has a VGA DAC, that's the component you should expose as a DRM encoder (even if it can't be controlled and doesn't limit the valid modes).
Actually, my problem is that both devices are connected on the same RGB connector, and thus share the same display mode (resolution, HSYNC, VSYNC, RGB output mode, ...). This means that all remote devices have to agree on a specific mode if we want to mirror the display on several output devices, otherwise we must disable one of the output devices.
That's not really a problem. From a DRM perspective you need to model your device as
,------. ,---------------. ,-----------------. | CRTC | -+--> | Dummy Encoder | ----> | Panel Connector | `------´ | `---------------´ `-----------------´ | ,---------------. ,-----------------. --> | HDMI Encoder | ----> | HDMI Connector | `---------------´ `-----------------´
The HDMI pipeline is pretty straightforward.
You have told me that the panel has a parallel RGB input without any encoder in the panel pipeline (by the way, which panel model are you using ?). However, DRM requires an encoder in every pipeline. You will thus need to instantiate a dummy encoder. One option would be to set the encoder and connector types to DRM_MODE_ENCODER_LVDS and DRM_MODE_CONNECTOR_LVDS respectively, as that's what userspace usually expects for panels. That doesn't reflect the reality in your case though, so creating a new DRM_MODE_CONNECTOR_DPI type might be needed, possibly to be used with DRM_MODE_ENCODER_NONE.
As neither encoder can modify the mode, the same mode will be output on the two connectors.
You also can't be using the current device tree bindings because they all assume a dependency from the display controller/output to the panel. For hotplugging you'd need the dependency the other way around (the panel needs to refer to the output by phandle).
Here [1] is a proposal for notification support in the drm_panel infrastructure (which is not that complicated), and here [2] is how I use it in my atmel-hlcdc driver to generate hotplug events.
Is there a way we could use the component framework for that ? I know that partial notification isn't supported at the moment, but Russell agreed it was a real use case that should be implemented at some point.
I'll give it a try.
Hi Laurent,
On Thu, 28 Aug 2014 14:19:22 +0200 Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
Hi Boris,
[...]
I don't have any VGA connector (or I'm missing something :-)),
My bad.
No problem.
but I have an LCD panel and an RGB to HDMI encoder connected on the same RGB connector.
There's no such thing as an RGB connector in DRM. Your SoC has a parallel RGB video output (I assume it's a DPI bus). From a DRM point of view, that bus corresponds to the output of the CRTC.
Okay, this mean I'll have to dispatch some of the code I've put in atmel_hlcdc_output.c into atmel_hlcdc_crtc.c (BTW, any chance you could take a look at this files ?).
As DRM hardcodes the pipeline model to CRTC -> encoder -> connector, you will also need a DRM encoder in the VGA path. I suppose your board has a VGA DAC, that's the component you should expose as a DRM encoder (even if it can't be controlled and doesn't limit the valid modes).
Actually, my problem is that both devices are connected on the same RGB connector, and thus share the same display mode (resolution, HSYNC, VSYNC, RGB output mode, ...). This means that all remote devices have to agree on a specific mode if we want to mirror the display on several output devices, otherwise we must disable one of the output devices.
That's not really a problem. From a DRM perspective you need to model your device as
,------. ,---------------. ,-----------------. | CRTC | -+--> | Dummy Encoder | ----> | Panel Connector | `------´ | `---------------´ `-----------------´ | ,---------------. ,-----------------. --> | HDMI Encoder | ----> | HDMI Connector | `---------------´ `-----------------´
The HDMI pipeline is pretty straightforward.
You have told me that the panel has a parallel RGB input without any encoder in the panel pipeline (by the way, which panel model are you using ?). However, DRM requires an encoder in every pipeline. You will thus need to instantiate a dummy encoder. One option would be to set the encoder and connector types to DRM_MODE_ENCODER_LVDS and DRM_MODE_CONNECTOR_LVDS respectively, as that's what userspace usually expects for panels. That doesn't reflect the reality in your case though, so creating a new DRM_MODE_CONNECTOR_DPI type might be needed, possibly to be used with DRM_MODE_ENCODER_NONE.
As neither encoder can modify the mode, the same mode will be output on the two connectors.
There are still several things to I'd like to understand: 1) who's gonna configure the RGB bus output format (RGB444, RGB666, RGB888) which directly depends on the device connected on this bus: the CRTC or the dummy and HDMI encoders. 2) Where should the HDMI encoder/connector support be implemented: in drivers/gpu/drm/atmel-hlcdc, drivers/gpu/drm/bridge or somewhere else. My point is that I don't want to add specific support for the Sil902x transmitter chip in the hlcdc driver.
Sorry if these are silly questions, but I'm still trying to understand how my case should be modeled :-).
Best Regards,
Boris
Hi Boris,
On Thursday 28 August 2014 16:21:00 Boris BREZILLON wrote:
On Thu, 28 Aug 2014 14:19:22 +0200 Laurent Pinchart wrote:
Hi Boris,
[...]
I don't have any VGA connector (or I'm missing something :-)),
My bad.
No problem.
but I have an LCD panel and an RGB to HDMI encoder connected on the same RGB connector.
There's no such thing as an RGB connector in DRM. Your SoC has a parallel RGB video output (I assume it's a DPI bus). From a DRM point of view, that bus corresponds to the output of the CRTC.
Okay, this mean I'll have to dispatch some of the code I've put in atmel_hlcdc_output.c into atmel_hlcdc_crtc.c (BTW, any chance you could take a look at this files ?).
Not in the very near future I'm afraid, I'm moving to a new flat in a couple of days, that will keep me pretty busy. If nobody has reviewed your patches in a week from now feel free to ping me.
As DRM hardcodes the pipeline model to CRTC -> encoder -> connector, you will also need a DRM encoder in the VGA path. I suppose your board has a VGA DAC, that's the component you should expose as a DRM encoder (even if it can't be controlled and doesn't limit the valid modes).
Actually, my problem is that both devices are connected on the same RGB connector, and thus share the same display mode (resolution, HSYNC, VSYNC, RGB output mode, ...). This means that all remote devices have to agree on a specific mode if we want to mirror the display on several output devices, otherwise we must disable one of the output devices.
That's not really a problem. From a DRM perspective you need to model your device as
,------. ,---------------. ,-----------------. | CRTC | -+--> | Dummy Encoder | ----> | Panel Connector | `------´ | `---------------´ `-----------------´ | ,---------------. ,-----------------. --> | HDMI Encoder | ----> | HDMI Connector | `---------------´ `-----------------´
The HDMI pipeline is pretty straightforward.
You have told me that the panel has a parallel RGB input without any encoder in the panel pipeline (by the way, which panel model are you using ?). However, DRM requires an encoder in every pipeline. You will thus need to instantiate a dummy encoder. One option would be to set the encoder and connector types to DRM_MODE_ENCODER_LVDS and DRM_MODE_CONNECTOR_LVDS respectively, as that's what userspace usually expects for panels. That doesn't reflect the reality in your case though, so creating a new DRM_MODE_CONNECTOR_DPI type might be needed, possibly to be used with DRM_MODE_ENCODER_NONE.
As neither encoder can modify the mode, the same mode will be output on the two connectors.
There are still several things to I'd like to understand:
- who's gonna configure the RGB bus output format (RGB444, RGB666, RGB888) which directly depends on the device connected on this bus: the CRTC or the dummy and HDMI encoders.
Your mileage my vary, but in general I believe this should be the responsibility of the CRTC driver (the HLCDC driver in your case), from information it gets from DT and/or queries dynamically from the encoders at runtime.
- Where should the HDMI encoder/connector support be implemented: in drivers/gpu/drm/atmel-hlcdc, drivers/gpu/drm/bridge or somewhere else. My point is that I don't want to add specific support for the Sil902x transmitter chip in the hlcdc driver.
The HDMI encoder should definitely be handled by a standalone driver. We have two infrastructures for this at the moment, drm_bridge and drm_encoder_slave. I'd like to see them being merged. I need to implement support for an HDMI encoder as well, I'll see if I can give this a try.
Sorry if these are silly questions, but I'm still trying to understand how my case should be modeled :-).
As I don't have straightforward answers I won't consider the questions as silly :-)
On 08/21/2014 11:41 AM, Boris BREZILLON wrote:
On Thu, 21 Aug 2014 11:04:07 +0200 Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Aug 21, 2014 at 10:37:06AM +0200, Boris BREZILLON wrote:
Hi Ludovic,
On Thu, 21 Aug 2014 10:16:19 +0200 Ludovic Desroches ludovic.desroches@atmel.com wrote:
Hi Boris,
You can add
Tested-by: Ludovic Desroches ludovic.desroches@atmel.com
Thanks for testing this driver.
Only one issue but not related to your patches, you can't display quickly the bootup logo since the panel detection takes too much time.
Yes, actually this is related to the device probe order: the hlcdc-display-controller device is probed before the simple-panel, thus nothing is detected on the RGB connector (I use of_drm_find_panel to check for panel availability) when the display controller is instantiated. I rely on the default polling infrastructure provided by the DRM/KMS framework which polls for a new connector every 10s, and this is far more than you kernel boot time.
Do anyone see a solution to reduce this delay (without changing the polling interval). I thought we could add a notifier infrastructure to the DRM panel framework, but I'm not sure this is how you want things done...
Other drivers return -EPROBE_DEFER when a panel hasn't been registered yet. This will automatically take care of ordering things in a way that DRM/KMS will only be initialized after the panel has been probed.
Actually I'd like to avoid doing this with a deferred probe, because, AFAIU, the remote endpoint is not tightly linked with the display controller driver (I mean the display controller can still be initialized without having a display connected on it). Moreover the atmel dev kit I'm using has an HDMI bridge connected on the same RGB connector and I'd like to use it in a near future. Returning -EPROBE_DEFER in case of several devices connected on the same connector implies that I'll have to wait for all the remote end-points to be available before my display controller could be instantiated.
While this could be acceptable when all drivers are statically linked in the kernel, it might be problematic when you're using modules, meaning that you won't be able to display anything on your LCD panel until your HDMI bridge module has been loaded.
I agree that deferring whole drm initialization due to panel not being ready is not an optimal solution. Nice solution of this problem is with DSI panels, DSI bus infrastructure provides callbacks for DRM encoder which are called when DSI panel is ready. Simple panels are platform devices so they do not have such callbacks. To solve this in a generic way I have proposed framework for tracking interfaces[1], the series contains also solution for the same problem with exynos display controller[2].
Regards Andrzej
[1]: https://lkml.org/lkml/2014/4/30/345 [2]: https://lkml.org/lkml/2014/4/30/653
That will still cause some delay before everything gets set up, but hopefully less than what you're seeing now. There's also another thread where this is being discussed because deferred probing is causing "unacceptable" delays as well.
Could you point this thread out to me please ?
Best Regards,
Boris
dri-devel@lists.freedesktop.org