Hello,
This patch series adds support for Atmel HLCDC (High LCD 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 that exposes 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 support 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.
It alse supports several RGB format on all planes and some YUV formats on
the HEO overlay plane, though YUV formats have not been tested yet.
Best Regards,
Boris
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 (7):
mfd: add atmel-hlcdc driver
pwm: add support for atmel-hlcdc-pwm device
drm: add Atmel HLCDC Display Controller support
ARM: AT91/dt: split sama5d3 lcd pin definitions to match RGB mode
configs
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 | 59 ++
.../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++
.../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 40 ++
arch/arm/boot/dts/sama5d31ek.dts | 24 +
arch/arm/boot/dts/sama5d33ek.dts | 24 +
arch/arm/boot/dts/sama5d34ek.dts | 24 +
arch/arm/boot/dts/sama5d36ek.dts | 24 +
arch/arm/boot/dts/sama5d3_lcd.dtsi | 153 ++++-
arch/arm/boot/dts/sama5d3xdm.dtsi | 32 +
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 | 529 ++++++++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 477 ++++++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 178 ++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 701 +++++++++++++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 417 ++++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c | 351 +++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 658 +++++++++++++++++++
drivers/gpu/drm/atmel_hlcdc/Kconfig | 11 +
drivers/gpu/drm/atmel_hlcdc/Makefile | 8 +
drivers/mfd/Kconfig | 11 +
drivers/mfd/Makefile | 1 +
drivers/mfd/atmel-hlcdc.c | 116 ++++
drivers/pwm/Kconfig | 9 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-atmel-hlcdc.c | 216 +++++++
include/linux/mfd/atmel-hlcdc.h | 78 +++
29 files changed, 4173 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_panel.c
create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
create mode 100644 drivers/gpu/drm/atmel_hlcdc/Kconfig
create mode 100644 drivers/gpu/drm/atmel_hlcdc/Makefile
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
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 this PWM device.
Signed-off-by: Boris BREZILLON <[email protected]>
---
.../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 40 ++++
drivers/pwm/Kconfig | 9 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-atmel-hlcdc.c | 216 +++++++++++++++++++++
4 files changed, 266 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
create mode 100644 drivers/pwm/pwm-atmel-hlcdc.c
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..5e2ba87
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
@@ -0,0 +1,40 @@
+Device-Tree bindings for Atmel's HLCDC (High LCD Controller) PWM driver
+
+The Atmel HLCDC PWM is subdevice of the HLCDC MFD device.
+See Documentation/devicetree/bindings/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.
+
+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-dc";
+ interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
+ pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
+ pinctrl-0 = <&pinctrl_lcd_base>;
+ pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
+ pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
+ pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
+ pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
+ };
+
+ hlcdc_pwm: hlcdc-pwm {
+ compatible = "atmel,hlcdc-pwm";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_lcd_pwm>;
+ #pwm-cells = <3>;
+ };
+ };
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 5b34ff2..7186242 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 e57d2c3..a245519 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_BFIN) += pwm-bfin.o
obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c
new file mode 100644
index 0000000..080e43e
--- /dev/null
+++ b/drivers/pwm/pwm-atmel-hlcdc.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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/mfd/atmel-hlcdc.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define ATMEL_HLCDC_PWMCVAL_MASK (0xff << 8)
+#define ATMEL_HLCDC_PWMCVAL(x) (((x) & 0xff) << 8)
+#define ATMEL_HLCDC_PWMPOL BIT(4)
+#define ATMEL_HLCDC_PWMPS_MASK 0x7
+#define ATMEL_HLCDC_PWMPS_MAX 0x6
+#define ATMEL_HLCDC_PWMPS(x) ((x) & 0x7)
+
+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))
+ cpu_relax();
+
+ 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))
+ cpu_relax();
+}
+
+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;
+ int ret;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->hlcdc = dev_get_drvdata(dev->parent);
+ 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);
+
+ 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 <[email protected]>");
+MODULE_DESCRIPTION("Atmel HLCDC PWM driver");
+MODULE_LICENSE("GPL");
--
1.8.3.2
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
Add support for the MFD device which will just retrieve HLCDC clocks and
create a regmap so that subdevices can access the HLCDC register range
concurrently.
Signed-off-by: Boris BREZILLON <[email protected]>
---
.../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++++++++
drivers/mfd/Kconfig | 11 ++
drivers/mfd/Makefile | 1 +
drivers/mfd/atmel-hlcdc.c | 116 +++++++++++++++++++++
include/linux/mfd/atmel-hlcdc.h | 78 ++++++++++++++
5 files changed, 247 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
create mode 100644 drivers/mfd/atmel-hlcdc.c
create mode 100644 include/linux/mfd/atmel-hlcdc.h
diff --git a/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
new file mode 100644
index 0000000..f5b69cb
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
@@ -0,0 +1,41 @@
+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 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
+ - a Display Controller: see Documentation/devicetree/bindings/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-dc";
+ interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
+ pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
+ pinctrl-0 = <&pinctrl_lcd_base>;
+ pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
+ pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
+ pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
+ pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
+ };
+
+ hlcdc_pwm: hlcdc-pwm {
+ compatible = "atmel,hlcdc-pwm";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_lcd_pwm>;
+ #pwm-cells = <3>;
+ };
+ };
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index ee8204c..82777f6 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -59,6 +59,17 @@ 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 (High LCD Controller)"
+ select MFD_CORE
+ select REGMAP_MMIO
+ help
+ Choose this option if you have an ATMEL SoC with an HLCDC (High
+ LCD 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..e4636e8
--- /dev/null
+++ b/drivers/mfd/atmel-hlcdc.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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/mfd/atmel-hlcdc.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+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-dc",
+ },
+};
+
+static int atmel_hlcdc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct regmap_config config;
+ 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 functional 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 functional 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);
+ }
+
+ memset(&config, 0, sizeof(config));
+ config.reg_bits = 32;
+ config.val_bits = 32;
+ config.reg_stride = 4;
+ config.max_register = (resource_size(res) / 4) - 1;
+ hlcdc->regmap = devm_regmap_init_mmio_clk(dev, "periph_clk", regs,
+ &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);
+
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id atmel_hlcdc_match[] = {
+ { .compatible = "atmel,sama5d3-hlcdc" },
+ { },
+};
+
+static struct platform_driver atmel_hlcdc_driver = {
+ .probe = atmel_hlcdc_probe,
+ .remove = atmel_hlcdc_remove,
+ .driver = {
+ .name = "atmel-hlcdc",
+ .owner = THIS_MODULE,
+ .of_match_table = atmel_hlcdc_match,
+ },
+};
+module_platform_driver(atmel_hlcdc_driver);
+
+MODULE_ALIAS("platform:atmel-hlcdc");
+MODULE_AUTHOR("Boris Brezillon <[email protected]>");
+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..d7a5589
--- /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 <[email protected]>
+ *
+ * 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 0x300
+#define ATMEL_HLCDC_PP BIT(10)
+#define ATMEL_HLCDC_VSPSU BIT(12)
+#define ATMEL_HLCDC_VSPHO BIT(13)
+#define ATMEL_HLCDC_GUARDTIME_MASK 0x1f0000
+
+#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 (0xff << ATMEL_HLCDC_CLKDIV_SHFT)
+#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 */
--
1.8.3.2
Add LCD panel related nodes (backlight, regulators and panel) to the
sama5d3 Display Module dtsi.
Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/boot/dts/sama5d3xdm.dtsi | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d3xdm.dtsi b/arch/arm/boot/dts/sama5d3xdm.dtsi
index 035ab72..7eb849f 100644
--- a/arch/arm/boot/dts/sama5d3xdm.dtsi
+++ b/arch/arm/boot/dts/sama5d3xdm.dtsi
@@ -38,4 +38,36 @@
};
};
};
+
+ 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>;
+ status = "disabled";
+ };
};
--
1.8.3.2
Enable LCD related nodes and reference panel node in the hlcdc (High
LCD Controller) controller on sama5d3xek boards.
Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/boot/dts/sama5d31ek.dts | 24 ++++++++++++++++++++++++
arch/arm/boot/dts/sama5d33ek.dts | 24 ++++++++++++++++++++++++
arch/arm/boot/dts/sama5d34ek.dts | 24 ++++++++++++++++++++++++
arch/arm/boot/dts/sama5d36ek.dts | 24 ++++++++++++++++++++++++
4 files changed, 96 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d31ek.dts b/arch/arm/boot/dts/sama5d31ek.dts
index 04eec0d..338f4c3 100644
--- a/arch/arm/boot/dts/sama5d31ek.dts
+++ b/arch/arm/boot/dts/sama5d31ek.dts
@@ -33,6 +33,14 @@
status = "okay";
};
+ hlcdc: hlcdc@f0030000 {
+ status = "okay";
+
+ hlcdc-display-controller {
+ atmel,panel = <&panel 3 0>;
+ };
+ };
+
macb1: ethernet@f802c000 {
status = "okay";
};
@@ -46,6 +54,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..f2ab41d 100644
--- a/arch/arm/boot/dts/sama5d33ek.dts
+++ b/arch/arm/boot/dts/sama5d33ek.dts
@@ -36,9 +36,33 @@
macb0: ethernet@f0028000 {
status = "okay";
};
+
+ hlcdc: hlcdc@f0030000 {
+ status = "okay";
+
+ hlcdc-display-controller {
+ atmel,panel = <&panel 3 0>;
+ };
+ };
};
};
+ 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..0d0049c 100644
--- a/arch/arm/boot/dts/sama5d34ek.dts
+++ b/arch/arm/boot/dts/sama5d34ek.dts
@@ -46,6 +46,14 @@
macb0: ethernet@f0028000 {
status = "okay";
};
+
+ hlcdc: hlcdc@f0030000 {
+ status = "okay";
+
+ hlcdc-display-controller {
+ atmel,panel = <&panel 3 0>;
+ };
+ };
};
};
@@ -56,6 +64,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..8350358 100644
--- a/arch/arm/boot/dts/sama5d36ek.dts
+++ b/arch/arm/boot/dts/sama5d36ek.dts
@@ -41,12 +41,36 @@
status = "okay";
};
+ hlcdc: hlcdc@f0030000 {
+ status = "okay";
+
+ hlcdc-display-controller {
+ atmel,panel = <&panel 3 0>;
+ };
+ };
+
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";
};
--
1.8.3.2
Define the HLCDC (High LCD Controller) IP available on some sama5d3 SoCs
(i.e. sama5d31, sama5d33, sama5d34 and sama5d36).
Signed-off-by: Boris BREZILLON <[email protected]>
---
arch/arm/boot/dts/sama5d3_lcd.dtsi | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/arch/arm/boot/dts/sama5d3_lcd.dtsi b/arch/arm/boot/dts/sama5d3_lcd.dtsi
index 2186b89..02a7012 100644
--- a/arch/arm/boot/dts/sama5d3_lcd.dtsi
+++ b/arch/arm/boot/dts/sama5d3_lcd.dtsi
@@ -116,6 +116,32 @@
};
};
+ 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-dc";
+ interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
+ pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
+ pinctrl-0 = <&pinctrl_lcd_base>;
+ pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
+ pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
+ pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
+ pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
+ };
+
+ 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 {
--
1.8.3.2
The Atmel HLCDC (High LCD Controller) IP available on some Atmel SoCs (i.e.
at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display
controller device.
This display controller support at least one primary plane and might
provide several overlays and an hardware cursor depending on the IP
version.
Signed-off-by: Boris BREZILLON <[email protected]>
---
.../devicetree/bindings/drm/atmel-hlcdc-dc.txt | 59 ++
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 | 529 ++++++++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 477 ++++++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 178 ++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 701 +++++++++++++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 417 ++++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c | 351 +++++++++++
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 658 +++++++++++++++++++
drivers/gpu/drm/atmel_hlcdc/Kconfig | 11 +
drivers/gpu/drm/atmel_hlcdc/Makefile | 8 +
14 files changed, 3410 insertions(+)
create mode 100644 Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.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_panel.c
create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
create mode 100644 drivers/gpu/drm/atmel_hlcdc/Kconfig
create mode 100644 drivers/gpu/drm/atmel_hlcdc/Makefile
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..594bdb2
--- /dev/null
+++ b/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt
@@ -0,0 +1,59 @@
+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 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt for more details.
+
+Required properties:
+ - compatible: value should be one of the following:
+ "atmel,hlcdc-dc"
+ - interrupts: the HLCDC interrupt definition
+ - pinctrl-names: the pin control state names. Should contain "default",
+ "rgb-444", "rgb-565", "rgb-666" and "rgb-888".
+ - pinctrl-[0-4]: should contain the pinctrl states described by pinctrl
+ names.
+ - atmel,panel: Should contain a phandle with 2 parameters.
+ The first cell is a phandle to a DRM panel device
+ The second cell encodes the RGB mode, which can take the following values:
+ * 0: RGB444
+ * 1: RGB565
+ * 2: RGB666
+ * 3: RGB888
+ The third cell encodes specific flags describing LCD signals configuration
+ (see Atmel's datasheet for a full description of these fields):
+ * bit 0: HSPOL: Horizontal Synchronization Pulse Polarity
+ * bit 1: VSPOL: Vertical Synchronization Pulse Polarity
+ * bit 2: VSPDLYS: Vertical Synchronization Pulse Start
+ * bit 3: VSPDLYE: Vertical Synchronization Pulse End
+ * bit 4: DISPPOL: Display Signal Polarity
+ * bit 7: DISPDLY: LCD Controller Display Power Signal Synchronization
+ * bit 12: VSPSU: LCD Controller Vertical synchronization Pulse Setup Configuration
+ * bit 13: VSPHO: LCD Controller Vertical synchronization Pulse Hold Configuration
+ * bit 16-20: GUARDTIME: LCD DISPLAY Guard Time
+
+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-dc";
+ interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
+ pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
+ pinctrl-0 = <&pinctrl_lcd_base>;
+ pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
+ pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
+ pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
+ pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
+ };
+
+ hlcdc_pwm: hlcdc-pwm {
+ compatible = "atmel,hlcdc-pwm";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_lcd_pwm>;
+ #pwm-cells = <3>;
+ };
+ };
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index d1cc2f6..df6f0c1 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -182,6 +182,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 48e38ba..28c8a61 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -54,6 +54,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..bf9fe0b
--- /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_panel.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..a18492e
--- /dev/null
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2014 Traphandler
+ * Copyright (C) 2014 Free Electrons
+ *
+ * Author: Jean-Jacques Hiblot <[email protected]>
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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"
+
+/**
+ * Structure storing hardware cursor informations like its position, its size
+ * or the GEM object currently used to display the HW cursor.
+ *
+ * @gem: the cursor GEM object
+ * @pitch: GEM object pitch
+ * @height: cursor height in pixels
+ * @width: cursor width in pixels
+ * @x: cursor x position
+ * @y: cursor y position
+ * @offset: start offset from the provided buffer
+ * @patched_height: patched height value after adapting the image size
+ * depending on cursor position
+ * @patched_width: patched width value after adapting the image size depending
+ * on cursor position
+ * @patched_x: patched x value after adapting to cursor position (positive
+ * value)
+ * @patched_y: patched x value after adapting to cursor position (positive
+ * value)
+ */
+struct atmel_hlcdc_crtc_cursor_info {
+ struct drm_gem_cma_object *gem;
+ unsigned int pitch;
+ u32 height;
+ u32 width;
+ int x;
+ int y;
+
+ /*
+ * These fields are automatically calculated by
+ * atmel_hlcdc_crtc_cursor_prepare_req.
+ */
+ unsigned int offset;
+ u32 patched_height;
+ u32 patched_width;
+ int patched_x;
+ int patched_y;
+};
+
+/**
+ * Structure storing HW cursor status.
+ *
+ * @status: the current cursor status
+ * @req: the requested cursor changes
+ * @plane: the hardware cursor plane
+ * @lock: cursor lock held when modifying cursor req or status
+ */
+struct atmel_hlcdc_crtc_cursor {
+ struct atmel_hlcdc_crtc_cursor_info status;
+ struct atmel_hlcdc_crtc_cursor_info req;
+ struct atmel_hlcdc_plane *plane;
+ struct mutex lock;
+};
+
+/**
+ * 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
+ * @cursor: hardware cursor status
+ */
+struct atmel_hlcdc_crtc {
+ struct drm_crtc base;
+ struct atmel_hlcdc *hlcdc;
+ struct drm_pending_vblank_event *event;
+ int id;
+ int dpms;
+ struct atmel_hlcdc_crtc_cursor cursor;
+};
+
+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 < 0 ||
+ vm.vsync_len > 0x40 || vm.vsync_len < 0 ||
+ vm.vfront_porch > 0x40 || vm.vfront_porch < 0 ||
+ vm.vback_porch > 0x40 || vm.vback_porch < 0 ||
+ vm.hfront_porch > 0x200 || vm.hfront_porch < 0 ||
+ vm.hback_porch > 0x200 || vm.hback_porch < 0 ||
+ mode->hdisplay > 2048 || mode->hdisplay < 0 ||
+ mode->vdisplay > 2048 || mode->vdisplay < 0)
+ 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 - 1) << 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 drm_plane *plane = c->primary;
+
+ if (crtc->event)
+ return -EBUSY;
+
+ if (event) {
+ crtc->event = event;
+ drm_vblank_get(c->dev, crtc->id);
+ }
+
+ return plane->funcs->update_plane(plane, c, fb,
+ 0, 0,
+ c->mode.hdisplay, c->mode.vdisplay,
+ c->x << 16, c->y << 16,
+ c->mode.hdisplay << 16,
+ c->mode.vdisplay << 16);
+}
+
+static void atmel_hlcdc_crtc_cursor_prepare_req(struct atmel_hlcdc_crtc *crtc)
+{
+ int bpp = drm_format_plane_cpp(DRM_FORMAT_ARGB8888, 0);
+ struct atmel_hlcdc_crtc_cursor_info *info = &crtc->cursor.req;
+ int x_offset = 0;
+ int y_offset = 0;
+
+ if (!info->gem) {
+ info->width = 0;
+ info->height = 0;
+ info->pitch = 0;
+ info->offset = 0;
+ return;
+ }
+
+ info->pitch = info->width * bpp;
+
+ if (info->x + info->width > crtc->base.mode.hdisplay)
+ info->patched_width = crtc->base.mode.hdisplay - info->x;
+ else
+ info->patched_width = info->width;
+
+ if (info->x < 0) {
+ info->patched_width += info->x;
+ x_offset = -info->x;
+ info->patched_x = 0;
+ } else {
+ info->patched_x = info->x;
+ }
+
+ if (info->y + info->height > crtc->base.mode.vdisplay)
+ info->patched_height = crtc->base.mode.vdisplay - info->y;
+ else
+ info->patched_height = info->height;
+
+ if (info->y < 0) {
+ info->patched_height += info->y;
+ y_offset = -info->y;
+ info->patched_y = 0;
+ } else {
+ info->patched_y = info->y;
+ }
+
+ info->offset = (x_offset * bpp) +
+ (y_offset * info->pitch);
+}
+
+static int atmel_hlcdc_crtc_cursor_apply_req(struct atmel_hlcdc_crtc *crtc)
+{
+ struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
+ struct atmel_hlcdc_crtc_cursor_info *req = &crtc->cursor.req;
+ struct atmel_hlcdc_crtc_cursor_info *status = &crtc->cursor.status;
+ int ret;
+
+ if (unlikely(!plane))
+ return -ENOTSUPP;
+
+ if (!req->gem ||
+ !req->patched_width || !req->patched_height) {
+ ret = plane->base.funcs->disable_plane(&plane->base);
+
+ if (!ret)
+ goto out;
+ else
+ goto err;
+ }
+
+ ret = atmel_hlcdc_layer_update_start(&plane->layer);
+ if (ret)
+ return ret;
+
+ atmel_hlcdc_plane_update_general_settings(plane, DRM_FORMAT_ARGB8888);
+
+ ret = atmel_hlcdc_plane_update_format(plane, DRM_FORMAT_ARGB8888);
+ if (ret)
+ goto err;
+
+ ret = atmel_hlcdc_plane_update_pos_and_size(plane, &crtc->base,
+ req->patched_x,
+ req->patched_y,
+ req->patched_width,
+ req->patched_height,
+ 0, 0,
+ req->patched_width,
+ req->patched_height);
+ if (ret)
+ goto err;
+
+ ret = atmel_hlcdc_plane_update_buffers(plane,
+ DRM_FORMAT_ARGB8888,
+ &req->gem,
+ &req->pitch,
+ &req->offset,
+ 0, 0,
+ req->patched_width,
+ req->height);
+ if (ret)
+ goto err;
+
+ if (!plane->base.crtc)
+ plane->base.crtc = &crtc->base;
+
+ atmel_hlcdc_layer_update_commit(&plane->layer);
+
+out:
+ if (req->gem)
+ drm_gem_object_reference(&req->gem->base);
+
+ if (status->gem)
+ drm_gem_object_unreference_unlocked(&status->gem->base);
+
+ *status = *req;
+
+ return 0;
+
+err:
+ atmel_hlcdc_layer_update_rollback(&plane->layer);
+ return ret;
+}
+
+static int atmel_hlcdc_crtc_cursor_set(struct drm_crtc *c,
+ struct drm_file *file_priv,
+ uint32_t handle,
+ uint32_t width, uint32_t height)
+{
+ struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
+ struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
+ struct drm_gem_cma_object *cma_gem = NULL;
+ int ret;
+
+ if (unlikely(!plane))
+ return -ENOTSUPP;
+
+ mutex_lock(&crtc->cursor.lock);
+
+ if (handle) {
+ struct drm_gem_object *gem;
+
+ gem = drm_gem_object_lookup(c->dev, file_priv, handle);
+ if (unlikely(!gem)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ cma_gem = to_drm_gem_cma_obj(gem);
+ }
+
+ crtc->cursor.req = crtc->cursor.status;
+
+ crtc->cursor.req.gem = cma_gem;
+ crtc->cursor.req.width = width;
+ crtc->cursor.req.height = height;
+ atmel_hlcdc_crtc_cursor_prepare_req(crtc);
+
+ ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
+ if (cma_gem)
+ drm_gem_object_unreference_unlocked(&cma_gem->base);
+
+out:
+ mutex_unlock(&crtc->cursor.lock);
+
+ return ret;
+}
+
+static int atmel_hlcdc_crtc_cursor_move(struct drm_crtc *c, int x, int y)
+{
+ struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
+ struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
+ int ret;
+
+ if (unlikely(!plane))
+ return -ENOTSUPP;
+
+ mutex_lock(&crtc->cursor.lock);
+ /*
+ * If there's no cursor update pending, get the current
+ * cursor size and buffer.
+ */
+ crtc->cursor.req = crtc->cursor.status;
+
+ crtc->cursor.req.x = x;
+ crtc->cursor.req.y = y;
+ atmel_hlcdc_crtc_cursor_prepare_req(crtc);
+
+ ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
+ mutex_unlock(&crtc->cursor.lock);
+
+ 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,
+};
+
+static const struct drm_crtc_funcs atmel_hlcdc_crtc_with_cursor_funcs = {
+ .page_flip = atmel_hlcdc_crtc_page_flip,
+ .set_config = drm_crtc_helper_set_config,
+ .destroy = atmel_hlcdc_crtc_destroy,
+ .cursor_set = atmel_hlcdc_crtc_cursor_set,
+ .cursor_move = atmel_hlcdc_crtc_cursor_move,
+};
+
+int atmel_hlcdc_crtc_create(struct drm_device *dev)
+{
+ struct atmel_hlcdc_dc *dc = dev->dev_private;
+ struct atmel_hlcdc_planes *planes = dc->planes;
+ const struct drm_crtc_funcs *funcs;
+ 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;
+ }
+
+ mutex_init(&crtc->cursor.lock);
+ crtc->hlcdc = dc->hlcdc;
+ crtc->cursor.plane = planes->cursor;
+
+ if (planes->cursor)
+ funcs = &atmel_hlcdc_crtc_with_cursor_funcs;
+ else
+ funcs = &atmel_hlcdc_crtc_funcs;
+
+ ret = drm_crtc_init_with_planes(dev, &crtc->base,
+ &planes->primary->base,
+ planes->cursor ? &planes->cursor->base : NULL,
+ funcs);
+ if (ret < 0)
+ goto fail;
+
+ atmel_hlcdc_layer_set_finished(&planes->primary->layer,
+ atmel_hlcdc_crtc_finish_page_flip,
+ crtc);
+
+ 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..e4ce24e
--- /dev/null
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2014 Traphandler
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Jean-Jacques Hiblot <[email protected]>
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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 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);
+}
+
+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_panel_create(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 atmel_hlcdc_dc_desc *desc;
+ struct atmel_hlcdc_dc *dc;
+ int ret;
+
+ desc = platform_get_drvdata(pdev);
+
+ dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL);
+ if (!dc) {
+ dev_err(dev->dev, "failed to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ dc->desc = platform_get_drvdata(pdev);
+ 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");
+ return ret;
+ }
+
+ 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);
+ 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);
+
+ dc->fbdev = drm_fbdev_cma_init(dev, 24,
+ dev->mode_config.num_crtc,
+ dev->mode_config.num_connector);
+
+ if (IS_ERR(dc->fbdev)) {
+ ret = PTR_ERR(dc->fbdev);
+ goto err_periph_clk_disable;
+ }
+
+ return 0;
+
+err_periph_clk_disable:
+ clk_disable_unprepare(dc->hlcdc->periph_clk);
+
+ 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);
+
+ 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 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,
+ },
+ },
+ {
+ .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 int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ int ret;
+
+ match = of_match_node(atmel_hlcdc_of_match, pdev->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;
+ }
+
+ ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, (void *)match->data);
+
+ 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-dc" },
+ { },
+};
+
+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-dc",
+ .owner = THIS_MODULE,
+ .of_match_table = atmel_hlcdc_dc_of_match,
+ },
+};
+module_platform_driver(atmel_hlcdc_dc_platform_driver);
+
+MODULE_AUTHOR("Jean-Jacques Hiblot <[email protected]>");
+MODULE_AUTHOR("Boris BREZILLON <[email protected]>");
+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..5d2919e
--- /dev/null
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014 Traphandler
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Jean-Jacques Hiblot <[email protected]>
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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
+ */
+struct atmel_hlcdc_plane_properties {
+ struct drm_property *alpha;
+};
+
+/**
+ * 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;
+ u8 alpha;
+};
+
+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 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
+ */
+struct atmel_hlcdc_dc {
+ 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];
+};
+
+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_update_buffers(struct atmel_hlcdc_plane *plane,
+ u32 pixel_format,
+ struct drm_gem_cma_object **gems,
+ unsigned int *pitches,
+ unsigned int *offsets,
+ uint32_t src_x, uint32_t src_y,
+ uint32_t src_w, uint32_t src_h);
+
+void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
+ u32 format);
+
+int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
+ u32 format);
+
+int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
+ struct drm_crtc *crtc,
+ 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);
+
+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_panel_create(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..b449fe1
--- /dev/null
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
@@ -0,0 +1,701 @@
+/*
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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_gem_flip_release(struct atmel_hlcdc_layer *layer,
+ struct atmel_hlcdc_layer_gem_flip *flip)
+{
+ int i;
+
+ for (i = 0; i < layer->max_planes; i++) {
+ if (!flip->gems[i])
+ break;
+
+ drm_gem_object_unreference_unlocked(flip->gems[i]);
+ }
+
+ kfree(flip);
+}
+
+static void atmel_hlcdc_layer_gc_work(struct work_struct *work)
+{
+ struct atmel_hlcdc_layer_gem_flip_gc *gc =
+ container_of(work,
+ struct atmel_hlcdc_layer_gem_flip_gc,
+ work);
+ struct atmel_hlcdc_layer *layer =
+ container_of(gc , struct atmel_hlcdc_layer, gc);
+
+ while (true) {
+ struct atmel_hlcdc_layer_gem_flip *flip;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gc->list_lock, flags);
+ flip = list_first_entry_or_null(&gc->list,
+ struct atmel_hlcdc_layer_gem_flip,
+ node);
+ if (flip)
+ list_del(&flip->node);
+ spin_unlock_irqrestore(&gc->list_lock, flags);
+
+ if (!flip)
+ break;
+
+ atmel_hlcdc_layer_gem_flip_release(layer, flip);
+
+ mutex_lock(&gc->finished_lock);
+ if (gc->finished)
+ gc->finished(gc->finished_data);
+ mutex_unlock(&gc->finished_lock);
+ }
+}
+
+static void
+atmel_hlcdc_layer_gem_flip_put(struct atmel_hlcdc_layer *layer,
+ struct atmel_hlcdc_layer_gem_flip *flip)
+{
+ struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
+ unsigned long flags;
+
+ if (--flip->remaining_gems <= 0) {
+ spin_lock_irqsave(&gc->list_lock, flags);
+ list_add_tail(&flip->node,
+ &gc->list);
+ spin_unlock_irqrestore(&gc->list_lock, flags);
+ schedule_work(&gc->work);
+ }
+}
+
+static void atmel_hlcdc_layer_start_queue(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;
+ int i;
+
+ for (i = 0; i < layer->max_planes; i++) {
+ dma->cur[i] = dma->queue[i];
+ if (!dma->queue[i])
+ continue;
+ dma->queue[i] = NULL;
+
+ regmap_write(regmap,
+ desc->regs_offset +
+ ATMEL_HLCDC_LAYER_PLANE_ADDR(i),
+ dma->cur[i]->addr);
+ regmap_write(regmap,
+ desc->regs_offset +
+ ATMEL_HLCDC_LAYER_PLANE_CTRL(i),
+ dma->cur[i]->ctrl);
+ regmap_write(regmap,
+ desc->regs_offset +
+ ATMEL_HLCDC_LAYER_PLANE_NEXT(i),
+ dma->cur[i]->next);
+ }
+}
+
+static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer)
+{
+ 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;
+ unsigned int cfg;
+ u32 action = 0;
+ int i;
+
+ if (upd->pending < 0 || upd->pending > 1)
+ 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;
+ }
+
+ if (slot->gem_flip && slot->gem_flip->remaining_gems) {
+ struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+
+ for (i = 0; i < layer->max_planes; i++) {
+ struct atmel_hlcdc_dma_channel_dscr *dscr;
+
+ if (!slot->gem_flip->gems[i])
+ break;
+
+ dscr = slot->dscrs[i];
+ slot->dscrs[i] = NULL;
+
+ if (!dma->enabled) {
+ action |= ATMEL_HLCDC_LAYER_DMA_CHAN;
+ 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);
+ dma->cur[i] = dscr;
+ } else {
+ action |= ATMEL_HLCDC_LAYER_A2Q;
+ regmap_write(regmap,
+ desc->regs_offset +
+ ATMEL_HLCDC_LAYER_PLANE_HEAD(i),
+ dscr->next);
+ dma->queue[i] = dscr;
+ }
+ }
+
+ dma->enabled = true;
+ slot->gem_flip = NULL;
+ }
+
+ if (action)
+ regmap_write(regmap,
+ desc->regs_offset + ATMEL_HLCDC_LAYER_CHER,
+ action);
+
+ for (i = 0; i < layer->max_planes; i++) {
+ if (!slot->dscrs[i])
+ continue;
+
+ slot->dscrs[i]->gem_flip = NULL;
+ slot->dscrs[i] = NULL;
+ }
+
+ if (slot->gem_flip) {
+ atmel_hlcdc_layer_gem_flip_put(layer, slot->gem_flip);
+ slot->gem_flip = NULL;
+ }
+
+ bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
+ memset(slot->configs, 0,
+ sizeof(*slot->configs) * layer->desc->nconfigs);
+
+ upd->pending = -1;
+}
+
+static bool
+atmel_hlcdc_layer_dma_channel_active(struct atmel_hlcdc_layer *layer)
+{
+ struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+ int i;
+
+ for (i = 0; i < layer->max_planes; i++) {
+ if (dma->cur[i])
+ return true;
+ }
+
+ return false;
+}
+
+void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer)
+{
+ struct atmel_hlcdc_layer_gem_flip *flip_gc[ATMEL_HLCDC_MAX_PLANES];
+ 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_dma_channel_dscr *dscr;
+ unsigned long flags;
+ unsigned int isr, imr;
+ unsigned int 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;
+
+ memset(flip_gc, 0, sizeof(flip_gc));
+
+ spin_lock_irqsave(&dma->lock, flags);
+ for (i = 0; i < layer->max_planes; i++) {
+ if ((status & ATMEL_HLCDC_LAYER_DONE_IRQ(i)) ||
+ (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i))) {
+ dscr = dma->cur[i];
+ dma->cur[i] = NULL;
+ flip_gc[i] = dscr->gem_flip;
+ dscr->gem_flip = NULL;
+ }
+
+ if (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i)) {
+ dma->cur[i] = dma->queue[i];
+ dma->queue[i] = NULL;
+ }
+ }
+
+ /*
+ * The DMA channel might have been disabled before we were able to
+ * add the new frame to the DMA transfer queue.
+ * Try to re-enable the channel in this case.
+ */
+ if (!atmel_hlcdc_layer_dma_channel_active(layer)) {
+ if (atmel_hlcdc_layer_dma_channel_busy(layer)) {
+ atmel_hlcdc_layer_start_queue(layer);
+
+ regmap_write(regmap,
+ desc->regs_offset +
+ ATMEL_HLCDC_LAYER_CHDR,
+ ATMEL_HLCDC_LAYER_A2Q);
+ regmap_write(regmap,
+ desc->regs_offset +
+ ATMEL_HLCDC_LAYER_CHER,
+ ATMEL_HLCDC_LAYER_DMA_CHAN);
+ } else {
+ dma->enabled = false;
+ }
+ }
+
+ if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
+ struct atmel_hlcdc_layer_update *upd = &layer->update;
+
+ spin_lock(&upd->pending_lock);
+ atmel_hlcdc_layer_update_apply(layer);
+ spin_unlock(&upd->pending_lock);
+ }
+ spin_unlock_irqrestore(&dma->lock, flags);
+
+ for (i = 0; i < layer->max_planes; i++) {
+ if (!flip_gc[i])
+ break;
+
+ atmel_hlcdc_layer_gem_flip_put(layer, flip_gc[i]);
+ }
+}
+
+int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer)
+{
+ struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&dma->lock, flags);
+ for (i = 0; i < layer->max_planes; i++) {
+ if (!dma->cur[i])
+ break;
+
+ dma->cur[i]->ctrl = 0;
+ }
+ spin_unlock_irqrestore(&dma->lock, flags);
+
+ return 0;
+}
+
+void atmel_hlcdc_layer_set_finished(struct atmel_hlcdc_layer *layer,
+ void (*finished)(void *data),
+ void *data)
+{
+ struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
+
+ mutex_lock(&gc->finished_lock);
+ gc->finished = finished;
+ gc->finished_data = data;
+ mutex_unlock(&gc->finished_lock);
+}
+
+static void atmel_hlcdc_layer_update_reset(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;
+ int i;
+
+ if (upd->next < 0 || upd->next > 1)
+ return;
+
+ slot = &upd->slots[upd->next];
+ bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
+ memset(slot->configs, 0,
+ sizeof(*slot->configs) * layer->desc->nconfigs);
+
+ spin_lock_irqsave(&dma->lock, flags);
+ for (i = 0; i < layer->max_planes; i++) {
+ if (!slot->dscrs[i])
+ break;
+ slot->dscrs[i]->gem_flip = NULL;
+ slot->dscrs[i] = NULL;
+ }
+ spin_unlock_irqrestore(&layer->dma.lock, flags);
+
+ if (slot->gem_flip) {
+ atmel_hlcdc_layer_gem_flip_release(layer, slot->gem_flip);
+ slot->gem_flip = NULL;
+ }
+
+ upd->next = -1;
+}
+
+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_gem_flip *gem_flip;
+ struct atmel_hlcdc_layer_update_slot *slot;
+ unsigned long flags;
+ int i, j = 0;
+ int pending;
+
+ gem_flip = kzalloc(sizeof(*gem_flip), GFP_KERNEL);
+ if (!gem_flip)
+ return -ENOMEM;
+
+ mutex_lock(&upd->lock);
+
+ spin_lock_irqsave(&upd->pending_lock, flags);
+ pending = upd->pending;
+ spin_unlock_irqrestore(&upd->pending_lock, flags);
+
+ upd->next = pending ? 0 : 1;
+
+ slot = &upd->slots[upd->next];
+
+ spin_lock_irqsave(&dma->lock, flags);
+ for (i = 0; i < layer->max_planes * 4; i++) {
+ if (!dma->dscrs[i].gem_flip) {
+ slot->dscrs[j++] = &dma->dscrs[i];
+ dma->dscrs[i].gem_flip = gem_flip;
+ if (j == layer->max_planes)
+ break;
+ }
+ }
+
+ if (j < layer->max_planes) {
+ for (i = 0; i < j; i++)
+ slot->dscrs[i]->gem_flip = NULL;
+ }
+ spin_unlock_irqrestore(&layer->dma.lock, flags);
+
+ if (j < layer->max_planes) {
+ mutex_unlock(&upd->lock);
+ kfree(gem_flip);
+ return -EBUSY;
+ }
+
+ slot->gem_flip = gem_flip;
+
+ spin_lock_irqsave(&upd->pending_lock, flags);
+ pending = upd->pending;
+ if (pending >= 0) {
+ memcpy(upd->slots[upd->next].configs,
+ upd->slots[upd->pending].configs,
+ layer->desc->nconfigs * sizeof(u32));
+ memcpy(upd->slots[upd->next].updated_configs,
+ upd->slots[upd->pending].updated_configs,
+ DIV_ROUND_UP(layer->desc->nconfigs,
+ BITS_PER_BYTE * sizeof(unsigned long)) *
+ sizeof(unsigned long));
+ }
+ spin_unlock_irqrestore(&upd->pending_lock, flags);
+
+ if (pending < 0)
+ regmap_bulk_read(regmap, ATMEL_HLCDC_LAYER_CFG(layer, 0),
+ upd->slots[upd->next].configs,
+ layer->desc->nconfigs);
+
+ 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);
+ mutex_unlock(&upd->lock);
+}
+
+int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
+ int plane_id,
+ struct drm_gem_cma_object *gem,
+ unsigned int offset)
+{
+ struct atmel_hlcdc_layer_update *upd = &layer->update;
+ struct atmel_hlcdc_layer_gem_flip *gem_flip;
+ struct atmel_hlcdc_layer_update_slot *slot;
+ struct atmel_hlcdc_dma_channel_dscr *dscr;
+ struct drm_gem_object *old_gem;
+
+ if (upd->next < 0 || upd->next > 1)
+ return -EINVAL;
+
+ if (plane_id >= layer->max_planes || plane_id < 0)
+ return -EINVAL;
+
+ slot = &upd->slots[upd->next];
+ dscr = slot->dscrs[plane_id];
+ dscr->addr = gem->paddr + offset;
+ dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH;
+ gem_flip = dscr->gem_flip;
+
+ old_gem = gem_flip->gems[plane_id];
+
+ if (gem) {
+ drm_gem_object_reference(&gem->base);
+ gem_flip->gems[plane_id] = &gem->base;
+ gem_flip->remaining_gems++;
+ } else {
+ gem_flip->gems[plane_id] = NULL;
+ }
+
+ if (old_gem) {
+ drm_gem_object_unreference_unlocked(old_gem);
+ gem_flip->remaining_gems--;
+ }
+
+ return 0;
+}
+
+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;
+ int pending;
+
+ if (upd->next < 0 || upd->next > 1)
+ return;
+
+ slot = &upd->slots[upd->next];
+
+ spin_lock_irqsave(&upd->pending_lock, flags);
+ pending = upd->pending;
+ upd->pending = upd->next;
+ upd->next = pending;
+ if (pending >= 0 && !slot->gem_flip->remaining_gems) {
+ struct atmel_hlcdc_layer_gem_flip *gem_flip = slot->gem_flip;
+ struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
+
+ memcpy(dscrs, slot->dscrs, sizeof(dscrs));
+ slot->gem_flip = upd->slots[pending].gem_flip;
+ memcpy(slot->dscrs, upd->slots[pending].dscrs,
+ sizeof(slot->dscrs));
+ upd->slots[pending].gem_flip = gem_flip;
+ memcpy(upd->slots[pending].dscrs, dscrs, sizeof(dscrs));
+ }
+ spin_unlock_irqrestore(&upd->pending_lock, flags);
+
+ if (pending >= 0) {
+ atmel_hlcdc_layer_update_reset(layer);
+ mutex_unlock(&upd->lock);
+ return;
+ }
+
+ spin_lock_irqsave(&dma->lock, flags);
+ if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
+ spin_lock(&upd->pending_lock);
+ atmel_hlcdc_layer_update_apply(layer);
+ spin_unlock(&upd->pending_lock);
+ }
+ spin_unlock_irqrestore(&dma->lock, flags);
+
+ atmel_hlcdc_layer_update_reset(layer);
+ mutex_unlock(&upd->lock);
+}
+
+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));
+ }
+
+ spin_lock_init(&dma->lock);
+
+ 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];
+
+ if (!dscr->gem_flip)
+ continue;
+
+ atmel_hlcdc_layer_gem_flip_put(layer, dscr->gem_flip);
+ }
+
+ dma_free_coherent(dev->dev, layer->max_planes * 4 *
+ sizeof(*dma->dscrs), dma->dscrs,
+ dma->dscrs[0].next);
+}
+
+static void atmel_hlcdc_layer_gc_init(struct atmel_hlcdc_layer *layer)
+{
+ struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
+
+ INIT_LIST_HEAD(&gc->list);
+ spin_lock_init(&gc->list_lock);
+ INIT_WORK(&gc->work, atmel_hlcdc_layer_gc_work);
+ mutex_init(&gc->finished_lock);
+}
+
+static void atmel_hlcdc_layer_gc_cleanup(struct atmel_hlcdc_layer *layer)
+{
+ struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
+
+ flush_work(&gc->work);
+}
+
+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;
+ spin_lock_init(&upd->pending_lock);
+ mutex_init(&upd->lock);
+
+ 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->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;
+ }
+
+ atmel_hlcdc_layer_gc_init(layer);
+ 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);
+
+ for (i = 0; i < layer->max_planes; i++)
+ regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER,
+ ATMEL_HLCDC_LAYER_ADD_IRQ(i) |
+ ATMEL_HLCDC_LAYER_DONE_IRQ(i));
+
+ 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);
+ atmel_hlcdc_layer_gc_cleanup(layer);
+}
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..868f444
--- /dev/null
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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/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(n) BIT(2 + ((n) * 8))
+#define ATMEL_HLCDC_LAYER_DSCR_IRQ(n) BIT(3 + ((n) * 8))
+#define ATMEL_HLCDC_LAYER_ADD_IRQ(n) BIT(4 + ((n) * 8))
+#define ATMEL_HLCDC_LAYER_DONE_IRQ(n) BIT(5 + ((n) * 8))
+#define ATMEL_HLCDC_LAYER_OVR_IRQ(n) BIT(6 + ((n) * 8))
+
+#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_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
+
+/**
+ * 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
+ * @color_space_conv: color spave 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 color_space_conv;
+};
+
+/**
+ * Atmel HLCDC GEM 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 the reference GEM objects anymore
+ * (i.e. the layer was disabled or updated).
+ *
+ * @node: list element structure. Used to attach the GEM flip structure to
+ * the garbage collector when the layer no longer need the referenced
+ * GEM objects.
+ * @gems: the referenced GEM objects. The number of GEM object referenced
+ * depends on the chosen format.
+ * @remaining_gems: the number of GEM object still referenced by the layer.
+ * When no more objects are referenced the GEM flip structure
+ * is added to the garbage collector.
+ */
+struct atmel_hlcdc_layer_gem_flip {
+ struct list_head node;
+ struct drm_gem_object *gems[ATMEL_HLCDC_MAX_PLANES];
+ int remaining_gems;
+};
+
+/**
+ * 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;
+ struct atmel_hlcdc_layer_gem_flip *gem_flip;
+} __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
+ *@gem_flip: gem_flip object
+ *@updated_configs: bitmask used to record modified configs
+ *@configs: new config values
+ */
+struct atmel_hlcdc_layer_update_slot {
+ struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
+ struct atmel_hlcdc_layer_gem_flip *gem_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
+ *@pending_lock: lock to access the pending field
+ *@lock: layer update lock
+ */
+struct atmel_hlcdc_layer_update {
+ struct atmel_hlcdc_layer_update_slot slots[2];
+ int pending;
+ int next;
+ spinlock_t pending_lock;
+ struct mutex lock;
+};
+
+/**
+ * Atmel HLCDC Layer GEM flip garbage collector structure
+ *
+ * This structure is used to schedule GEM object release when we are in
+ * interrupt context (within atmel_hlcdc_layer_irq function).
+ *
+ *@list: GEM flip objects to release
+ *@list_lock: lock to access the GEM flip list
+ *@work: work queue scheduled when there are GEM flip to collect
+ *@finished: action to execute the GEM flip and all attached objects have been
+ * released
+ *@finished_data: data passed to the finished callback
+ *@finished_lock: lock to access finished related fields
+ */
+struct atmel_hlcdc_layer_gem_flip_gc {
+ struct list_head list;
+ spinlock_t list_lock;
+ struct work_struct work;
+ void (*finished)(void *data);
+ void *finished_data;
+ struct mutex finished_lock;
+};
+
+/**
+ * Atmel HLCDC Layer DMA channel structure
+ *
+ * This structure stores informations on the DMA channel associated to a
+ * given layer.
+ *
+ *@enabled: DMA channel status
+ *@lock: lock to access the DMA channel fields
+ *@cur: current DMA transfers (one for each plane/buffer)
+ *@queue: next DMA transfers, to be launch on next frame update
+ *@dscrs: allocated DMA descriptors
+ */
+struct atmel_hlcdc_layer_dma_channel {
+ bool enabled;
+ spinlock_t lock;
+ struct atmel_hlcdc_dma_channel_dscr *cur[ATMEL_HLCDC_MAX_PLANES];
+ struct atmel_hlcdc_dma_channel_dscr *queue[ATMEL_HLCDC_MAX_PLANES];
+ 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: GEM flip garbage collector
+ *@update: update handler
+ */
+struct atmel_hlcdc_layer {
+ const struct atmel_hlcdc_layer_desc *desc;
+ int max_planes;
+ struct atmel_hlcdc *hlcdc;
+ struct atmel_hlcdc_layer_dma_channel dma;
+ struct atmel_hlcdc_layer_gem_flip_gc gc;
+ struct atmel_hlcdc_layer_update update;
+};
+
+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);
+
+int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
+ int plane_id,
+ struct drm_gem_cma_object *gem,
+ unsigned int offset);
+
+void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer);
+
+void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer);
+
+static inline bool
+atmel_hlcdc_layer_dma_channel_busy(struct atmel_hlcdc_layer *layer)
+{
+ struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
+ int i;
+
+ for (i = 0; i < layer->max_planes; i++) {
+ if (dma->queue[i])
+ return true;
+ }
+
+ return false;
+}
+
+#endif /* DRM_ATMEL_HLCDC_LAYER_H */
diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
new file mode 100644
index 0000000..3295021
--- /dev/null
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2014 Traphandler
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Jean-Jacques Hiblot <[email protected]>
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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 <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,
+};
+
+/**
+ * Atmel HLCDC Panel structure
+ *
+ * This structure stores informations about an DRM panel connected through
+ * the RGB connector.
+ *
+ * @mode: RGB output mode
+ * @connector: DRM connector
+ * @encoder: DRM encoder
+ * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
+ * @panel: pointer to the attached DRM panel
+ * @pinctrl: pinctrl state used by the current RGB output mode
+ * @np: DRM panel DT node
+ * @dpms: current DPMS mode
+ */
+struct atmel_hlcdc_panel {
+ enum atmel_hlcdc_connector_rgb_mode mode;
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+ struct atmel_hlcdc *hlcdc;
+ struct drm_panel *panel;
+ struct pinctrl *pinctrl;
+ struct device_node *np;
+ int dpms;
+};
+
+static const char * const pin_state_names[] = {
+ [ATMEL_HLCDC_CONNECTOR_RGB444] = "rgb-444",
+ [ATMEL_HLCDC_CONNECTOR_RGB565] = "rgb-565",
+ [ATMEL_HLCDC_CONNECTOR_RGB666] = "rgb-666",
+ [ATMEL_HLCDC_CONNECTOR_RGB888] = "rgb-888",
+};
+
+static inline struct atmel_hlcdc_panel *
+drm_connector_to_atmel_hlcdc_panel(struct drm_connector *connector)
+{
+ return container_of(connector, struct atmel_hlcdc_panel, connector);
+}
+
+static inline struct atmel_hlcdc_panel *
+drm_encoder_to_atmel_hlcdc_panel(struct drm_encoder *encoder)
+{
+ return container_of(encoder, struct atmel_hlcdc_panel, encoder);
+}
+
+static void atmel_hlcdc_panel_encoder_dpms(struct drm_encoder *encoder,
+ int mode)
+{
+ struct atmel_hlcdc_panel *panel =
+ drm_encoder_to_atmel_hlcdc_panel(encoder);
+ struct regmap *regmap = panel->hlcdc->regmap;
+ unsigned int status;
+
+ if (mode != DRM_MODE_DPMS_ON)
+ mode = DRM_MODE_DPMS_OFF;
+
+ if (mode == panel->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(panel->hlcdc->sys_clk);
+ if (panel->panel)
+ drm_panel_disable(panel->panel);
+ } else {
+ if (panel->panel)
+ drm_panel_enable(panel->panel);
+ clk_prepare_enable(panel->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();
+ }
+
+ panel->dpms = mode;
+}
+
+static bool
+atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted)
+{
+ return true;
+}
+
+static void atmel_hlcdc_panel_encoder_prepare(struct drm_encoder *encoder)
+{
+ atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static void atmel_hlcdc_panel_encoder_commit(struct drm_encoder *encoder)
+{
+ atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void
+atmel_hlcdc_panel_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted)
+{
+ struct atmel_hlcdc_panel *panel =
+ drm_encoder_to_atmel_hlcdc_panel(encoder);
+ unsigned long prate = clk_get_rate(panel->hlcdc->sys_clk);
+ unsigned long mode_rate = mode->clock * 1000;
+ int div;
+ u32 cfg0 = 0;
+
+ if ((prate / 2) < mode_rate) {
+ prate *= 2;
+ cfg0 |= ATMEL_HLCDC_CLKSEL;
+ }
+
+ div = DIV_ROUND_UP(prate, mode_rate);
+ if (div < 2)
+ div = 2;
+
+ cfg0 |= ATMEL_HLCDC_CLKDIV(div);
+
+ regmap_update_bits(panel->hlcdc->regmap, ATMEL_HLCDC_CFG(0),
+ ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK, cfg0);
+}
+
+static struct drm_encoder_helper_funcs encoder_helper_funcs = {
+ .dpms = atmel_hlcdc_panel_encoder_dpms,
+ .mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup,
+ .prepare = atmel_hlcdc_panel_encoder_prepare,
+ .commit = atmel_hlcdc_panel_encoder_commit,
+ .mode_set = atmel_hlcdc_panel_encoder_mode_set,
+};
+
+static void atmel_hlcdc_panel_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+ memset(encoder, 0, sizeof(*encoder));
+}
+
+static const struct drm_encoder_funcs encoder_funcs = {
+ .destroy = atmel_hlcdc_panel_encoder_destroy,
+};
+
+static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
+{
+ struct atmel_hlcdc_panel *panel =
+ drm_connector_to_atmel_hlcdc_panel(connector);
+ int ret;
+
+ if (!panel->panel)
+ return -ENOENT;
+
+ ret = panel->panel->funcs->get_modes(panel->panel);
+ return ret;
+}
+
+static int atmel_hlcdc_panel_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static struct drm_encoder *
+atmel_hlcdc_panel_best_encoder(struct drm_connector *connector)
+{
+ struct atmel_hlcdc_panel *panel =
+ drm_connector_to_atmel_hlcdc_panel(connector);
+
+ return &panel->encoder;
+}
+
+static struct drm_connector_helper_funcs connector_helper_funcs = {
+ .get_modes = atmel_hlcdc_panel_get_modes,
+ .mode_valid = atmel_hlcdc_panel_mode_valid,
+ .best_encoder = atmel_hlcdc_panel_best_encoder,
+};
+
+static enum drm_connector_status
+atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct atmel_hlcdc_panel *panel =
+ drm_connector_to_atmel_hlcdc_panel(connector);
+
+ if (!panel->panel) {
+ panel->panel = of_drm_find_panel(panel->np);
+ if (panel->panel)
+ drm_panel_attach(panel->panel, &panel->connector);
+ }
+
+ if (panel->panel)
+ return connector_status_connected;
+
+ return connector_status_disconnected;
+}
+
+static void
+atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
+{
+ struct atmel_hlcdc_panel *panel =
+ drm_connector_to_atmel_hlcdc_panel(connector);
+
+ if (panel->panel)
+ drm_panel_detach(panel->panel);
+
+ drm_sysfs_connector_remove(connector);
+ drm_connector_cleanup(connector);
+
+ pinctrl_put(panel->pinctrl);
+}
+
+static const struct drm_connector_funcs connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = atmel_hlcdc_panel_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = atmel_hlcdc_panel_connector_destroy,
+};
+
+int atmel_hlcdc_panel_create(struct drm_device *dev)
+{
+ struct atmel_hlcdc_dc *dc = dev->dev_private;
+ struct atmel_hlcdc_panel *panel;
+ struct of_phandle_args out_args;
+ u32 cfg;
+ int ret;
+
+ panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ ret = of_parse_phandle_with_fixed_args(dev->dev->of_node,
+ "atmel,panel", 2, 0,
+ &out_args);
+ if (ret) {
+ dev_err(dev->dev, "failed to retrieve panel info from DT\n");
+ return ret;
+ }
+
+ switch (out_args.args[0]) {
+ case ATMEL_HLCDC_CONNECTOR_RGB444:
+ case ATMEL_HLCDC_CONNECTOR_RGB565:
+ case ATMEL_HLCDC_CONNECTOR_RGB666:
+ case ATMEL_HLCDC_CONNECTOR_RGB888:
+ break;
+ default:
+ dev_err(dev->dev, "unknown RGB mode\n");
+ return -EINVAL;
+ }
+
+ panel->np = out_args.np;
+ panel->mode = out_args.args[0];
+ panel->pinctrl = pinctrl_get_select(dev->dev,
+ pin_state_names[panel->mode]);
+ if (IS_ERR(panel->pinctrl)) {
+ dev_err(dev->dev, "could not request pinctrl state\n");
+ return PTR_ERR(panel->pinctrl);
+ }
+
+ cfg = out_args.args[1] & (ATMEL_HLCDC_HSPOL |
+ ATMEL_HLCDC_VSPOL |
+ ATMEL_HLCDC_VSPDLYS |
+ ATMEL_HLCDC_VSPDLYE |
+ ATMEL_HLCDC_DISPPOL |
+ ATMEL_HLCDC_DISPDLY |
+ ATMEL_HLCDC_VSPSU |
+ ATMEL_HLCDC_VSPHO);
+ cfg |= panel->mode << 8;
+
+ regmap_write(dc->hlcdc->regmap,
+ ATMEL_HLCDC_CFG(5),
+ cfg);
+
+ panel->dpms = DRM_MODE_DPMS_OFF;
+
+ panel->hlcdc = dc->hlcdc;
+
+ drm_connector_init(dev, &panel->connector, &connector_funcs,
+ DRM_MODE_CONNECTOR_LVDS);
+ drm_connector_helper_add(&panel->connector, &connector_helper_funcs);
+ panel->connector.dpms = DRM_MODE_DPMS_OFF;
+ panel->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
+
+ drm_encoder_init(dev, &panel->encoder, &encoder_funcs,
+ DRM_MODE_ENCODER_LVDS);
+ drm_encoder_helper_add(&panel->encoder, &encoder_helper_funcs);
+
+ drm_mode_connector_attach_encoder(&panel->connector, &panel->encoder);
+ drm_sysfs_connector_add(&panel->connector);
+
+ panel->encoder.possible_crtcs = 0x1;
+
+ 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..f428b47
--- /dev/null
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ *
+ * Author: Boris BREZILLON <[email protected]>
+ *
+ * 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_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_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_NV61,
+ DRM_FORMAT_YUV422,
+ DRM_FORMAT_NV21,
+};
+
+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_NV61:
+ *mode = ATMEL_HLCDC_NV61_MODE;
+ break;
+ case DRM_FORMAT_YUV422:
+ *mode = ATMEL_HLCDC_YUV422_MODE;
+ break;
+ case DRM_FORMAT_NV21:
+ *mode = ATMEL_HLCDC_NV21_MODE;
+ break;
+ case DRM_FORMAT_YUV420:
+ *mode = ATMEL_HLCDC_YUV420_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,
+};
+
+int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
+ struct drm_crtc *crtc,
+ 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)
+{
+ const struct atmel_hlcdc_layer_cfg_layout *layout =
+ &plane->layer.desc->layout;
+
+ if (!crtc_h || !crtc_w)
+ return -EINVAL;
+
+ if (!layout->pos && (crtc_x || crtc_y))
+ return -EINVAL;
+
+ if ((crtc_x + crtc_w) > crtc->mode.crtc_hdisplay ||
+ (crtc_y + crtc_h) > crtc->mode.crtc_vdisplay)
+ return -EINVAL;
+
+ if (!layout->size &&
+ (crtc->mode.crtc_hdisplay != crtc_w ||
+ crtc->mode.crtc_vdisplay != crtc_h))
+ return -EINVAL;
+
+ if (plane->layer.desc->max_height &&
+ crtc_h > plane->layer.desc->max_height)
+ return -EINVAL;
+
+ if (plane->layer.desc->max_width &&
+ crtc_w > plane->layer.desc->max_width)
+ return -EINVAL;
+
+ if (!layout->memsize && (crtc_h != src_h || crtc_w != src_w))
+ return -EINVAL;
+
+ if (layout->size)
+ atmel_hlcdc_layer_update_cfg(&plane->layer,
+ layout->size,
+ 0xffffffff,
+ (crtc_w - 1) |
+ ((crtc_h - 1) << 16));
+
+ if (layout->memsize)
+ atmel_hlcdc_layer_update_cfg(&plane->layer,
+ layout->memsize,
+ 0xffffffff,
+ (src_w - 1) |
+ ((src_h - 1) << 16));
+
+ if (layout->pos)
+ atmel_hlcdc_layer_update_cfg(&plane->layer,
+ layout->pos,
+ 0xffffffff,
+ crtc_x | (crtc_y << 16));
+
+ /* TODO: rework the rescaling part */
+ if (crtc_w != src_w || crtc_h != src_h) {
+ u32 factor_reg = 0;
+
+ if (crtc_w != src_w) {
+ int i;
+ u32 factor;
+ u32 *coeff_tab = heo_upscaling_xcoef;
+ u32 max_memsize;
+
+ if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
+ factor++;
+ max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
+ if (max_memsize > src_w)
+ factor--;
+ factor_reg |= factor | 0x80000000;
+ }
+
+ if (crtc_h != src_h) {
+ int i;
+ u32 factor;
+ u32 *coeff_tab = heo_upscaling_ycoef;
+ u32 max_memsize;
+
+ if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
+ factor++;
+ max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
+ if (max_memsize > src_w)
+ factor--;
+ factor_reg |= (factor << 16) | 0x80000000;
+ }
+
+ atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff,
+ factor_reg);
+ }
+
+ return 0;
+}
+
+void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
+ u32 format)
+{
+ 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(format))
+ cfg |= ATMEL_HLCDC_LAYER_LAEN;
+ else
+ cfg |= (plane->alpha << ATMEL_HLCDC_LAYER_GA_SHIFT) |
+ 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 |
+ ATMEL_HLCDC_LAYER_GA_MASK, cfg);
+}
+
+int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
+ u32 format)
+{
+ u32 mode;
+ int ret;
+
+ ret = atmel_hlcdc_format_to_plane_mode(format, &mode);
+ if (ret)
+ return ret;
+
+ atmel_hlcdc_layer_update_cfg(&plane->layer,
+ ATMEL_HLCDC_LAYER_FORMAT_CFG_ID,
+ 0xffffffff,
+ mode);
+
+ return 0;
+}
+
+int atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
+ u32 pixel_format,
+ struct drm_gem_cma_object **gems,
+ unsigned int *pitches,
+ unsigned int *offsets,
+ uint32_t src_x, uint32_t src_y,
+ uint32_t src_w, uint32_t src_h)
+{
+ struct atmel_hlcdc_layer *layer = &plane->layer;
+ const struct atmel_hlcdc_layer_cfg_layout *layout =
+ &layer->desc->layout;
+ int bpp[ATMEL_HLCDC_MAX_PLANES];
+ int nplanes;
+ int ret = 0;
+ int i;
+
+ nplanes = drm_format_num_planes(pixel_format);
+
+ for (i = 0; i < nplanes; i++) {
+ bpp[i] = drm_format_plane_cpp(pixel_format, i);
+ if (!bpp[i])
+ return -EINVAL;
+
+ if (!gems[i])
+ return -EINVAL;
+ }
+
+ for (i = 0; i < nplanes; i++) {
+ int offset;
+
+ offset = offsets[i] + (src_y * pitches[i]) +
+ (src_x * bpp[i]);
+
+ ret = atmel_hlcdc_layer_update_set_gem(&plane->layer, i,
+ gems[i], offset);
+ if (ret)
+ return ret;
+
+ if (layout->xstride[i])
+ atmel_hlcdc_layer_update_cfg(&plane->layer,
+ layout->xstride[i],
+ 0xffffffff,
+ pitches[i] - (bpp[i] * src_w));
+ }
+
+ 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 drm_gem_cma_object *gems[ATMEL_HLCDC_MAX_PLANES];
+ int nplanes;
+ int ret = 0;
+ int i;
+
+ /* Subpixel positioning is not supported */
+ if ((src_x | src_y | src_w | src_h) & SUBPIXEL_MASK) {
+ DRM_DEBUG_KMS("Primary base does not support subpixel positioning\n");
+ return -EINVAL;
+ }
+
+ src_h >>= 16;
+ src_w >>= 16;
+ src_x >>= 16;
+ src_y >>= 16;
+
+ nplanes = drm_format_num_planes(fb->pixel_format);
+ if (nplanes > ATMEL_HLCDC_MAX_PLANES)
+ return -EINVAL;
+
+ for (i = 0; i < nplanes; i++)
+ gems[i] = drm_fb_cma_get_gem_obj(fb, i);
+
+ atmel_hlcdc_layer_update_start(&plane->layer);
+ ret = atmel_hlcdc_plane_update_pos_and_size(plane, crtc,
+ crtc_x, crtc_y,
+ crtc_w, crtc_h,
+ src_x, src_y,
+ src_w, src_h);
+ if (ret)
+ goto out;
+
+ atmel_hlcdc_plane_update_general_settings(plane, fb->pixel_format);
+
+ ret = atmel_hlcdc_plane_update_format(plane, fb->pixel_format);
+ if (ret)
+ goto out;
+
+
+ ret = atmel_hlcdc_plane_update_buffers(plane, fb->pixel_format,
+ gems,
+ fb->pitches, fb->offsets,
+ src_x, src_y,
+ src_w, src_h);
+
+ if (ret)
+ goto out;
+
+ atmel_hlcdc_layer_update_commit(&plane->layer);
+
+ drm_framebuffer_reference(fb);
+ if (plane->base.fb)
+ drm_framebuffer_unreference(plane->base.fb);
+ plane->base.fb = fb;
+
+ return 0;
+
+out:
+ atmel_hlcdc_layer_update_rollback(&plane->layer);
+
+ return ret;
+}
+
+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);
+ plane->alpha = alpha;
+ atmel_hlcdc_plane_update_general_settings(plane,
+ plane->base.fb->pixel_format);
+ atmel_hlcdc_layer_update_commit(&plane->layer);
+
+ 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
+ return -EINVAL;
+
+ return 0;
+}
+
+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 *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);
+
+ /* Set default property values*/
+ plane->alpha = 0xff;
+
+ 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);
+
+ return plane;
+}
+
+static struct atmel_hlcdc_plane_properties *
+atmel_hlcdc_plane_create_properties(struct drm_device *dev)
+{
+ struct atmel_hlcdc_plane_properties *props;
+
+ 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);
+
+ 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]);
+ 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;
+ drm_object_attach_property(&plane->base.base,
+ props->alpha, 255);
+ break;
+
+ case ATMEL_HLCDC_CURSOR_LAYER:
+ if (planes->cursor)
+ return ERR_PTR(-EINVAL);
+ planes->cursor = plane;
+ drm_object_attach_property(&plane->base.base,
+ props->alpha, 255);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return planes;
+}
diff --git a/drivers/gpu/drm/atmel_hlcdc/Kconfig b/drivers/gpu/drm/atmel_hlcdc/Kconfig
new file mode 100644
index 0000000..59c8eeb
--- /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 && ARM && 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..08de8d7
--- /dev/null
+++ b/drivers/gpu/drm/atmel_hlcdc/Makefile
@@ -0,0 +1,8 @@
+atmel_hlcdc-y := atmel_hlcdc_crtc.o \
+ atmel_hlcdc_drv.o \
+ atmel_hlcdc_layer.o \
+ atmel_hlcdc_panel.o \
+ atmel_hlcdc_plane.o \
+ atmel_hlcdc_pwm.o
+
+obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel_hlcdc.o
--
1.8.3.2
The HLCDC (High LCD Controller) IP supports 4 different output mode
(RGB444, RGB565, RGB666 and RGB888) and the pin muxing depends on the
chosen RGB mode.
Split the pin definition to be able to set the pin config according to the
selected mode.
Signed-off-by: Boris BREZILLON <[email protected]>
---
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 */
};
};
};
--
1.8.3.2
On 09/06/2014 at 18:04:15 +0200, 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.
>
> This driver add support for this PWM device.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> .../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 40 ++++
Please separate the DT bindings documentation from the driver code.
> drivers/pwm/Kconfig | 9 +
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-atmel-hlcdc.c | 216 +++++++++++++++++++++
> 4 files changed, 266 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
> create mode 100644 drivers/pwm/pwm-atmel-hlcdc.c
>
> 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..5e2ba87
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
> @@ -0,0 +1,40 @@
> +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) PWM driver
> +
> +The Atmel HLCDC PWM is subdevice of the HLCDC MFD device.
> +See Documentation/devicetree/bindings/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.
> +
> +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-dc";
> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
> + pinctrl-0 = <&pinctrl_lcd_base>;
> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
> + };
> +
> + hlcdc_pwm: hlcdc-pwm {
> + compatible = "atmel,hlcdc-pwm";
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_lcd_pwm>;
> + #pwm-cells = <3>;
> + };
> + };
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 5b34ff2..7186242 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 e57d2c3..a245519 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_BFIN) += pwm-bfin.o
> obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
> diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c
> new file mode 100644
> index 0000000..080e43e
> --- /dev/null
> +++ b/drivers/pwm/pwm-atmel-hlcdc.c
> @@ -0,0 +1,216 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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/mfd/atmel-hlcdc.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +
> +#define ATMEL_HLCDC_PWMCVAL_MASK (0xff << 8)
Maybe you could use GENMASK()
> +#define ATMEL_HLCDC_PWMCVAL(x) (((x) & 0xff) << 8)
Maybe use ATMEL_HLCDC_PWMCVAL_MASK here ?
> +#define ATMEL_HLCDC_PWMPOL BIT(4)
> +#define ATMEL_HLCDC_PWMPS_MASK 0x7
ditto
> +#define ATMEL_HLCDC_PWMPS_MAX 0x6
> +#define ATMEL_HLCDC_PWMPS(x) ((x) & 0x7)
Why not using ATMEL_HLCDC_PWMPS_MASK here ?
> +
> +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))
> + cpu_relax();
> +
I'm not sure you have to wait here, it will be enabled at some point,
that is enough.
> + 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))
> + cpu_relax();
Ditto
> +}
> +
> +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;
> + int ret;
> +
> + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
> + if (!chip)
> + return -ENOMEM;
> +
> + chip->hlcdc = dev_get_drvdata(dev->parent);
> + 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);
> +
> + 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 <[email protected]>");
> +MODULE_DESCRIPTION("Atmel HLCDC PWM driver");
> +MODULE_LICENSE("GPL");
> --
> 1.8.3.2
>
--
Alexandre Belloni, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
Hello Alexandre,
On 13/06/2014 13:43, Alexandre Belloni wrote:
> On 09/06/2014 at 18:04:15 +0200, 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.
>>
>> This driver add support for this PWM device.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> .../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 40 ++++
> Please separate the DT bindings documentation from the driver code.
I'll do it.
>
>> drivers/pwm/Kconfig | 9 +
>> drivers/pwm/Makefile | 1 +
>> drivers/pwm/pwm-atmel-hlcdc.c | 216 +++++++++++++++++++++
>> 4 files changed, 266 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
>> create mode 100644 drivers/pwm/pwm-atmel-hlcdc.c
>>
>> 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..5e2ba87
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
>> @@ -0,0 +1,40 @@
>> +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) PWM driver
>> +
>> +The Atmel HLCDC PWM is subdevice of the HLCDC MFD device.
>> +See Documentation/devicetree/bindings/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.
>> +
>> +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-dc";
>> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
>> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
>> + pinctrl-0 = <&pinctrl_lcd_base>;
>> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
>> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
>> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
>> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
>> + };
>> +
>> + hlcdc_pwm: hlcdc-pwm {
>> + compatible = "atmel,hlcdc-pwm";
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_lcd_pwm>;
>> + #pwm-cells = <3>;
>> + };
>> + };
>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>> index 5b34ff2..7186242 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 e57d2c3..a245519 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_BFIN) += pwm-bfin.o
>> obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
>> diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c
>> new file mode 100644
>> index 0000000..080e43e
>> --- /dev/null
>> +++ b/drivers/pwm/pwm-atmel-hlcdc.c
>> @@ -0,0 +1,216 @@
>> +/*
>> + * Copyright (C) 2014 Free Electrons
>> + * Copyright (C) 2014 Atmel
>> + *
>> + * Author: Boris BREZILLON <[email protected]>
>> + *
>> + * 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/mfd/atmel-hlcdc.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pwm.h>
>> +
>> +#define ATMEL_HLCDC_PWMCVAL_MASK (0xff << 8)
> Maybe you could use GENMASK()
IMHO this does not help in understanding the code (I even find it more
tricky to figure out what the mask is), but if other people think we
should use GENMASK, then I'll use to it.
>
>> +#define ATMEL_HLCDC_PWMCVAL(x) (((x) & 0xff) << 8)
> Maybe use ATMEL_HLCDC_PWMCVAL_MASK here ?
Absolutely, I'll replace 0xff by ATMEL_HLCDC_PWMCVAL_MASK.
>
>> +#define ATMEL_HLCDC_PWMPOL BIT(4)
>> +#define ATMEL_HLCDC_PWMPS_MASK 0x7
> ditto
>
>> +#define ATMEL_HLCDC_PWMPS_MAX 0x6
>> +#define ATMEL_HLCDC_PWMPS(x) ((x) & 0x7)
> Why not using ATMEL_HLCDC_PWMPS_MASK here ?
>
>> +
>> +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))
>> + cpu_relax();
>> +
> I'm not sure you have to wait here, it will be enabled at some point,
> that is enough.
Okay.
>
>> + 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))
>> + cpu_relax();
> Ditto
>
>> +}
>> +
>> +static const struct pwm_ops atmel_hlcdc_pwm_ops = {
>> + .config = atmel_hlcdc_pwm_config,
Thanks for your review.
Best Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
On 06/09/2014 06:04 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
>
> Add support for the MFD device which will just retrieve HLCDC clocks and
> create a regmap so that subdevices can access the HLCDC register range
> concurrently.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> .../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++++++++
> drivers/mfd/Kconfig | 11 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/atmel-hlcdc.c | 116 +++++++++++++++++++++
> include/linux/mfd/atmel-hlcdc.h | 78 ++++++++++++++
> 5 files changed, 247 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> create mode 100644 drivers/mfd/atmel-hlcdc.c
> create mode 100644 include/linux/mfd/atmel-hlcdc.h
>
> diff --git a/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> new file mode 100644
> index 0000000..f5b69cb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> @@ -0,0 +1,41 @@
> +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 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
> + - a Display Controller: see Documentation/devicetree/bindings/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-dc";
> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
> + pinctrl-0 = <&pinctrl_lcd_base>;
> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
> + };
> +
> + hlcdc_pwm: hlcdc-pwm {
> + compatible = "atmel,hlcdc-pwm";
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_lcd_pwm>;
> + #pwm-cells = <3>;
> + };
> + };
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index ee8204c..82777f6 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -59,6 +59,17 @@ 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 (High LCD Controller)"
> + select MFD_CORE
> + select REGMAP_MMIO
> + help
> + Choose this option if you have an ATMEL SoC with an HLCDC (High
> + LCD 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..e4636e8
> --- /dev/null
> +++ b/drivers/mfd/atmel-hlcdc.c
> @@ -0,0 +1,116 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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/mfd/atmel-hlcdc.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +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-dc",
> + },
> +};
> +
> +static int atmel_hlcdc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct regmap_config config;
> + 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 functional 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 functional 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);
> + }
> +
> + memset(&config, 0, sizeof(config));
> + config.reg_bits = 32;
> + config.val_bits = 32;
> + config.reg_stride = 4;
> + config.max_register = (resource_size(res) / 4) - 1;
> + hlcdc->regmap = devm_regmap_init_mmio_clk(dev, "periph_clk", regs,
> + &config);
I don't think it's necessary to use "periph_clk" here. This clock will
always be running because the HLCDC needs it to work (it's not just an
interface clock). In the end it's just some extra work for each register
access.
> + 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);
> +
> + dev_set_drvdata(&pdev->dev, NULL);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id atmel_hlcdc_match[] = {
> + { .compatible = "atmel,sama5d3-hlcdc" },
> + { },
> +};
> +
> +static struct platform_driver atmel_hlcdc_driver = {
> + .probe = atmel_hlcdc_probe,
> + .remove = atmel_hlcdc_remove,
> + .driver = {
> + .name = "atmel-hlcdc",
> + .owner = THIS_MODULE,
> + .of_match_table = atmel_hlcdc_match,
> + },
> +};
> +module_platform_driver(atmel_hlcdc_driver);
> +
> +MODULE_ALIAS("platform:atmel-hlcdc");
> +MODULE_AUTHOR("Boris Brezillon <[email protected]>");
> +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..d7a5589
> --- /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 <[email protected]>
> + *
> + * 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 0x300
> +#define ATMEL_HLCDC_PP BIT(10)
> +#define ATMEL_HLCDC_VSPSU BIT(12)
> +#define ATMEL_HLCDC_VSPHO BIT(13)
> +#define ATMEL_HLCDC_GUARDTIME_MASK 0x1f0000
> +
> +#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 (0xff << ATMEL_HLCDC_CLKDIV_SHFT)
> +#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 */
>
On 06/09/2014 06:04 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.
>
> This driver add support for this PWM device.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> .../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 40 ++++
> drivers/pwm/Kconfig | 9 +
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-atmel-hlcdc.c | 216 +++++++++++++++++++++
> 4 files changed, 266 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
> create mode 100644 drivers/pwm/pwm-atmel-hlcdc.c
>
> 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..5e2ba87
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
> @@ -0,0 +1,40 @@
> +Device-Tree bindings for Atmel's HLCDC (High LCD Controller) PWM driver
> +
> +The Atmel HLCDC PWM is subdevice of the HLCDC MFD device.
> +See Documentation/devicetree/bindings/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.
> +
> +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-dc";
> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
> + pinctrl-0 = <&pinctrl_lcd_base>;
> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
> + };
> +
> + hlcdc_pwm: hlcdc-pwm {
> + compatible = "atmel,hlcdc-pwm";
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_lcd_pwm>;
> + #pwm-cells = <3>;
> + };
> + };
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 5b34ff2..7186242 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
I'd personnaly prefer a 'select' instead of 'depends on' here. Or maybe
the MFD driver should enabled y defaut for platforms supporting the 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 e57d2c3..a245519 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_BFIN) += pwm-bfin.o
> obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
> diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c
> new file mode 100644
> index 0000000..080e43e
> --- /dev/null
> +++ b/drivers/pwm/pwm-atmel-hlcdc.c
> @@ -0,0 +1,216 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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/mfd/atmel-hlcdc.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +
> +#define ATMEL_HLCDC_PWMCVAL_MASK (0xff << 8)
> +#define ATMEL_HLCDC_PWMCVAL(x) (((x) & 0xff) << 8)
> +#define ATMEL_HLCDC_PWMPOL BIT(4)
> +#define ATMEL_HLCDC_PWMPS_MASK 0x7
> +#define ATMEL_HLCDC_PWMPS_MAX 0x6
> +#define ATMEL_HLCDC_PWMPS(x) ((x) & 0x7)
> +
> +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))
> + cpu_relax();
> +
> + 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))
> + cpu_relax();
> +}
> +
> +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;
> + int ret;
> +
> + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
> + if (!chip)
> + return -ENOMEM;
> +
> + chip->hlcdc = dev_get_drvdata(dev->parent);
> + 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);
> +
> + 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 <[email protected]>");
> +MODULE_DESCRIPTION("Atmel HLCDC PWM driver");
> +MODULE_LICENSE("GPL");
>
On 06/09/2014 06:04 PM, Boris BREZILLON wrote:
> The Atmel HLCDC (High LCD Controller) IP available on some Atmel SoCs (i.e.
> at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display
> controller device.
>
> This display controller support at least one primary plane and might
> provide several overlays and an hardware cursor depending on the IP
> version.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> .../devicetree/bindings/drm/atmel-hlcdc-dc.txt | 59 ++
> 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 | 529 ++++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 477 ++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 178 ++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 701 +++++++++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 417 ++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c | 351 +++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 658 +++++++++++++++++++
> drivers/gpu/drm/atmel_hlcdc/Kconfig | 11 +
> drivers/gpu/drm/atmel_hlcdc/Makefile | 8 +
> 14 files changed, 3410 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.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_panel.c
> create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> create mode 100644 drivers/gpu/drm/atmel_hlcdc/Kconfig
> create mode 100644 drivers/gpu/drm/atmel_hlcdc/Makefile
>
> 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..594bdb2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt
> @@ -0,0 +1,59 @@
> +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 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt for more details.
> +
> +Required properties:
> + - compatible: value should be one of the following:
> + "atmel,hlcdc-dc"
> + - interrupts: the HLCDC interrupt definition
> + - pinctrl-names: the pin control state names. Should contain "default",
> + "rgb-444", "rgb-565", "rgb-666" and "rgb-888".
> + - pinctrl-[0-4]: should contain the pinctrl states described by pinctrl
> + names.
> + - atmel,panel: Should contain a phandle with 2 parameters.
> + The first cell is a phandle to a DRM panel device
> + The second cell encodes the RGB mode, which can take the following values:
> + * 0: RGB444
> + * 1: RGB565
> + * 2: RGB666
> + * 3: RGB888
> + The third cell encodes specific flags describing LCD signals configuration
> + (see Atmel's datasheet for a full description of these fields):
> + * bit 0: HSPOL: Horizontal Synchronization Pulse Polarity
> + * bit 1: VSPOL: Vertical Synchronization Pulse Polarity
> + * bit 2: VSPDLYS: Vertical Synchronization Pulse Start
> + * bit 3: VSPDLYE: Vertical Synchronization Pulse End
> + * bit 4: DISPPOL: Display Signal Polarity
> + * bit 7: DISPDLY: LCD Controller Display Power Signal Synchronization
> + * bit 12: VSPSU: LCD Controller Vertical synchronization Pulse Setup Configuration
> + * bit 13: VSPHO: LCD Controller Vertical synchronization Pulse Hold Configuration
> + * bit 16-20: GUARDTIME: LCD DISPLAY Guard Time
> +
> +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-dc";
> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
> + pinctrl-0 = <&pinctrl_lcd_base>;
> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
> + };
> +
> + hlcdc_pwm: hlcdc-pwm {
> + compatible = "atmel,hlcdc-pwm";
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_lcd_pwm>;
> + #pwm-cells = <3>;
> + };
> + };
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index d1cc2f6..df6f0c1 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -182,6 +182,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 48e38ba..28c8a61 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -54,6 +54,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..bf9fe0b
> --- /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_panel.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..a18492e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
> @@ -0,0 +1,529 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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"
> +
> +/**
> + * Structure storing hardware cursor informations like its position, its size
> + * or the GEM object currently used to display the HW cursor.
> + *
> + * @gem: the cursor GEM object
> + * @pitch: GEM object pitch
> + * @height: cursor height in pixels
> + * @width: cursor width in pixels
> + * @x: cursor x position
> + * @y: cursor y position
> + * @offset: start offset from the provided buffer
> + * @patched_height: patched height value after adapting the image size
> + * depending on cursor position
> + * @patched_width: patched width value after adapting the image size depending
> + * on cursor position
> + * @patched_x: patched x value after adapting to cursor position (positive
> + * value)
> + * @patched_y: patched x value after adapting to cursor position (positive
> + * value)
> + */
> +struct atmel_hlcdc_crtc_cursor_info {
> + struct drm_gem_cma_object *gem;
> + unsigned int pitch;
> + u32 height;
> + u32 width;
> + int x;
> + int y;
> +
> + /*
> + * These fields are automatically calculated by
> + * atmel_hlcdc_crtc_cursor_prepare_req.
> + */
> + unsigned int offset;
> + u32 patched_height;
> + u32 patched_width;
> + int patched_x;
> + int patched_y;
> +};
> +
> +/**
> + * Structure storing HW cursor status.
> + *
> + * @status: the current cursor status
> + * @req: the requested cursor changes
> + * @plane: the hardware cursor plane
> + * @lock: cursor lock held when modifying cursor req or status
> + */
> +struct atmel_hlcdc_crtc_cursor {
> + struct atmel_hlcdc_crtc_cursor_info status;
> + struct atmel_hlcdc_crtc_cursor_info req;
> + struct atmel_hlcdc_plane *plane;
> + struct mutex lock;
> +};
> +
> +/**
> + * 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
> + * @cursor: hardware cursor status
> + */
> +struct atmel_hlcdc_crtc {
> + struct drm_crtc base;
> + struct atmel_hlcdc *hlcdc;
> + struct drm_pending_vblank_event *event;
> + int id;
> + int dpms;
> + struct atmel_hlcdc_crtc_cursor cursor;
> +};
> +
> +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 < 0 ||
> + vm.vsync_len > 0x40 || vm.vsync_len < 0 ||
> + vm.vfront_porch > 0x40 || vm.vfront_porch < 0 ||
> + vm.vback_porch > 0x40 || vm.vback_porch < 0 ||
> + vm.hfront_porch > 0x200 || vm.hfront_porch < 0 ||
> + vm.hback_porch > 0x200 || vm.hback_porch < 0 ||
> + mode->hdisplay > 2048 || mode->hdisplay < 0 ||
> + mode->vdisplay > 2048 || mode->vdisplay < 0)
> + 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 - 1) << 16));
Acording to the datasheet, it's vm.vback_porch instead of
(vm.vback_porch -1).
> +
> + 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 drm_plane *plane = c->primary;
> +
> + if (crtc->event)
> + return -EBUSY;
> +
> + if (event) {
> + crtc->event = event;
> + drm_vblank_get(c->dev, crtc->id);
> + }
> +
> + return plane->funcs->update_plane(plane, c, fb,
> + 0, 0,
> + c->mode.hdisplay, c->mode.vdisplay,
> + c->x << 16, c->y << 16,
> + c->mode.hdisplay << 16,
> + c->mode.vdisplay << 16);
> +}
> +
> +static void atmel_hlcdc_crtc_cursor_prepare_req(struct atmel_hlcdc_crtc *crtc)
> +{
> + int bpp = drm_format_plane_cpp(DRM_FORMAT_ARGB8888, 0);
> + struct atmel_hlcdc_crtc_cursor_info *info = &crtc->cursor.req;
> + int x_offset = 0;
> + int y_offset = 0;
> +
> + if (!info->gem) {
> + info->width = 0;
> + info->height = 0;
> + info->pitch = 0;
> + info->offset = 0;
> + return;
> + }
> +
> + info->pitch = info->width * bpp;
> +
> + if (info->x + info->width > crtc->base.mode.hdisplay)
> + info->patched_width = crtc->base.mode.hdisplay - info->x;
> + else
> + info->patched_width = info->width;
> +
> + if (info->x < 0) {
> + info->patched_width += info->x;
> + x_offset = -info->x;
> + info->patched_x = 0;
> + } else {
> + info->patched_x = info->x;
> + }
> +
> + if (info->y + info->height > crtc->base.mode.vdisplay)
> + info->patched_height = crtc->base.mode.vdisplay - info->y;
> + else
> + info->patched_height = info->height;
> +
> + if (info->y < 0) {
> + info->patched_height += info->y;
> + y_offset = -info->y;
> + info->patched_y = 0;
> + } else {
> + info->patched_y = info->y;
> + }
> +
> + info->offset = (x_offset * bpp) +
> + (y_offset * info->pitch);
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_apply_req(struct atmel_hlcdc_crtc *crtc)
> +{
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + struct atmel_hlcdc_crtc_cursor_info *req = &crtc->cursor.req;
> + struct atmel_hlcdc_crtc_cursor_info *status = &crtc->cursor.status;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + if (!req->gem ||
> + !req->patched_width || !req->patched_height) {
> + ret = plane->base.funcs->disable_plane(&plane->base);
> +
> + if (!ret)
> + goto out;
> + else
> + goto err;
> + }
> +
> + ret = atmel_hlcdc_layer_update_start(&plane->layer);
> + if (ret)
> + return ret;
> +
> + atmel_hlcdc_plane_update_general_settings(plane, DRM_FORMAT_ARGB8888);
> +
> + ret = atmel_hlcdc_plane_update_format(plane, DRM_FORMAT_ARGB8888);
> + if (ret)
> + goto err;
> +
> + ret = atmel_hlcdc_plane_update_pos_and_size(plane, &crtc->base,
> + req->patched_x,
> + req->patched_y,
> + req->patched_width,
> + req->patched_height,
> + 0, 0,
> + req->patched_width,
> + req->patched_height);
> + if (ret)
> + goto err;
> +
> + ret = atmel_hlcdc_plane_update_buffers(plane,
> + DRM_FORMAT_ARGB8888,
> + &req->gem,
> + &req->pitch,
> + &req->offset,
> + 0, 0,
> + req->patched_width,
> + req->height);
> + if (ret)
> + goto err;
> +
> + if (!plane->base.crtc)
> + plane->base.crtc = &crtc->base;
> +
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> +out:
> + if (req->gem)
> + drm_gem_object_reference(&req->gem->base);
> +
> + if (status->gem)
> + drm_gem_object_unreference_unlocked(&status->gem->base);
> +
> + *status = *req;
> +
> + return 0;
> +
> +err:
> + atmel_hlcdc_layer_update_rollback(&plane->layer);
> + return ret;
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_set(struct drm_crtc *c,
> + struct drm_file *file_priv,
> + uint32_t handle,
> + uint32_t width, uint32_t height)
> +{
> + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + struct drm_gem_cma_object *cma_gem = NULL;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + mutex_lock(&crtc->cursor.lock);
> +
> + if (handle) {
> + struct drm_gem_object *gem;
> +
> + gem = drm_gem_object_lookup(c->dev, file_priv, handle);
> + if (unlikely(!gem)) {
> + ret = -ENOENT;
> + goto out;
> + }
> +
> + cma_gem = to_drm_gem_cma_obj(gem);
> + }
> +
> + crtc->cursor.req = crtc->cursor.status;
> +
> + crtc->cursor.req.gem = cma_gem;
> + crtc->cursor.req.width = width;
> + crtc->cursor.req.height = height;
> + atmel_hlcdc_crtc_cursor_prepare_req(crtc);
> +
> + ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
> + if (cma_gem)
> + drm_gem_object_unreference_unlocked(&cma_gem->base);
> +
> +out:
> + mutex_unlock(&crtc->cursor.lock);
> +
> + return ret;
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_move(struct drm_crtc *c, int x, int y)
> +{
> + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + mutex_lock(&crtc->cursor.lock);
> + /*
> + * If there's no cursor update pending, get the current
> + * cursor size and buffer.
> + */
> + crtc->cursor.req = crtc->cursor.status;
> +
> + crtc->cursor.req.x = x;
> + crtc->cursor.req.y = y;
> + atmel_hlcdc_crtc_cursor_prepare_req(crtc);
> +
> + ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
> + mutex_unlock(&crtc->cursor.lock);
> +
> + 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,
> +};
> +
> +static const struct drm_crtc_funcs atmel_hlcdc_crtc_with_cursor_funcs = {
> + .page_flip = atmel_hlcdc_crtc_page_flip,
> + .set_config = drm_crtc_helper_set_config,
> + .destroy = atmel_hlcdc_crtc_destroy,
> + .cursor_set = atmel_hlcdc_crtc_cursor_set,
> + .cursor_move = atmel_hlcdc_crtc_cursor_move,
> +};
> +
> +int atmel_hlcdc_crtc_create(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_dc *dc = dev->dev_private;
> + struct atmel_hlcdc_planes *planes = dc->planes;
> + const struct drm_crtc_funcs *funcs;
> + 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;
> + }
> +
> + mutex_init(&crtc->cursor.lock);
> + crtc->hlcdc = dc->hlcdc;
> + crtc->cursor.plane = planes->cursor;
> +
> + if (planes->cursor)
> + funcs = &atmel_hlcdc_crtc_with_cursor_funcs;
> + else
> + funcs = &atmel_hlcdc_crtc_funcs;
> +
> + ret = drm_crtc_init_with_planes(dev, &crtc->base,
> + &planes->primary->base,
> + planes->cursor ? &planes->cursor->base : NULL,
> + funcs);
> + if (ret < 0)
> + goto fail;
> +
> + atmel_hlcdc_layer_set_finished(&planes->primary->layer,
> + atmel_hlcdc_crtc_finish_page_flip,
> + crtc);
> +
> + 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..e4ce24e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
> @@ -0,0 +1,477 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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 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);
> +}
> +
> +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_panel_create(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 atmel_hlcdc_dc_desc *desc;
> + struct atmel_hlcdc_dc *dc;
> + int ret;
> +
> + desc = platform_get_drvdata(pdev);
> +
> + dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL);
> + if (!dc) {
> + dev_err(dev->dev, "failed to allocate private data\n");
> + return -ENOMEM;
> + }
> +
> + dc->desc = platform_get_drvdata(pdev);
> + 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");
> + return ret;
> + }
> +
> + 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);
> + 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);
> +
> + dc->fbdev = drm_fbdev_cma_init(dev, 24,
> + dev->mode_config.num_crtc,
> + dev->mode_config.num_connector);
> +
> + if (IS_ERR(dc->fbdev)) {
> + ret = PTR_ERR(dc->fbdev);
> + goto err_periph_clk_disable;
> + }
> +
> + return 0;
> +
> +err_periph_clk_disable:
> + clk_disable_unprepare(dc->hlcdc->periph_clk);
> +
> + 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);
> +
> + 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 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,
> + },
> + },
> + {
> + .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 int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
> +{
> + const struct of_device_id *match;
> + int ret;
> +
> + match = of_match_node(atmel_hlcdc_of_match, pdev->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;
> + }
> +
> + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, (void *)match->data);
> +
> + 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-dc" },
> + { },
> +};
> +
> +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-dc",
> + .owner = THIS_MODULE,
> + .of_match_table = atmel_hlcdc_dc_of_match,
> + },
> +};
> +module_platform_driver(atmel_hlcdc_dc_platform_driver);
> +
> +MODULE_AUTHOR("Jean-Jacques Hiblot <[email protected]>");
> +MODULE_AUTHOR("Boris BREZILLON <[email protected]>");
> +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..5d2919e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
> @@ -0,0 +1,178 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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
> + */
> +struct atmel_hlcdc_plane_properties {
> + struct drm_property *alpha;
> +};
> +
> +/**
> + * 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;
> + u8 alpha;
> +};
> +
> +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 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
> + */
> +struct atmel_hlcdc_dc {
> + 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];
> +};
> +
> +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_update_buffers(struct atmel_hlcdc_plane *plane,
> + u32 pixel_format,
> + struct drm_gem_cma_object **gems,
> + unsigned int *pitches,
> + unsigned int *offsets,
> + uint32_t src_x, uint32_t src_y,
> + uint32_t src_w, uint32_t src_h);
> +
> +void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
> + u32 format);
> +
> +int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
> + u32 format);
> +
> +int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
> + struct drm_crtc *crtc,
> + 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);
> +
> +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_panel_create(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..b449fe1
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
> @@ -0,0 +1,701 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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_gem_flip_release(struct atmel_hlcdc_layer *layer,
> + struct atmel_hlcdc_layer_gem_flip *flip)
> +{
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!flip->gems[i])
> + break;
> +
> + drm_gem_object_unreference_unlocked(flip->gems[i]);
> + }
> +
> + kfree(flip);
> +}
> +
> +static void atmel_hlcdc_layer_gc_work(struct work_struct *work)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc =
> + container_of(work,
> + struct atmel_hlcdc_layer_gem_flip_gc,
> + work);
> + struct atmel_hlcdc_layer *layer =
> + container_of(gc , struct atmel_hlcdc_layer, gc);
> +
> + while (true) {
> + struct atmel_hlcdc_layer_gem_flip *flip;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&gc->list_lock, flags);
> + flip = list_first_entry_or_null(&gc->list,
> + struct atmel_hlcdc_layer_gem_flip,
> + node);
> + if (flip)
> + list_del(&flip->node);
> + spin_unlock_irqrestore(&gc->list_lock, flags);
> +
> + if (!flip)
> + break;
> +
> + atmel_hlcdc_layer_gem_flip_release(layer, flip);
> +
> + mutex_lock(&gc->finished_lock);
> + if (gc->finished)
> + gc->finished(gc->finished_data);
> + mutex_unlock(&gc->finished_lock);
> + }
> +}
> +
> +static void
> +atmel_hlcdc_layer_gem_flip_put(struct atmel_hlcdc_layer *layer,
> + struct atmel_hlcdc_layer_gem_flip *flip)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> + unsigned long flags;
> +
> + if (--flip->remaining_gems <= 0) {
> + spin_lock_irqsave(&gc->list_lock, flags);
> + list_add_tail(&flip->node,
> + &gc->list);
> + spin_unlock_irqrestore(&gc->list_lock, flags);
> + schedule_work(&gc->work);
> + }
> +}
> +
> +static void atmel_hlcdc_layer_start_queue(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;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + dma->cur[i] = dma->queue[i];
> + if (!dma->queue[i])
> + continue;
> + dma->queue[i] = NULL;
> +
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_ADDR(i),
> + dma->cur[i]->addr);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_CTRL(i),
> + dma->cur[i]->ctrl);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_NEXT(i),
> + dma->cur[i]->next);
> + }
> +}
> +
> +static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer)
> +{
> + 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;
> + unsigned int cfg;
> + u32 action = 0;
> + int i;
> +
> + if (upd->pending < 0 || upd->pending > 1)
> + 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;
> + }
> +
> + if (slot->gem_flip && slot->gem_flip->remaining_gems) {
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + struct atmel_hlcdc_dma_channel_dscr *dscr;
> +
> + if (!slot->gem_flip->gems[i])
> + break;
> +
> + dscr = slot->dscrs[i];
> + slot->dscrs[i] = NULL;
> +
> + if (!dma->enabled) {
> + action |= ATMEL_HLCDC_LAYER_DMA_CHAN;
> + 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);
> + dma->cur[i] = dscr;
> + } else {
> + action |= ATMEL_HLCDC_LAYER_A2Q;
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_HEAD(i),
> + dscr->next);
> + dma->queue[i] = dscr;
> + }
> + }
> +
> + dma->enabled = true;
> + slot->gem_flip = NULL;
> + }
> +
> + if (action)
> + regmap_write(regmap,
> + desc->regs_offset + ATMEL_HLCDC_LAYER_CHER,
> + action);
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!slot->dscrs[i])
> + continue;
> +
> + slot->dscrs[i]->gem_flip = NULL;
> + slot->dscrs[i] = NULL;
> + }
> +
> + if (slot->gem_flip) {
> + atmel_hlcdc_layer_gem_flip_put(layer, slot->gem_flip);
> + slot->gem_flip = NULL;
> + }
> +
> + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
> + memset(slot->configs, 0,
> + sizeof(*slot->configs) * layer->desc->nconfigs);
> +
> + upd->pending = -1;
> +}
> +
> +static bool
> +atmel_hlcdc_layer_dma_channel_active(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (dma->cur[i])
> + return true;
> + }
> +
> + return false;
> +}
> +
> +void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip *flip_gc[ATMEL_HLCDC_MAX_PLANES];
> + 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_dma_channel_dscr *dscr;
> + unsigned long flags;
> + unsigned int isr, imr;
> + unsigned int 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;
> +
> + memset(flip_gc, 0, sizeof(flip_gc));
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if ((status & ATMEL_HLCDC_LAYER_DONE_IRQ(i)) ||
> + (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i))) {
> + dscr = dma->cur[i];
> + dma->cur[i] = NULL;
> + flip_gc[i] = dscr->gem_flip;
> + dscr->gem_flip = NULL;
> + }
> +
> + if (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i)) {
> + dma->cur[i] = dma->queue[i];
> + dma->queue[i] = NULL;
> + }
> + }
> +
> + /*
> + * The DMA channel might have been disabled before we were able to
> + * add the new frame to the DMA transfer queue.
> + * Try to re-enable the channel in this case.
> + */
> + if (!atmel_hlcdc_layer_dma_channel_active(layer)) {
> + if (atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + atmel_hlcdc_layer_start_queue(layer);
> +
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_CHDR,
> + ATMEL_HLCDC_LAYER_A2Q);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_CHER,
> + ATMEL_HLCDC_LAYER_DMA_CHAN);
> + } else {
> + dma->enabled = false;
> + }
> + }
> +
> + if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + struct atmel_hlcdc_layer_update *upd = &layer->update;
> +
> + spin_lock(&upd->pending_lock);
> + atmel_hlcdc_layer_update_apply(layer);
> + spin_unlock(&upd->pending_lock);
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!flip_gc[i])
> + break;
> +
> + atmel_hlcdc_layer_gem_flip_put(layer, flip_gc[i]);
> + }
> +}
> +
> +int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + unsigned long flags;
> + int i;
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!dma->cur[i])
> + break;
> +
> + dma->cur[i]->ctrl = 0;
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + return 0;
> +}
> +
> +void atmel_hlcdc_layer_set_finished(struct atmel_hlcdc_layer *layer,
> + void (*finished)(void *data),
> + void *data)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + mutex_lock(&gc->finished_lock);
> + gc->finished = finished;
> + gc->finished_data = data;
> + mutex_unlock(&gc->finished_lock);
> +}
> +
> +static void atmel_hlcdc_layer_update_reset(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;
> + int i;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return;
> +
> + slot = &upd->slots[upd->next];
> + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
> + memset(slot->configs, 0,
> + sizeof(*slot->configs) * layer->desc->nconfigs);
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!slot->dscrs[i])
> + break;
> + slot->dscrs[i]->gem_flip = NULL;
> + slot->dscrs[i] = NULL;
> + }
> + spin_unlock_irqrestore(&layer->dma.lock, flags);
> +
> + if (slot->gem_flip) {
> + atmel_hlcdc_layer_gem_flip_release(layer, slot->gem_flip);
> + slot->gem_flip = NULL;
> + }
> +
> + upd->next = -1;
> +}
> +
> +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_gem_flip *gem_flip;
> + struct atmel_hlcdc_layer_update_slot *slot;
> + unsigned long flags;
> + int i, j = 0;
> + int pending;
> +
> + gem_flip = kzalloc(sizeof(*gem_flip), GFP_KERNEL);
> + if (!gem_flip)
> + return -ENOMEM;
> +
> + mutex_lock(&upd->lock);
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + upd->next = pending ? 0 : 1;
> +
> + slot = &upd->slots[upd->next];
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes * 4; i++) {
> + if (!dma->dscrs[i].gem_flip) {
> + slot->dscrs[j++] = &dma->dscrs[i];
> + dma->dscrs[i].gem_flip = gem_flip;
> + if (j == layer->max_planes)
> + break;
> + }
> + }
> +
> + if (j < layer->max_planes) {
> + for (i = 0; i < j; i++)
> + slot->dscrs[i]->gem_flip = NULL;
> + }
> + spin_unlock_irqrestore(&layer->dma.lock, flags);
> +
> + if (j < layer->max_planes) {
> + mutex_unlock(&upd->lock);
> + kfree(gem_flip);
> + return -EBUSY;
> + }
> +
> + slot->gem_flip = gem_flip;
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + if (pending >= 0) {
> + memcpy(upd->slots[upd->next].configs,
> + upd->slots[upd->pending].configs,
> + layer->desc->nconfigs * sizeof(u32));
> + memcpy(upd->slots[upd->next].updated_configs,
> + upd->slots[upd->pending].updated_configs,
> + DIV_ROUND_UP(layer->desc->nconfigs,
> + BITS_PER_BYTE * sizeof(unsigned long)) *
> + sizeof(unsigned long));
> + }
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + if (pending < 0)
> + regmap_bulk_read(regmap, ATMEL_HLCDC_LAYER_CFG(layer, 0),
> + upd->slots[upd->next].configs,
> + layer->desc->nconfigs);
> +
> + 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);
> + mutex_unlock(&upd->lock);
> +}
> +
> +int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
> + int plane_id,
> + struct drm_gem_cma_object *gem,
> + unsigned int offset)
> +{
> + struct atmel_hlcdc_layer_update *upd = &layer->update;
> + struct atmel_hlcdc_layer_gem_flip *gem_flip;
> + struct atmel_hlcdc_layer_update_slot *slot;
> + struct atmel_hlcdc_dma_channel_dscr *dscr;
> + struct drm_gem_object *old_gem;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return -EINVAL;
> +
> + if (plane_id >= layer->max_planes || plane_id < 0)
> + return -EINVAL;
> +
> + slot = &upd->slots[upd->next];
> + dscr = slot->dscrs[plane_id];
> + dscr->addr = gem->paddr + offset;
> + dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH;
> + gem_flip = dscr->gem_flip;
> +
> + old_gem = gem_flip->gems[plane_id];
> +
> + if (gem) {
> + drm_gem_object_reference(&gem->base);
> + gem_flip->gems[plane_id] = &gem->base;
> + gem_flip->remaining_gems++;
> + } else {
> + gem_flip->gems[plane_id] = NULL;
> + }
> +
> + if (old_gem) {
> + drm_gem_object_unreference_unlocked(old_gem);
> + gem_flip->remaining_gems--;
> + }
> +
> + return 0;
> +}
> +
> +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;
> + int pending;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return;
> +
> + slot = &upd->slots[upd->next];
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + upd->pending = upd->next;
> + upd->next = pending;
> + if (pending >= 0 && !slot->gem_flip->remaining_gems) {
> + struct atmel_hlcdc_layer_gem_flip *gem_flip = slot->gem_flip;
> + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
> +
> + memcpy(dscrs, slot->dscrs, sizeof(dscrs));
> + slot->gem_flip = upd->slots[pending].gem_flip;
> + memcpy(slot->dscrs, upd->slots[pending].dscrs,
> + sizeof(slot->dscrs));
> + upd->slots[pending].gem_flip = gem_flip;
> + memcpy(upd->slots[pending].dscrs, dscrs, sizeof(dscrs));
> + }
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + if (pending >= 0) {
> + atmel_hlcdc_layer_update_reset(layer);
> + mutex_unlock(&upd->lock);
> + return;
> + }
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + spin_lock(&upd->pending_lock);
> + atmel_hlcdc_layer_update_apply(layer);
> + spin_unlock(&upd->pending_lock);
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + atmel_hlcdc_layer_update_reset(layer);
> + mutex_unlock(&upd->lock);
> +}
> +
> +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));
> + }
> +
> + spin_lock_init(&dma->lock);
> +
> + 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];
> +
> + if (!dscr->gem_flip)
> + continue;
> +
> + atmel_hlcdc_layer_gem_flip_put(layer, dscr->gem_flip);
> + }
> +
> + dma_free_coherent(dev->dev, layer->max_planes * 4 *
> + sizeof(*dma->dscrs), dma->dscrs,
> + dma->dscrs[0].next);
> +}
> +
> +static void atmel_hlcdc_layer_gc_init(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + INIT_LIST_HEAD(&gc->list);
> + spin_lock_init(&gc->list_lock);
> + INIT_WORK(&gc->work, atmel_hlcdc_layer_gc_work);
> + mutex_init(&gc->finished_lock);
> +}
> +
> +static void atmel_hlcdc_layer_gc_cleanup(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + flush_work(&gc->work);
> +}
> +
> +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;
> + spin_lock_init(&upd->pending_lock);
> + mutex_init(&upd->lock);
> +
> + 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->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;
> + }
> +
> + atmel_hlcdc_layer_gc_init(layer);
> + 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);
> +
> + for (i = 0; i < layer->max_planes; i++)
> + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER,
> + ATMEL_HLCDC_LAYER_ADD_IRQ(i) |
> + ATMEL_HLCDC_LAYER_DONE_IRQ(i));
> +
> + 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);
> + atmel_hlcdc_layer_gc_cleanup(layer);
> +}
> 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..868f444
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
> @@ -0,0 +1,417 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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/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(n) BIT(2 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_DSCR_IRQ(n) BIT(3 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_ADD_IRQ(n) BIT(4 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_DONE_IRQ(n) BIT(5 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_OVR_IRQ(n) BIT(6 + ((n) * 8))
> +
> +#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_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
> +
> +/**
> + * 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
> + * @color_space_conv: color spave 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 color_space_conv;
> +};
> +
> +/**
> + * Atmel HLCDC GEM 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 the reference GEM objects anymore
> + * (i.e. the layer was disabled or updated).
> + *
> + * @node: list element structure. Used to attach the GEM flip structure to
> + * the garbage collector when the layer no longer need the referenced
> + * GEM objects.
> + * @gems: the referenced GEM objects. The number of GEM object referenced
> + * depends on the chosen format.
> + * @remaining_gems: the number of GEM object still referenced by the layer.
> + * When no more objects are referenced the GEM flip structure
> + * is added to the garbage collector.
> + */
> +struct atmel_hlcdc_layer_gem_flip {
> + struct list_head node;
> + struct drm_gem_object *gems[ATMEL_HLCDC_MAX_PLANES];
> + int remaining_gems;
> +};
> +
> +/**
> + * 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;
> + struct atmel_hlcdc_layer_gem_flip *gem_flip;
> +} __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
> + *@gem_flip: gem_flip object
> + *@updated_configs: bitmask used to record modified configs
> + *@configs: new config values
> + */
> +struct atmel_hlcdc_layer_update_slot {
> + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
> + struct atmel_hlcdc_layer_gem_flip *gem_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
> + *@pending_lock: lock to access the pending field
> + *@lock: layer update lock
> + */
> +struct atmel_hlcdc_layer_update {
> + struct atmel_hlcdc_layer_update_slot slots[2];
> + int pending;
> + int next;
> + spinlock_t pending_lock;
> + struct mutex lock;
> +};
> +
> +/**
> + * Atmel HLCDC Layer GEM flip garbage collector structure
> + *
> + * This structure is used to schedule GEM object release when we are in
> + * interrupt context (within atmel_hlcdc_layer_irq function).
> + *
> + *@list: GEM flip objects to release
> + *@list_lock: lock to access the GEM flip list
> + *@work: work queue scheduled when there are GEM flip to collect
> + *@finished: action to execute the GEM flip and all attached objects have been
> + * released
> + *@finished_data: data passed to the finished callback
> + *@finished_lock: lock to access finished related fields
> + */
> +struct atmel_hlcdc_layer_gem_flip_gc {
> + struct list_head list;
> + spinlock_t list_lock;
> + struct work_struct work;
> + void (*finished)(void *data);
> + void *finished_data;
> + struct mutex finished_lock;
> +};
> +
> +/**
> + * Atmel HLCDC Layer DMA channel structure
> + *
> + * This structure stores informations on the DMA channel associated to a
> + * given layer.
> + *
> + *@enabled: DMA channel status
> + *@lock: lock to access the DMA channel fields
> + *@cur: current DMA transfers (one for each plane/buffer)
> + *@queue: next DMA transfers, to be launch on next frame update
> + *@dscrs: allocated DMA descriptors
> + */
> +struct atmel_hlcdc_layer_dma_channel {
> + bool enabled;
> + spinlock_t lock;
> + struct atmel_hlcdc_dma_channel_dscr *cur[ATMEL_HLCDC_MAX_PLANES];
> + struct atmel_hlcdc_dma_channel_dscr *queue[ATMEL_HLCDC_MAX_PLANES];
> + 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: GEM flip garbage collector
> + *@update: update handler
> + */
> +struct atmel_hlcdc_layer {
> + const struct atmel_hlcdc_layer_desc *desc;
> + int max_planes;
> + struct atmel_hlcdc *hlcdc;
> + struct atmel_hlcdc_layer_dma_channel dma;
> + struct atmel_hlcdc_layer_gem_flip_gc gc;
> + struct atmel_hlcdc_layer_update update;
> +};
> +
> +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);
> +
> +int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
> + int plane_id,
> + struct drm_gem_cma_object *gem,
> + unsigned int offset);
> +
> +void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer);
> +
> +void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer);
> +
> +static inline bool
> +atmel_hlcdc_layer_dma_channel_busy(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (dma->queue[i])
> + return true;
> + }
> +
> + return false;
> +}
> +
> +#endif /* DRM_ATMEL_HLCDC_LAYER_H */
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
> new file mode 100644
> index 0000000..3295021
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
> @@ -0,0 +1,351 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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 <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,
> +};
> +
> +/**
> + * Atmel HLCDC Panel structure
> + *
> + * This structure stores informations about an DRM panel connected through
> + * the RGB connector.
> + *
> + * @mode: RGB output mode
> + * @connector: DRM connector
> + * @encoder: DRM encoder
> + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
> + * @panel: pointer to the attached DRM panel
> + * @pinctrl: pinctrl state used by the current RGB output mode
> + * @np: DRM panel DT node
> + * @dpms: current DPMS mode
> + */
> +struct atmel_hlcdc_panel {
> + enum atmel_hlcdc_connector_rgb_mode mode;
> + struct drm_connector connector;
> + struct drm_encoder encoder;
> + struct atmel_hlcdc *hlcdc;
> + struct drm_panel *panel;
> + struct pinctrl *pinctrl;
> + struct device_node *np;
> + int dpms;
> +};
> +
> +static const char * const pin_state_names[] = {
> + [ATMEL_HLCDC_CONNECTOR_RGB444] = "rgb-444",
> + [ATMEL_HLCDC_CONNECTOR_RGB565] = "rgb-565",
> + [ATMEL_HLCDC_CONNECTOR_RGB666] = "rgb-666",
> + [ATMEL_HLCDC_CONNECTOR_RGB888] = "rgb-888",
> +};
> +
> +static inline struct atmel_hlcdc_panel *
> +drm_connector_to_atmel_hlcdc_panel(struct drm_connector *connector)
> +{
> + return container_of(connector, struct atmel_hlcdc_panel, connector);
> +}
> +
> +static inline struct atmel_hlcdc_panel *
> +drm_encoder_to_atmel_hlcdc_panel(struct drm_encoder *encoder)
> +{
> + return container_of(encoder, struct atmel_hlcdc_panel, encoder);
> +}
> +
> +static void atmel_hlcdc_panel_encoder_dpms(struct drm_encoder *encoder,
> + int mode)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_encoder_to_atmel_hlcdc_panel(encoder);
> + struct regmap *regmap = panel->hlcdc->regmap;
> + unsigned int status;
> +
> + if (mode != DRM_MODE_DPMS_ON)
> + mode = DRM_MODE_DPMS_OFF;
> +
> + if (mode == panel->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(panel->hlcdc->sys_clk);
> + if (panel->panel)
> + drm_panel_disable(panel->panel);
> + } else {
> + if (panel->panel)
> + drm_panel_enable(panel->panel);
> + clk_prepare_enable(panel->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();
> + }
> +
> + panel->dpms = mode;
> +}
> +
> +static bool
> +atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder,
> + const struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted)
> +{
> + return true;
> +}
> +
> +static void atmel_hlcdc_panel_encoder_prepare(struct drm_encoder *encoder)
> +{
> + atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
> +}
> +
> +static void atmel_hlcdc_panel_encoder_commit(struct drm_encoder *encoder)
> +{
> + atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
> +}
> +
> +static void
> +atmel_hlcdc_panel_encoder_mode_set(struct drm_encoder *encoder,
> + struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_encoder_to_atmel_hlcdc_panel(encoder);
> + unsigned long prate = clk_get_rate(panel->hlcdc->sys_clk);
> + unsigned long mode_rate = mode->clock * 1000;
> + int div;
> + u32 cfg0 = 0;
> +
> + if ((prate / 2) < mode_rate) {
> + prate *= 2;
> + cfg0 |= ATMEL_HLCDC_CLKSEL;
> + }
> +
> + div = DIV_ROUND_UP(prate, mode_rate);
> + if (div < 2)
> + div = 2;
> +
> + cfg0 |= ATMEL_HLCDC_CLKDIV(div);
> +
> + regmap_update_bits(panel->hlcdc->regmap, ATMEL_HLCDC_CFG(0),
> + ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK, cfg0);
> +}
> +
> +static struct drm_encoder_helper_funcs encoder_helper_funcs = {
> + .dpms = atmel_hlcdc_panel_encoder_dpms,
> + .mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup,
> + .prepare = atmel_hlcdc_panel_encoder_prepare,
> + .commit = atmel_hlcdc_panel_encoder_commit,
> + .mode_set = atmel_hlcdc_panel_encoder_mode_set,
> +};
> +
> +static void atmel_hlcdc_panel_encoder_destroy(struct drm_encoder *encoder)
> +{
> + drm_encoder_cleanup(encoder);
> + memset(encoder, 0, sizeof(*encoder));
> +}
> +
> +static const struct drm_encoder_funcs encoder_funcs = {
> + .destroy = atmel_hlcdc_panel_encoder_destroy,
> +};
> +
> +static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> + int ret;
> +
> + if (!panel->panel)
> + return -ENOENT;
> +
> + ret = panel->panel->funcs->get_modes(panel->panel);
> + return ret;
> +}
> +
> +static int atmel_hlcdc_panel_mode_valid(struct drm_connector *connector,
> + struct drm_display_mode *mode)
> +{
> + return MODE_OK;
> +}
> +
> +static struct drm_encoder *
> +atmel_hlcdc_panel_best_encoder(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + return &panel->encoder;
> +}
> +
> +static struct drm_connector_helper_funcs connector_helper_funcs = {
> + .get_modes = atmel_hlcdc_panel_get_modes,
> + .mode_valid = atmel_hlcdc_panel_mode_valid,
> + .best_encoder = atmel_hlcdc_panel_best_encoder,
> +};
> +
> +static enum drm_connector_status
> +atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + if (!panel->panel) {
> + panel->panel = of_drm_find_panel(panel->np);
> + if (panel->panel)
> + drm_panel_attach(panel->panel, &panel->connector);
> + }
> +
> + if (panel->panel)
> + return connector_status_connected;
> +
> + return connector_status_disconnected;
> +}
> +
> +static void
> +atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + if (panel->panel)
> + drm_panel_detach(panel->panel);
> +
> + drm_sysfs_connector_remove(connector);
> + drm_connector_cleanup(connector);
> +
> + pinctrl_put(panel->pinctrl);
> +}
> +
> +static const struct drm_connector_funcs connector_funcs = {
> + .dpms = drm_helper_connector_dpms,
> + .detect = atmel_hlcdc_panel_connector_detect,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = atmel_hlcdc_panel_connector_destroy,
> +};
> +
> +int atmel_hlcdc_panel_create(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_dc *dc = dev->dev_private;
> + struct atmel_hlcdc_panel *panel;
> + struct of_phandle_args out_args;
> + u32 cfg;
> + int ret;
> +
> + panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL);
> + if (!panel)
> + return -ENOMEM;
> +
> + ret = of_parse_phandle_with_fixed_args(dev->dev->of_node,
> + "atmel,panel", 2, 0,
> + &out_args);
> + if (ret) {
> + dev_err(dev->dev, "failed to retrieve panel info from DT\n");
> + return ret;
> + }
> +
> + switch (out_args.args[0]) {
> + case ATMEL_HLCDC_CONNECTOR_RGB444:
> + case ATMEL_HLCDC_CONNECTOR_RGB565:
> + case ATMEL_HLCDC_CONNECTOR_RGB666:
> + case ATMEL_HLCDC_CONNECTOR_RGB888:
> + break;
> + default:
> + dev_err(dev->dev, "unknown RGB mode\n");
> + return -EINVAL;
> + }
> +
> + panel->np = out_args.np;
> + panel->mode = out_args.args[0];
> + panel->pinctrl = pinctrl_get_select(dev->dev,
> + pin_state_names[panel->mode]);
> + if (IS_ERR(panel->pinctrl)) {
> + dev_err(dev->dev, "could not request pinctrl state\n");
> + return PTR_ERR(panel->pinctrl);
> + }
> +
> + cfg = out_args.args[1] & (ATMEL_HLCDC_HSPOL |
> + ATMEL_HLCDC_VSPOL |
> + ATMEL_HLCDC_VSPDLYS |
> + ATMEL_HLCDC_VSPDLYE |
> + ATMEL_HLCDC_DISPPOL |
> + ATMEL_HLCDC_DISPDLY |
> + ATMEL_HLCDC_VSPSU |
> + ATMEL_HLCDC_VSPHO);
> + cfg |= panel->mode << 8;
> +
> + regmap_write(dc->hlcdc->regmap,
> + ATMEL_HLCDC_CFG(5),
> + cfg);
> +
> + panel->dpms = DRM_MODE_DPMS_OFF;
> +
> + panel->hlcdc = dc->hlcdc;
> +
> + drm_connector_init(dev, &panel->connector, &connector_funcs,
> + DRM_MODE_CONNECTOR_LVDS);
> + drm_connector_helper_add(&panel->connector, &connector_helper_funcs);
> + panel->connector.dpms = DRM_MODE_DPMS_OFF;
> + panel->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
> +
> + drm_encoder_init(dev, &panel->encoder, &encoder_funcs,
> + DRM_MODE_ENCODER_LVDS);
> + drm_encoder_helper_add(&panel->encoder, &encoder_helper_funcs);
> +
> + drm_mode_connector_attach_encoder(&panel->connector, &panel->encoder);
> + drm_sysfs_connector_add(&panel->connector);
> +
> + panel->encoder.possible_crtcs = 0x1;
> +
> + 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..f428b47
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> @@ -0,0 +1,658 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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_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_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_NV61,
> + DRM_FORMAT_YUV422,
> + DRM_FORMAT_NV21,
> +};
> +
> +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_NV61:
> + *mode = ATMEL_HLCDC_NV61_MODE;
> + break;
> + case DRM_FORMAT_YUV422:
> + *mode = ATMEL_HLCDC_YUV422_MODE;
> + break;
> + case DRM_FORMAT_NV21:
> + *mode = ATMEL_HLCDC_NV21_MODE;
> + break;
> + case DRM_FORMAT_YUV420:
> + *mode = ATMEL_HLCDC_YUV420_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,
> +};
> +
> +int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
> + struct drm_crtc *crtc,
> + 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)
> +{
> + const struct atmel_hlcdc_layer_cfg_layout *layout =
> + &plane->layer.desc->layout;
> +
> + if (!crtc_h || !crtc_w)
> + return -EINVAL;
> +
> + if (!layout->pos && (crtc_x || crtc_y))
> + return -EINVAL;
> +
> + if ((crtc_x + crtc_w) > crtc->mode.crtc_hdisplay ||
> + (crtc_y + crtc_h) > crtc->mode.crtc_vdisplay)
> + return -EINVAL;
> +
> + if (!layout->size &&
> + (crtc->mode.crtc_hdisplay != crtc_w ||
> + crtc->mode.crtc_vdisplay != crtc_h))
> + return -EINVAL;
> +
> + if (plane->layer.desc->max_height &&
> + crtc_h > plane->layer.desc->max_height)
> + return -EINVAL;
> +
> + if (plane->layer.desc->max_width &&
> + crtc_w > plane->layer.desc->max_width)
> + return -EINVAL;
> +
> + if (!layout->memsize && (crtc_h != src_h || crtc_w != src_w))
> + return -EINVAL;
> +
> + if (layout->size)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->size,
> + 0xffffffff,
> + (crtc_w - 1) |
> + ((crtc_h - 1) << 16));
> +
> + if (layout->memsize)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->memsize,
> + 0xffffffff,
> + (src_w - 1) |
> + ((src_h - 1) << 16));
> +
> + if (layout->pos)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->pos,
> + 0xffffffff,
> + crtc_x | (crtc_y << 16));
> +
> + /* TODO: rework the rescaling part */
> + if (crtc_w != src_w || crtc_h != src_h) {
> + u32 factor_reg = 0;
> +
> + if (crtc_w != src_w) {
> + int i;
> + u32 factor;
> + u32 *coeff_tab = heo_upscaling_xcoef;
> + u32 max_memsize;
> +
> + if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
> + factor++;
> + max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
> + if (max_memsize > src_w)
> + factor--;
> + factor_reg |= factor | 0x80000000;
> + }
> +
> + if (crtc_h != src_h) {
> + int i;
> + u32 factor;
> + u32 *coeff_tab = heo_upscaling_ycoef;
> + u32 max_memsize;
> +
> + if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
> + factor++;
> + max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
> + if (max_memsize > src_w)
> + factor--;
> + factor_reg |= (factor << 16) | 0x80000000;
> + }
> +
> + atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff,
> + factor_reg);
> + }
> +
> + return 0;
> +}
> +
> +void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
> + u32 format)
> +{
> + 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(format))
> + cfg |= ATMEL_HLCDC_LAYER_LAEN;
> + else
> + cfg |= (plane->alpha << ATMEL_HLCDC_LAYER_GA_SHIFT) |
> + 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 |
> + ATMEL_HLCDC_LAYER_GA_MASK, cfg);
> +}
> +
> +int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
> + u32 format)
> +{
> + u32 mode;
> + int ret;
> +
> + ret = atmel_hlcdc_format_to_plane_mode(format, &mode);
> + if (ret)
> + return ret;
> +
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + ATMEL_HLCDC_LAYER_FORMAT_CFG_ID,
> + 0xffffffff,
> + mode);
> +
> + return 0;
> +}
> +
> +int atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
> + u32 pixel_format,
> + struct drm_gem_cma_object **gems,
> + unsigned int *pitches,
> + unsigned int *offsets,
> + uint32_t src_x, uint32_t src_y,
> + uint32_t src_w, uint32_t src_h)
> +{
> + struct atmel_hlcdc_layer *layer = &plane->layer;
> + const struct atmel_hlcdc_layer_cfg_layout *layout =
> + &layer->desc->layout;
> + int bpp[ATMEL_HLCDC_MAX_PLANES];
> + int nplanes;
> + int ret = 0;
> + int i;
> +
> + nplanes = drm_format_num_planes(pixel_format);
> +
> + for (i = 0; i < nplanes; i++) {
> + bpp[i] = drm_format_plane_cpp(pixel_format, i);
> + if (!bpp[i])
> + return -EINVAL;
> +
> + if (!gems[i])
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < nplanes; i++) {
> + int offset;
> +
> + offset = offsets[i] + (src_y * pitches[i]) +
> + (src_x * bpp[i]);
> +
> + ret = atmel_hlcdc_layer_update_set_gem(&plane->layer, i,
> + gems[i], offset);
> + if (ret)
> + return ret;
> +
> + if (layout->xstride[i])
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->xstride[i],
> + 0xffffffff,
> + pitches[i] - (bpp[i] * src_w));
> + }
> +
> + 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 drm_gem_cma_object *gems[ATMEL_HLCDC_MAX_PLANES];
> + int nplanes;
> + int ret = 0;
> + int i;
> +
> + /* Subpixel positioning is not supported */
> + if ((src_x | src_y | src_w | src_h) & SUBPIXEL_MASK) {
> + DRM_DEBUG_KMS("Primary base does not support subpixel positioning\n");
> + return -EINVAL;
> + }
> +
> + src_h >>= 16;
> + src_w >>= 16;
> + src_x >>= 16;
> + src_y >>= 16;
> +
> + nplanes = drm_format_num_planes(fb->pixel_format);
> + if (nplanes > ATMEL_HLCDC_MAX_PLANES)
> + return -EINVAL;
> +
> + for (i = 0; i < nplanes; i++)
> + gems[i] = drm_fb_cma_get_gem_obj(fb, i);
> +
> + atmel_hlcdc_layer_update_start(&plane->layer);
> + ret = atmel_hlcdc_plane_update_pos_and_size(plane, crtc,
> + crtc_x, crtc_y,
> + crtc_w, crtc_h,
> + src_x, src_y,
> + src_w, src_h);
> + if (ret)
> + goto out;
> +
> + atmel_hlcdc_plane_update_general_settings(plane, fb->pixel_format);
> +
> + ret = atmel_hlcdc_plane_update_format(plane, fb->pixel_format);
> + if (ret)
> + goto out;
> +
> +
> + ret = atmel_hlcdc_plane_update_buffers(plane, fb->pixel_format,
> + gems,
> + fb->pitches, fb->offsets,
> + src_x, src_y,
> + src_w, src_h);
> +
> + if (ret)
> + goto out;
> +
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> + drm_framebuffer_reference(fb);
> + if (plane->base.fb)
> + drm_framebuffer_unreference(plane->base.fb);
> + plane->base.fb = fb;
> +
> + return 0;
> +
> +out:
> + atmel_hlcdc_layer_update_rollback(&plane->layer);
> +
> + return ret;
> +}
> +
> +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);
> + plane->alpha = alpha;
> + atmel_hlcdc_plane_update_general_settings(plane,
> + plane->base.fb->pixel_format);
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> + 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
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +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 *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);
> +
> + /* Set default property values*/
> + plane->alpha = 0xff;
> +
> + 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);
> +
> + return plane;
> +}
> +
> +static struct atmel_hlcdc_plane_properties *
> +atmel_hlcdc_plane_create_properties(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_plane_properties *props;
> +
> + 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);
> +
> + 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]);
> + 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;
> + drm_object_attach_property(&plane->base.base,
> + props->alpha, 255);
> + break;
> +
> + case ATMEL_HLCDC_CURSOR_LAYER:
> + if (planes->cursor)
> + return ERR_PTR(-EINVAL);
> + planes->cursor = plane;
> + drm_object_attach_property(&plane->base.base,
> + props->alpha, 255);
> + break;
> +
> + default:
> + break;
> + }
> + }
> +
> + return planes;
> +}
> diff --git a/drivers/gpu/drm/atmel_hlcdc/Kconfig b/drivers/gpu/drm/atmel_hlcdc/Kconfig
> new file mode 100644
> index 0000000..59c8eeb
> --- /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 && ARM && 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..08de8d7
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel_hlcdc/Makefile
> @@ -0,0 +1,8 @@
> +atmel_hlcdc-y := atmel_hlcdc_crtc.o \
> + atmel_hlcdc_drv.o \
> + atmel_hlcdc_layer.o \
> + atmel_hlcdc_panel.o \
> + atmel_hlcdc_plane.o \
> + atmel_hlcdc_pwm.o
> +
> +obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel_hlcdc.o
>
Hello JJ,
On 15/06/2014 10:53, Jean-Jacques Hiblot wrote:
> On 06/09/2014 06:04 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
>>
>> Add support for the MFD device which will just retrieve HLCDC clocks and
>> create a regmap so that subdevices can access the HLCDC register range
>> concurrently.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> .../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++++++++
>> drivers/mfd/Kconfig | 11 ++
>> drivers/mfd/Makefile | 1 +
[...]
>> + memset(&config, 0, sizeof(config));
>> + config.reg_bits = 32;
>> + config.val_bits = 32;
>> + config.reg_stride = 4;
>> + config.max_register = (resource_size(res) / 4) - 1;
>> + hlcdc->regmap = devm_regmap_init_mmio_clk(dev, "periph_clk", regs,
>> + &config);
> I don't think it's necessary to use "periph_clk" here. This clock will
> always be running because the HLCDC needs it to work (it's not just an
> interface clock). In the end it's just some extra work for each register
> access.
Yes, I thought about removing this clk from regmap registration too (for
the exact same reason: avoiding extra enable/disable work when accessing
registers), but ATM I do not prepare/enable periph_clk in the hlcdc-pwm
driver, this means the regmap won't work until the hlcdc-dc driver has
probed the display controller device.
How about preparing/enabling the periph_clk in the MFD device, so that
PWM and Display Controller subdevices won't have to bother about this
clk, and the regmap will work as expected ?
Or, should we just prepare/enable the periph clock in each subdevices ?
Best Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
Hello JJ,
On 15/06/2014 11:11, Jean-Jacques Hiblot wrote:
>
> On 06/09/2014 06:04 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.
>>
>> This driver add support for this PWM device.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> .../devicetree/bindings/pwm/atmel-hlcdc-pwm.txt | 40 ++++
>> drivers/pwm/Kconfig | 9 +
>> drivers/pwm/Makefile | 1 +
>> drivers/pwm/pwm-atmel-hlcdc.c | 216 +++++++++++++++++++++
>> 4 files changed, 266 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
>> create mode 100644 drivers/pwm/pwm-atmel-hlcdc.c
>>
>> 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..5e2ba87
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
>> @@ -0,0 +1,40 @@
[...]
>> +++ 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
> I'd personnaly prefer a 'select' instead of 'depends on' here. Or maybe
> the MFD driver should enabled y defaut for platforms supporting the hlcdc.
I don't have any strong opinion on the "select" vs "depends on" approach.
Does anyone else think we should use a 'select' instead of a 'depends on' ?
I see at least one benefit, we would be able to see the HLCDC_PWM option
even if the HLCDC driver is not enabled.
Best Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
Hello JJ,
On 15/06/2014 11:32, Jean-Jacques Hiblot wrote:
>
> On 06/09/2014 06:04 PM, Boris BREZILLON wrote:
>> The Atmel HLCDC (High LCD Controller) IP available on some Atmel SoCs (i.e.
>> at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display
>> controller device.
>>
>> This display controller support at least one primary plane and might
>> provide several overlays and an hardware cursor depending on the IP
>> version.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
[...]
>> + vm.vfront_porch > 0x40 || vm.vfront_porch < 0 ||
>> + vm.vback_porch > 0x40 || vm.vback_porch < 0 ||
>> + vm.hfront_porch > 0x200 || vm.hfront_porch < 0 ||
>> + vm.hback_porch > 0x200 || vm.hback_porch < 0 ||
>> + mode->hdisplay > 2048 || mode->hdisplay < 0 ||
>> + mode->vdisplay > 2048 || mode->vdisplay < 0)
>> + 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 - 1) << 16));
> Acording to the datasheet, it's vm.vback_porch instead of
> (vm.vback_porch -1).
Oh, nice catch!
I'll check with Atmel that this is not a typo in their datasheet,
because all other fields need the minus 1.
Thanks for your review.
Best Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
2014-06-15 16:48 GMT+02:00 Boris BREZILLON <[email protected]>:
>
>
> Hello JJ,
>
> On 15/06/2014 10:53, Jean-Jacques Hiblot wrote:
> > On 06/09/2014 06:04 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
> >>
> >> Add support for the MFD device which will just retrieve HLCDC clocks and
> >> create a regmap so that subdevices can access the HLCDC register range
> >> concurrently.
> >>
> >> Signed-off-by: Boris BREZILLON <[email protected]>
> >> ---
> >> .../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++++++++
> >> drivers/mfd/Kconfig | 11 ++
> >> drivers/mfd/Makefile | 1 +
> [...]
> >> + memset(&config, 0, sizeof(config));
> >> + config.reg_bits = 32;
> >> + config.val_bits = 32;
> >> + config.reg_stride = 4;
> >> + config.max_register = (resource_size(res) / 4) - 1;
> >> + hlcdc->regmap = devm_regmap_init_mmio_clk(dev, "periph_clk", regs,
> >> + &config);
> > I don't think it's necessary to use "periph_clk" here. This clock will
> > always be running because the HLCDC needs it to work (it's not just an
> > interface clock). In the end it's just some extra work for each register
> > access.
>
> Yes, I thought about removing this clk from regmap registration too (for
> the exact same reason: avoiding extra enable/disable work when accessing
> registers), but ATM I do not prepare/enable periph_clk in the hlcdc-pwm
> driver, this means the regmap won't work until the hlcdc-dc driver has
> probed the display controller device.
>
> How about preparing/enabling the periph_clk in the MFD device, so that
> PWM and Display Controller subdevices won't have to bother about this
> clk, and the regmap will work as expected ?
> Or, should we just prepare/enable the periph clock in each subdevices ?
I think the latest is the best approach. This way the PWM and the DRM
driver can handle their clock gating independently. BTW it's quite
probable that the PWM don't really needs this clock except for
register access.
>
>
>
> Best Regards,
>
> Boris
>
> --
> Boris Brezillon, Free Electrons
> Embedded Linux and Kernel engineering
> http://free-electrons.com
>
> 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
>
> Add support for the MFD device which will just retrieve HLCDC clocks and
> create a regmap so that subdevices can access the HLCDC register range
> concurrently.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> .../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++++++++
> drivers/mfd/Kconfig | 11 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/atmel-hlcdc.c | 116 +++++++++++++++++++++
> include/linux/mfd/atmel-hlcdc.h | 78 ++++++++++++++
> 5 files changed, 247 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> create mode 100644 drivers/mfd/atmel-hlcdc.c
> create mode 100644 include/linux/mfd/atmel-hlcdc.h
> diff --git a/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> new file mode 100644
> index 0000000..f5b69cb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> @@ -0,0 +1,41 @@
> +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 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
> + - a Display Controller: see Documentation/devicetree/bindings/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";
Not sure you really want to disable the the node in the example.
> + hlcdc-display-controller {
> + compatible = "atmel,hlcdc-dc";
> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
I assume you're using the 3rd parameter for flags. If so, please use
the defines.
> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
> + pinctrl-0 = <&pinctrl_lcd_base>;
> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
> + };
> +
> + hlcdc_pwm: hlcdc-pwm {
> + compatible = "atmel,hlcdc-pwm";
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_lcd_pwm>;
> + #pwm-cells = <3>;
> + };
> + };
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index ee8204c..82777f6 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -59,6 +59,17 @@ 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 (High LCD Controller)"
What's the difference between a high and a low controller?
> + select MFD_CORE
> + select REGMAP_MMIO
> + help
> + Choose this option if you have an ATMEL SoC with an HLCDC (High
> + LCD 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..e4636e8
> --- /dev/null
> +++ b/drivers/mfd/atmel-hlcdc.c
> @@ -0,0 +1,116 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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/mfd/atmel-hlcdc.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
Looks like you're inheriting the clk and regmap header files - don't
do that. Each source file which uses the interfaces should include
them independently.
> +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-dc",
> + },
> +};
> +
> +static int atmel_hlcdc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct regmap_config config;
> + 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 functional 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 functional clock\n");
Can you be more descriptive? This error message is the same as the
one above. How will a user know which one has failed?
> + 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);
> + }
> +
> + memset(&config, 0, sizeof(config));
> + config.reg_bits = 32;
> + config.val_bits = 32;
> + config.reg_stride = 4;
> + config.max_register = (resource_size(res) / 4) - 1;
I'd prefer you did this as a separate struct, like everyone else
does.
> + hlcdc->regmap = devm_regmap_init_mmio_clk(dev, "periph_clk", regs,
> + &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);
> +
> + dev_set_drvdata(&pdev->dev, NULL);
This is done automatically for you - you can remove it.
> + return 0;
> +}
> +
> +static const struct of_device_id atmel_hlcdc_match[] = {
> + { .compatible = "atmel,sama5d3-hlcdc" },
> + { },
> +};
> +
> +static struct platform_driver atmel_hlcdc_driver = {
> + .probe = atmel_hlcdc_probe,
> + .remove = atmel_hlcdc_remove,
> + .driver = {
> + .name = "atmel-hlcdc",
> + .owner = THIS_MODULE,
> + .of_match_table = atmel_hlcdc_match,
Is this driver DT only?
> + },
> +};
> +module_platform_driver(atmel_hlcdc_driver);
> +
> +MODULE_ALIAS("platform:atmel-hlcdc");
> +MODULE_AUTHOR("Boris Brezillon <[email protected]>");
> +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..d7a5589
> --- /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 <[email protected]>
> + *
> + * 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 0x300
> +#define ATMEL_HLCDC_PP BIT(10)
> +#define ATMEL_HLCDC_VSPSU BIT(12)
> +#define ATMEL_HLCDC_VSPHO BIT(13)
> +#define ATMEL_HLCDC_GUARDTIME_MASK 0x1f0000
There should only be one space after '#define'.
> +#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 (0xff << ATMEL_HLCDC_CLKDIV_SHFT)
> +#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 */
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
Hello Lee,
On 16/06/2014 14:50, Lee Jones 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
>>
>> Add support for the MFD device which will just retrieve HLCDC clocks and
>> create a regmap so that subdevices can access the HLCDC register range
>> concurrently.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> .../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++++++++
>> drivers/mfd/Kconfig | 11 ++
>> drivers/mfd/Makefile | 1 +
>> drivers/mfd/atmel-hlcdc.c | 116 +++++++++++++++++++++
>> include/linux/mfd/atmel-hlcdc.h | 78 ++++++++++++++
>> 5 files changed, 247 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
>> create mode 100644 drivers/mfd/atmel-hlcdc.c
>> create mode 100644 include/linux/mfd/atmel-hlcdc.h
>> diff --git a/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
>> new file mode 100644
>> index 0000000..f5b69cb
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
>> @@ -0,0 +1,41 @@
>> +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 Documentation/devicetree/bindings/pwm/atmel-hlcdc-pwm.txt
>> + - a Display Controller: see Documentation/devicetree/bindings/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";
> Not sure you really want to disable the the node in the example.
Nope, I'll remove this line.
>> + hlcdc-display-controller {
>> + compatible = "atmel,hlcdc-dc";
>> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
> I assume you're using the 3rd parameter for flags. If so, please use
> the defines.
No, the third parameter encodes the irq priority (from 0 to 7 IIRC).
>> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
>> + pinctrl-0 = <&pinctrl_lcd_base>;
>> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
>> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
>> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
>> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
>> + };
>> +
>> + hlcdc_pwm: hlcdc-pwm {
>> + compatible = "atmel,hlcdc-pwm";
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pinctrl_lcd_pwm>;
>> + #pwm-cells = <3>;
>> + };
>> + };
>> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
>> index ee8204c..82777f6 100644
>> --- a/drivers/mfd/Kconfig
>> +++ b/drivers/mfd/Kconfig
>> @@ -59,6 +59,17 @@ 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 (High LCD Controller)"
> What's the difference between a high and a low controller?
I don't know exactly.
I guess the name changed when the LCD Controller design changed.
Maybe "High-end LCD Controller" would be better...
Nicolas, can you help me on this one ?
>
>> + select MFD_CORE
>> + select REGMAP_MMIO
>> + help
>> + Choose this option if you have an ATMEL SoC with an HLCDC (High
>> + LCD 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..e4636e8
>> --- /dev/null
>> +++ b/drivers/mfd/atmel-hlcdc.c
>> @@ -0,0 +1,116 @@
>> +/*
>> + * Copyright (C) 2014 Free Electrons
>> + * Copyright (C) 2014 Atmel
>> + *
>> + * Author: Boris BREZILLON <[email protected]>
>> + *
>> + * 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/mfd/atmel-hlcdc.h>
>> +#include <linux/mfd/core.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
> Looks like you're inheriting the clk and regmap header files - don't
> do that. Each source file which uses the interfaces should include
> them independently.
Okay, I'll explicitly include clk and regmap headers.
>
>> +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-dc",
>> + },
>> +};
>> +
>> +static int atmel_hlcdc_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct regmap_config config;
>> + 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 functional 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 functional clock\n");
> Can you be more descriptive? This error message is the same as the
> one above. How will a user know which one has failed?
This is a copy/paste error. I'll fix that.
>
>> + 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);
>> + }
>> +
>> + memset(&config, 0, sizeof(config));
>> + config.reg_bits = 32;
>> + config.val_bits = 32;
>> + config.reg_stride = 4;
>> + config.max_register = (resource_size(res) / 4) - 1;
> I'd prefer you did this as a separate struct, like everyone else
> does.
I tend to avoid static structures when they're used to initialize
configurable things (here max_register depends on the resource size).
This prevent getting the structure filled with dirty values from
previous probe calls.
Moreover, AFAIK, all the fields set in config are copied to the regmap
struct during registration, and config pointer is never used after the
registration has completed.
Anyway, I can define a static struct and dynamically modify max_register.
>> + hlcdc->regmap = devm_regmap_init_mmio_clk(dev, "periph_clk", regs,
>> + &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);
>> +
>> + dev_set_drvdata(&pdev->dev, NULL);
> This is done automatically for you - you can remove it.
Okay.
>
>> + return 0;
>> +}
>> +
>> +static const struct of_device_id atmel_hlcdc_match[] = {
>> + { .compatible = "atmel,sama5d3-hlcdc" },
>> + { },
>> +};
>> +
>> +static struct platform_driver atmel_hlcdc_driver = {
>> + .probe = atmel_hlcdc_probe,
>> + .remove = atmel_hlcdc_remove,
>> + .driver = {
>> + .name = "atmel-hlcdc",
>> + .owner = THIS_MODULE,
>> + .of_match_table = atmel_hlcdc_match,
> Is this driver DT only?
Yes it is.
>
>> + },
>> +};
>> +module_platform_driver(atmel_hlcdc_driver);
>> +
>> +MODULE_ALIAS("platform:atmel-hlcdc");
>> +MODULE_AUTHOR("Boris Brezillon <[email protected]>");
>> +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..d7a5589
>> --- /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 <[email protected]>
>> + *
>> + * 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 0x300
>> +#define ATMEL_HLCDC_PP BIT(10)
>> +#define ATMEL_HLCDC_VSPSU BIT(12)
>> +#define ATMEL_HLCDC_VSPHO BIT(13)
>> +#define ATMEL_HLCDC_GUARDTIME_MASK 0x1f0000
> There should only be one space after '#define'.
I'll fix that.
Thanks for your review.
Best Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
On Mon, 16 Jun 2014, Boris BREZILLON wrote:
> On 16/06/2014 14:50, Lee Jones 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
> >>
> >> Add support for the MFD device which will just retrieve HLCDC clocks and
> >> create a regmap so that subdevices can access the HLCDC register range
> >> concurrently.
> >>
> >> Signed-off-by: Boris BREZILLON <[email protected]>
> >> ---
> >> .../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++++++++
> >> drivers/mfd/Kconfig | 11 ++
> >> drivers/mfd/Makefile | 1 +
> >> drivers/mfd/atmel-hlcdc.c | 116 +++++++++++++++++++++
> >> include/linux/mfd/atmel-hlcdc.h | 78 ++++++++++++++
> >> 5 files changed, 247 insertions(+)
> >> create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> >> create mode 100644 drivers/mfd/atmel-hlcdc.c
> >> create mode 100644 include/linux/mfd/atmel-hlcdc.h
> >> diff --git a/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> >> new file mode 100644
> >> index 0000000..f5b69cb
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
[...]
> >> + hlcdc-display-controller {
> >> + compatible = "atmel,hlcdc-dc";
> >> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
> > I assume you're using the 3rd parameter for flags. If so, please use
> > the defines.
>
> No, the third parameter encodes the irq priority (from 0 to 7 IIRC).
Ah okay. Can you point me to the documentation for this IRQ
controller please? I'd like to have a quick peek. It might be worth
defining the priority to prevent confusion, also you definitely should
document it in your binding.
[...]
> >> +static struct platform_driver atmel_hlcdc_driver = {
> >> + .probe = atmel_hlcdc_probe,
> >> + .remove = atmel_hlcdc_remove,
> >> + .driver = {
> >> + .name = "atmel-hlcdc",
> >> + .owner = THIS_MODULE,
> >> + .of_match_table = atmel_hlcdc_match,
> > Is this driver DT only?
>
> Yes it is.
So it should depend on OF in the Kconfig entry.
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
On 16/06/2014 19:03, Lee Jones wrote:
> On Mon, 16 Jun 2014, Boris BREZILLON wrote:
>> On 16/06/2014 14:50, Lee Jones 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
>>>>
>>>> Add support for the MFD device which will just retrieve HLCDC clocks and
>>>> create a regmap so that subdevices can access the HLCDC register range
>>>> concurrently.
>>>>
>>>> Signed-off-by: Boris BREZILLON <[email protected]>
>>>> ---
>>>> .../devicetree/bindings/mfd/atmel-hlcdc.txt | 41 ++++++++
>>>> drivers/mfd/Kconfig | 11 ++
>>>> drivers/mfd/Makefile | 1 +
>>>> drivers/mfd/atmel-hlcdc.c | 116 +++++++++++++++++++++
>>>> include/linux/mfd/atmel-hlcdc.h | 78 ++++++++++++++
>>>> 5 files changed, 247 insertions(+)
>>>> create mode 100644 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
>>>> create mode 100644 drivers/mfd/atmel-hlcdc.c
>>>> create mode 100644 include/linux/mfd/atmel-hlcdc.h
>>>> diff --git a/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
>>>> new file mode 100644
>>>> index 0000000..f5b69cb
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt
> [...]
>
>>>> + hlcdc-display-controller {
>>>> + compatible = "atmel,hlcdc-dc";
>>>> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
>>> I assume you're using the 3rd parameter for flags. If so, please use
>>> the defines.
>> No, the third parameter encodes the irq priority (from 0 to 7 IIRC).
> Ah okay. Can you point me to the documentation for this IRQ
> controller please? I'd like to have a quick peek. It might be worth
> defining the priority to prevent confusion, also you definitely should
> document it in your binding.
Here's Atmel's irq chip DT bindings documentation:
http://lxr.free-electrons.com/source/Documentation/devicetree/bindings/arm/atmel-aic.txt
I'll describe interrupt fields in the HLCDC DT bindings doc.
>
> [...]
>
>>>> +static struct platform_driver atmel_hlcdc_driver = {
>>>> + .probe = atmel_hlcdc_probe,
>>>> + .remove = atmel_hlcdc_remove,
>>>> + .driver = {
>>>> + .name = "atmel-hlcdc",
>>>> + .owner = THIS_MODULE,
>>>> + .of_match_table = atmel_hlcdc_match,
>>> Is this driver DT only?
>> Yes it is.
> So it should depend on OF in the Kconfig entry.
>
Absolutely, I'll add the dependency.
Thanks,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
On 09/06/2014 18:04, Boris BREZILLON wrote:
> The Atmel HLCDC (High LCD Controller) IP available on some Atmel SoCs (i.e.
> at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display
> controller device.
>
> This display controller support at least one primary plane and might
> provide several overlays and an hardware cursor depending on the IP
> version.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> .../devicetree/bindings/drm/atmel-hlcdc-dc.txt | 59 ++
> 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 | 529 ++++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 477 ++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 178 ++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 701 +++++++++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 417 ++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c | 351 +++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 658 +++++++++++++++++++
> drivers/gpu/drm/atmel_hlcdc/Kconfig | 11 +
> drivers/gpu/drm/atmel_hlcdc/Makefile | 8 +
These two files should not be part of the driver. I'll fix that.
> 14 files changed, 3410 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.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_panel.c
> create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> create mode 100644 drivers/gpu/drm/atmel_hlcdc/Kconfig
> create mode 100644 drivers/gpu/drm/atmel_hlcdc/Makefile
>
> 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..594bdb2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt
> @@ -0,0 +1,59 @@
> +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 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt for more details.
> +
> +Required properties:
> + - compatible: value should be one of the following:
> + "atmel,hlcdc-dc"
> + - interrupts: the HLCDC interrupt definition
> + - pinctrl-names: the pin control state names. Should contain "default",
> + "rgb-444", "rgb-565", "rgb-666" and "rgb-888".
> + - pinctrl-[0-4]: should contain the pinctrl states described by pinctrl
> + names.
> + - atmel,panel: Should contain a phandle with 2 parameters.
> + The first cell is a phandle to a DRM panel device
> + The second cell encodes the RGB mode, which can take the following values:
> + * 0: RGB444
> + * 1: RGB565
> + * 2: RGB666
> + * 3: RGB888
> + The third cell encodes specific flags describing LCD signals configuration
> + (see Atmel's datasheet for a full description of these fields):
> + * bit 0: HSPOL: Horizontal Synchronization Pulse Polarity
> + * bit 1: VSPOL: Vertical Synchronization Pulse Polarity
> + * bit 2: VSPDLYS: Vertical Synchronization Pulse Start
> + * bit 3: VSPDLYE: Vertical Synchronization Pulse End
> + * bit 4: DISPPOL: Display Signal Polarity
> + * bit 7: DISPDLY: LCD Controller Display Power Signal Synchronization
> + * bit 12: VSPSU: LCD Controller Vertical synchronization Pulse Setup Configuration
> + * bit 13: VSPHO: LCD Controller Vertical synchronization Pulse Hold Configuration
> + * bit 16-20: GUARDTIME: LCD DISPLAY Guard Time
> +
> +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-dc";
> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
> + pinctrl-0 = <&pinctrl_lcd_base>;
> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
> + };
> +
> + hlcdc_pwm: hlcdc-pwm {
> + compatible = "atmel,hlcdc-pwm";
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_lcd_pwm>;
> + #pwm-cells = <3>;
> + };
> + };
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index d1cc2f6..df6f0c1 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -182,6 +182,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 48e38ba..28c8a61 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -54,6 +54,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..bf9fe0b
> --- /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_panel.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..a18492e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
> @@ -0,0 +1,529 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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"
> +
> +/**
> + * Structure storing hardware cursor informations like its position, its size
> + * or the GEM object currently used to display the HW cursor.
> + *
> + * @gem: the cursor GEM object
> + * @pitch: GEM object pitch
> + * @height: cursor height in pixels
> + * @width: cursor width in pixels
> + * @x: cursor x position
> + * @y: cursor y position
> + * @offset: start offset from the provided buffer
> + * @patched_height: patched height value after adapting the image size
> + * depending on cursor position
> + * @patched_width: patched width value after adapting the image size depending
> + * on cursor position
> + * @patched_x: patched x value after adapting to cursor position (positive
> + * value)
> + * @patched_y: patched x value after adapting to cursor position (positive
> + * value)
> + */
> +struct atmel_hlcdc_crtc_cursor_info {
> + struct drm_gem_cma_object *gem;
> + unsigned int pitch;
> + u32 height;
> + u32 width;
> + int x;
> + int y;
> +
> + /*
> + * These fields are automatically calculated by
> + * atmel_hlcdc_crtc_cursor_prepare_req.
> + */
> + unsigned int offset;
> + u32 patched_height;
> + u32 patched_width;
> + int patched_x;
> + int patched_y;
> +};
> +
> +/**
> + * Structure storing HW cursor status.
> + *
> + * @status: the current cursor status
> + * @req: the requested cursor changes
> + * @plane: the hardware cursor plane
> + * @lock: cursor lock held when modifying cursor req or status
> + */
> +struct atmel_hlcdc_crtc_cursor {
> + struct atmel_hlcdc_crtc_cursor_info status;
> + struct atmel_hlcdc_crtc_cursor_info req;
> + struct atmel_hlcdc_plane *plane;
> + struct mutex lock;
> +};
> +
> +/**
> + * 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
> + * @cursor: hardware cursor status
> + */
> +struct atmel_hlcdc_crtc {
> + struct drm_crtc base;
> + struct atmel_hlcdc *hlcdc;
> + struct drm_pending_vblank_event *event;
> + int id;
> + int dpms;
> + struct atmel_hlcdc_crtc_cursor cursor;
> +};
> +
> +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 < 0 ||
> + vm.vsync_len > 0x40 || vm.vsync_len < 0 ||
> + vm.vfront_porch > 0x40 || vm.vfront_porch < 0 ||
> + vm.vback_porch > 0x40 || vm.vback_porch < 0 ||
> + vm.hfront_porch > 0x200 || vm.hfront_porch < 0 ||
> + vm.hback_porch > 0x200 || vm.hback_porch < 0 ||
> + mode->hdisplay > 2048 || mode->hdisplay < 0 ||
> + mode->vdisplay > 2048 || mode->vdisplay < 0)
> + 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 - 1) << 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 drm_plane *plane = c->primary;
> +
> + if (crtc->event)
> + return -EBUSY;
> +
> + if (event) {
> + crtc->event = event;
> + drm_vblank_get(c->dev, crtc->id);
> + }
> +
> + return plane->funcs->update_plane(plane, c, fb,
> + 0, 0,
> + c->mode.hdisplay, c->mode.vdisplay,
> + c->x << 16, c->y << 16,
> + c->mode.hdisplay << 16,
> + c->mode.vdisplay << 16);
> +}
> +
> +static void atmel_hlcdc_crtc_cursor_prepare_req(struct atmel_hlcdc_crtc *crtc)
> +{
> + int bpp = drm_format_plane_cpp(DRM_FORMAT_ARGB8888, 0);
> + struct atmel_hlcdc_crtc_cursor_info *info = &crtc->cursor.req;
> + int x_offset = 0;
> + int y_offset = 0;
> +
> + if (!info->gem) {
> + info->width = 0;
> + info->height = 0;
> + info->pitch = 0;
> + info->offset = 0;
> + return;
> + }
> +
> + info->pitch = info->width * bpp;
> +
> + if (info->x + info->width > crtc->base.mode.hdisplay)
> + info->patched_width = crtc->base.mode.hdisplay - info->x;
> + else
> + info->patched_width = info->width;
> +
> + if (info->x < 0) {
> + info->patched_width += info->x;
> + x_offset = -info->x;
> + info->patched_x = 0;
> + } else {
> + info->patched_x = info->x;
> + }
> +
> + if (info->y + info->height > crtc->base.mode.vdisplay)
> + info->patched_height = crtc->base.mode.vdisplay - info->y;
> + else
> + info->patched_height = info->height;
> +
> + if (info->y < 0) {
> + info->patched_height += info->y;
> + y_offset = -info->y;
> + info->patched_y = 0;
> + } else {
> + info->patched_y = info->y;
> + }
> +
> + info->offset = (x_offset * bpp) +
> + (y_offset * info->pitch);
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_apply_req(struct atmel_hlcdc_crtc *crtc)
> +{
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + struct atmel_hlcdc_crtc_cursor_info *req = &crtc->cursor.req;
> + struct atmel_hlcdc_crtc_cursor_info *status = &crtc->cursor.status;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + if (!req->gem ||
> + !req->patched_width || !req->patched_height) {
> + ret = plane->base.funcs->disable_plane(&plane->base);
> +
> + if (!ret)
> + goto out;
> + else
> + goto err;
> + }
> +
> + ret = atmel_hlcdc_layer_update_start(&plane->layer);
> + if (ret)
> + return ret;
> +
> + atmel_hlcdc_plane_update_general_settings(plane, DRM_FORMAT_ARGB8888);
> +
> + ret = atmel_hlcdc_plane_update_format(plane, DRM_FORMAT_ARGB8888);
> + if (ret)
> + goto err;
> +
> + ret = atmel_hlcdc_plane_update_pos_and_size(plane, &crtc->base,
> + req->patched_x,
> + req->patched_y,
> + req->patched_width,
> + req->patched_height,
> + 0, 0,
> + req->patched_width,
> + req->patched_height);
> + if (ret)
> + goto err;
> +
> + ret = atmel_hlcdc_plane_update_buffers(plane,
> + DRM_FORMAT_ARGB8888,
> + &req->gem,
> + &req->pitch,
> + &req->offset,
> + 0, 0,
> + req->patched_width,
> + req->height);
> + if (ret)
> + goto err;
> +
> + if (!plane->base.crtc)
> + plane->base.crtc = &crtc->base;
> +
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> +out:
> + if (req->gem)
> + drm_gem_object_reference(&req->gem->base);
> +
> + if (status->gem)
> + drm_gem_object_unreference_unlocked(&status->gem->base);
> +
> + *status = *req;
> +
> + return 0;
> +
> +err:
> + atmel_hlcdc_layer_update_rollback(&plane->layer);
> + return ret;
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_set(struct drm_crtc *c,
> + struct drm_file *file_priv,
> + uint32_t handle,
> + uint32_t width, uint32_t height)
> +{
> + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + struct drm_gem_cma_object *cma_gem = NULL;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + mutex_lock(&crtc->cursor.lock);
> +
> + if (handle) {
> + struct drm_gem_object *gem;
> +
> + gem = drm_gem_object_lookup(c->dev, file_priv, handle);
> + if (unlikely(!gem)) {
> + ret = -ENOENT;
> + goto out;
> + }
> +
> + cma_gem = to_drm_gem_cma_obj(gem);
> + }
> +
> + crtc->cursor.req = crtc->cursor.status;
> +
> + crtc->cursor.req.gem = cma_gem;
> + crtc->cursor.req.width = width;
> + crtc->cursor.req.height = height;
> + atmel_hlcdc_crtc_cursor_prepare_req(crtc);
> +
> + ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
> + if (cma_gem)
> + drm_gem_object_unreference_unlocked(&cma_gem->base);
> +
> +out:
> + mutex_unlock(&crtc->cursor.lock);
> +
> + return ret;
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_move(struct drm_crtc *c, int x, int y)
> +{
> + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + mutex_lock(&crtc->cursor.lock);
> + /*
> + * If there's no cursor update pending, get the current
> + * cursor size and buffer.
> + */
> + crtc->cursor.req = crtc->cursor.status;
> +
> + crtc->cursor.req.x = x;
> + crtc->cursor.req.y = y;
> + atmel_hlcdc_crtc_cursor_prepare_req(crtc);
> +
> + ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
> + mutex_unlock(&crtc->cursor.lock);
> +
> + 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,
> +};
> +
> +static const struct drm_crtc_funcs atmel_hlcdc_crtc_with_cursor_funcs = {
> + .page_flip = atmel_hlcdc_crtc_page_flip,
> + .set_config = drm_crtc_helper_set_config,
> + .destroy = atmel_hlcdc_crtc_destroy,
> + .cursor_set = atmel_hlcdc_crtc_cursor_set,
> + .cursor_move = atmel_hlcdc_crtc_cursor_move,
> +};
> +
> +int atmel_hlcdc_crtc_create(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_dc *dc = dev->dev_private;
> + struct atmel_hlcdc_planes *planes = dc->planes;
> + const struct drm_crtc_funcs *funcs;
> + 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;
> + }
> +
> + mutex_init(&crtc->cursor.lock);
> + crtc->hlcdc = dc->hlcdc;
> + crtc->cursor.plane = planes->cursor;
> +
> + if (planes->cursor)
> + funcs = &atmel_hlcdc_crtc_with_cursor_funcs;
> + else
> + funcs = &atmel_hlcdc_crtc_funcs;
> +
> + ret = drm_crtc_init_with_planes(dev, &crtc->base,
> + &planes->primary->base,
> + planes->cursor ? &planes->cursor->base : NULL,
> + funcs);
> + if (ret < 0)
> + goto fail;
> +
> + atmel_hlcdc_layer_set_finished(&planes->primary->layer,
> + atmel_hlcdc_crtc_finish_page_flip,
> + crtc);
> +
> + 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..e4ce24e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
> @@ -0,0 +1,477 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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 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);
> +}
> +
> +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_panel_create(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 atmel_hlcdc_dc_desc *desc;
> + struct atmel_hlcdc_dc *dc;
> + int ret;
> +
> + desc = platform_get_drvdata(pdev);
> +
> + dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL);
> + if (!dc) {
> + dev_err(dev->dev, "failed to allocate private data\n");
> + return -ENOMEM;
> + }
> +
> + dc->desc = platform_get_drvdata(pdev);
> + 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");
> + return ret;
> + }
> +
> + 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);
> + 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);
> +
> + dc->fbdev = drm_fbdev_cma_init(dev, 24,
> + dev->mode_config.num_crtc,
> + dev->mode_config.num_connector);
> +
> + if (IS_ERR(dc->fbdev)) {
> + ret = PTR_ERR(dc->fbdev);
> + goto err_periph_clk_disable;
> + }
> +
> + return 0;
> +
> +err_periph_clk_disable:
> + clk_disable_unprepare(dc->hlcdc->periph_clk);
> +
> + 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);
> +
> + 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 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,
> + },
> + },
> + {
> + .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 int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
> +{
> + const struct of_device_id *match;
> + int ret;
> +
> + match = of_match_node(atmel_hlcdc_of_match, pdev->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;
> + }
> +
> + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, (void *)match->data);
> +
> + 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-dc" },
> + { },
> +};
> +
> +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-dc",
> + .owner = THIS_MODULE,
> + .of_match_table = atmel_hlcdc_dc_of_match,
> + },
> +};
> +module_platform_driver(atmel_hlcdc_dc_platform_driver);
> +
> +MODULE_AUTHOR("Jean-Jacques Hiblot <[email protected]>");
> +MODULE_AUTHOR("Boris BREZILLON <[email protected]>");
> +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..5d2919e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
> @@ -0,0 +1,178 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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
> + */
> +struct atmel_hlcdc_plane_properties {
> + struct drm_property *alpha;
> +};
> +
> +/**
> + * 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;
> + u8 alpha;
> +};
> +
> +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 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
> + */
> +struct atmel_hlcdc_dc {
> + 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];
> +};
> +
> +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_update_buffers(struct atmel_hlcdc_plane *plane,
> + u32 pixel_format,
> + struct drm_gem_cma_object **gems,
> + unsigned int *pitches,
> + unsigned int *offsets,
> + uint32_t src_x, uint32_t src_y,
> + uint32_t src_w, uint32_t src_h);
> +
> +void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
> + u32 format);
> +
> +int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
> + u32 format);
> +
> +int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
> + struct drm_crtc *crtc,
> + 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);
> +
> +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_panel_create(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..b449fe1
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
> @@ -0,0 +1,701 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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_gem_flip_release(struct atmel_hlcdc_layer *layer,
> + struct atmel_hlcdc_layer_gem_flip *flip)
> +{
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!flip->gems[i])
> + break;
> +
> + drm_gem_object_unreference_unlocked(flip->gems[i]);
> + }
> +
> + kfree(flip);
> +}
> +
> +static void atmel_hlcdc_layer_gc_work(struct work_struct *work)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc =
> + container_of(work,
> + struct atmel_hlcdc_layer_gem_flip_gc,
> + work);
> + struct atmel_hlcdc_layer *layer =
> + container_of(gc , struct atmel_hlcdc_layer, gc);
> +
> + while (true) {
> + struct atmel_hlcdc_layer_gem_flip *flip;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&gc->list_lock, flags);
> + flip = list_first_entry_or_null(&gc->list,
> + struct atmel_hlcdc_layer_gem_flip,
> + node);
> + if (flip)
> + list_del(&flip->node);
> + spin_unlock_irqrestore(&gc->list_lock, flags);
> +
> + if (!flip)
> + break;
> +
> + atmel_hlcdc_layer_gem_flip_release(layer, flip);
> +
> + mutex_lock(&gc->finished_lock);
> + if (gc->finished)
> + gc->finished(gc->finished_data);
> + mutex_unlock(&gc->finished_lock);
> + }
> +}
> +
> +static void
> +atmel_hlcdc_layer_gem_flip_put(struct atmel_hlcdc_layer *layer,
> + struct atmel_hlcdc_layer_gem_flip *flip)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> + unsigned long flags;
> +
> + if (--flip->remaining_gems <= 0) {
> + spin_lock_irqsave(&gc->list_lock, flags);
> + list_add_tail(&flip->node,
> + &gc->list);
> + spin_unlock_irqrestore(&gc->list_lock, flags);
> + schedule_work(&gc->work);
> + }
> +}
> +
> +static void atmel_hlcdc_layer_start_queue(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;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + dma->cur[i] = dma->queue[i];
> + if (!dma->queue[i])
> + continue;
> + dma->queue[i] = NULL;
> +
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_ADDR(i),
> + dma->cur[i]->addr);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_CTRL(i),
> + dma->cur[i]->ctrl);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_NEXT(i),
> + dma->cur[i]->next);
> + }
> +}
> +
> +static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer)
> +{
> + 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;
> + unsigned int cfg;
> + u32 action = 0;
> + int i;
> +
> + if (upd->pending < 0 || upd->pending > 1)
> + 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;
> + }
> +
> + if (slot->gem_flip && slot->gem_flip->remaining_gems) {
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + struct atmel_hlcdc_dma_channel_dscr *dscr;
> +
> + if (!slot->gem_flip->gems[i])
> + break;
> +
> + dscr = slot->dscrs[i];
> + slot->dscrs[i] = NULL;
> +
> + if (!dma->enabled) {
> + action |= ATMEL_HLCDC_LAYER_DMA_CHAN;
> + 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);
> + dma->cur[i] = dscr;
> + } else {
> + action |= ATMEL_HLCDC_LAYER_A2Q;
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_HEAD(i),
> + dscr->next);
> + dma->queue[i] = dscr;
> + }
> + }
> +
> + dma->enabled = true;
> + slot->gem_flip = NULL;
> + }
> +
> + if (action)
> + regmap_write(regmap,
> + desc->regs_offset + ATMEL_HLCDC_LAYER_CHER,
> + action);
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!slot->dscrs[i])
> + continue;
> +
> + slot->dscrs[i]->gem_flip = NULL;
> + slot->dscrs[i] = NULL;
> + }
> +
> + if (slot->gem_flip) {
> + atmel_hlcdc_layer_gem_flip_put(layer, slot->gem_flip);
> + slot->gem_flip = NULL;
> + }
> +
> + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
> + memset(slot->configs, 0,
> + sizeof(*slot->configs) * layer->desc->nconfigs);
> +
> + upd->pending = -1;
> +}
> +
> +static bool
> +atmel_hlcdc_layer_dma_channel_active(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (dma->cur[i])
> + return true;
> + }
> +
> + return false;
> +}
> +
> +void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip *flip_gc[ATMEL_HLCDC_MAX_PLANES];
> + 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_dma_channel_dscr *dscr;
> + unsigned long flags;
> + unsigned int isr, imr;
> + unsigned int 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;
> +
> + memset(flip_gc, 0, sizeof(flip_gc));
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if ((status & ATMEL_HLCDC_LAYER_DONE_IRQ(i)) ||
> + (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i))) {
> + dscr = dma->cur[i];
> + dma->cur[i] = NULL;
> + flip_gc[i] = dscr->gem_flip;
> + dscr->gem_flip = NULL;
> + }
> +
> + if (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i)) {
> + dma->cur[i] = dma->queue[i];
> + dma->queue[i] = NULL;
> + }
> + }
> +
> + /*
> + * The DMA channel might have been disabled before we were able to
> + * add the new frame to the DMA transfer queue.
> + * Try to re-enable the channel in this case.
> + */
> + if (!atmel_hlcdc_layer_dma_channel_active(layer)) {
> + if (atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + atmel_hlcdc_layer_start_queue(layer);
> +
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_CHDR,
> + ATMEL_HLCDC_LAYER_A2Q);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_CHER,
> + ATMEL_HLCDC_LAYER_DMA_CHAN);
> + } else {
> + dma->enabled = false;
> + }
> + }
> +
> + if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + struct atmel_hlcdc_layer_update *upd = &layer->update;
> +
> + spin_lock(&upd->pending_lock);
> + atmel_hlcdc_layer_update_apply(layer);
> + spin_unlock(&upd->pending_lock);
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!flip_gc[i])
> + break;
> +
> + atmel_hlcdc_layer_gem_flip_put(layer, flip_gc[i]);
> + }
> +}
> +
> +int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + unsigned long flags;
> + int i;
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!dma->cur[i])
> + break;
> +
> + dma->cur[i]->ctrl = 0;
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + return 0;
> +}
> +
> +void atmel_hlcdc_layer_set_finished(struct atmel_hlcdc_layer *layer,
> + void (*finished)(void *data),
> + void *data)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + mutex_lock(&gc->finished_lock);
> + gc->finished = finished;
> + gc->finished_data = data;
> + mutex_unlock(&gc->finished_lock);
> +}
> +
> +static void atmel_hlcdc_layer_update_reset(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;
> + int i;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return;
> +
> + slot = &upd->slots[upd->next];
> + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
> + memset(slot->configs, 0,
> + sizeof(*slot->configs) * layer->desc->nconfigs);
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!slot->dscrs[i])
> + break;
> + slot->dscrs[i]->gem_flip = NULL;
> + slot->dscrs[i] = NULL;
> + }
> + spin_unlock_irqrestore(&layer->dma.lock, flags);
> +
> + if (slot->gem_flip) {
> + atmel_hlcdc_layer_gem_flip_release(layer, slot->gem_flip);
> + slot->gem_flip = NULL;
> + }
> +
> + upd->next = -1;
> +}
> +
> +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_gem_flip *gem_flip;
> + struct atmel_hlcdc_layer_update_slot *slot;
> + unsigned long flags;
> + int i, j = 0;
> + int pending;
> +
> + gem_flip = kzalloc(sizeof(*gem_flip), GFP_KERNEL);
> + if (!gem_flip)
> + return -ENOMEM;
> +
> + mutex_lock(&upd->lock);
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + upd->next = pending ? 0 : 1;
> +
> + slot = &upd->slots[upd->next];
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes * 4; i++) {
> + if (!dma->dscrs[i].gem_flip) {
> + slot->dscrs[j++] = &dma->dscrs[i];
> + dma->dscrs[i].gem_flip = gem_flip;
> + if (j == layer->max_planes)
> + break;
> + }
> + }
> +
> + if (j < layer->max_planes) {
> + for (i = 0; i < j; i++)
> + slot->dscrs[i]->gem_flip = NULL;
> + }
> + spin_unlock_irqrestore(&layer->dma.lock, flags);
> +
> + if (j < layer->max_planes) {
> + mutex_unlock(&upd->lock);
> + kfree(gem_flip);
> + return -EBUSY;
> + }
> +
> + slot->gem_flip = gem_flip;
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + if (pending >= 0) {
> + memcpy(upd->slots[upd->next].configs,
> + upd->slots[upd->pending].configs,
> + layer->desc->nconfigs * sizeof(u32));
> + memcpy(upd->slots[upd->next].updated_configs,
> + upd->slots[upd->pending].updated_configs,
> + DIV_ROUND_UP(layer->desc->nconfigs,
> + BITS_PER_BYTE * sizeof(unsigned long)) *
> + sizeof(unsigned long));
> + }
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + if (pending < 0)
> + regmap_bulk_read(regmap, ATMEL_HLCDC_LAYER_CFG(layer, 0),
> + upd->slots[upd->next].configs,
> + layer->desc->nconfigs);
> +
> + 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);
> + mutex_unlock(&upd->lock);
> +}
> +
> +int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
> + int plane_id,
> + struct drm_gem_cma_object *gem,
> + unsigned int offset)
> +{
> + struct atmel_hlcdc_layer_update *upd = &layer->update;
> + struct atmel_hlcdc_layer_gem_flip *gem_flip;
> + struct atmel_hlcdc_layer_update_slot *slot;
> + struct atmel_hlcdc_dma_channel_dscr *dscr;
> + struct drm_gem_object *old_gem;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return -EINVAL;
> +
> + if (plane_id >= layer->max_planes || plane_id < 0)
> + return -EINVAL;
> +
> + slot = &upd->slots[upd->next];
> + dscr = slot->dscrs[plane_id];
> + dscr->addr = gem->paddr + offset;
> + dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH;
> + gem_flip = dscr->gem_flip;
> +
> + old_gem = gem_flip->gems[plane_id];
> +
> + if (gem) {
> + drm_gem_object_reference(&gem->base);
> + gem_flip->gems[plane_id] = &gem->base;
> + gem_flip->remaining_gems++;
> + } else {
> + gem_flip->gems[plane_id] = NULL;
> + }
> +
> + if (old_gem) {
> + drm_gem_object_unreference_unlocked(old_gem);
> + gem_flip->remaining_gems--;
> + }
> +
> + return 0;
> +}
> +
> +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;
> + int pending;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return;
> +
> + slot = &upd->slots[upd->next];
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + upd->pending = upd->next;
> + upd->next = pending;
> + if (pending >= 0 && !slot->gem_flip->remaining_gems) {
> + struct atmel_hlcdc_layer_gem_flip *gem_flip = slot->gem_flip;
> + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
> +
> + memcpy(dscrs, slot->dscrs, sizeof(dscrs));
> + slot->gem_flip = upd->slots[pending].gem_flip;
> + memcpy(slot->dscrs, upd->slots[pending].dscrs,
> + sizeof(slot->dscrs));
> + upd->slots[pending].gem_flip = gem_flip;
> + memcpy(upd->slots[pending].dscrs, dscrs, sizeof(dscrs));
> + }
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + if (pending >= 0) {
> + atmel_hlcdc_layer_update_reset(layer);
> + mutex_unlock(&upd->lock);
> + return;
> + }
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + spin_lock(&upd->pending_lock);
> + atmel_hlcdc_layer_update_apply(layer);
> + spin_unlock(&upd->pending_lock);
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + atmel_hlcdc_layer_update_reset(layer);
> + mutex_unlock(&upd->lock);
> +}
> +
> +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));
> + }
> +
> + spin_lock_init(&dma->lock);
> +
> + 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];
> +
> + if (!dscr->gem_flip)
> + continue;
> +
> + atmel_hlcdc_layer_gem_flip_put(layer, dscr->gem_flip);
> + }
> +
> + dma_free_coherent(dev->dev, layer->max_planes * 4 *
> + sizeof(*dma->dscrs), dma->dscrs,
> + dma->dscrs[0].next);
> +}
> +
> +static void atmel_hlcdc_layer_gc_init(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + INIT_LIST_HEAD(&gc->list);
> + spin_lock_init(&gc->list_lock);
> + INIT_WORK(&gc->work, atmel_hlcdc_layer_gc_work);
> + mutex_init(&gc->finished_lock);
> +}
> +
> +static void atmel_hlcdc_layer_gc_cleanup(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + flush_work(&gc->work);
> +}
> +
> +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;
> + spin_lock_init(&upd->pending_lock);
> + mutex_init(&upd->lock);
> +
> + 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->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;
> + }
> +
> + atmel_hlcdc_layer_gc_init(layer);
> + 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);
> +
> + for (i = 0; i < layer->max_planes; i++)
> + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER,
> + ATMEL_HLCDC_LAYER_ADD_IRQ(i) |
> + ATMEL_HLCDC_LAYER_DONE_IRQ(i));
> +
> + 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);
> + atmel_hlcdc_layer_gc_cleanup(layer);
> +}
> 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..868f444
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
> @@ -0,0 +1,417 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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/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(n) BIT(2 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_DSCR_IRQ(n) BIT(3 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_ADD_IRQ(n) BIT(4 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_DONE_IRQ(n) BIT(5 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_OVR_IRQ(n) BIT(6 + ((n) * 8))
> +
> +#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_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
> +
> +/**
> + * 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
> + * @color_space_conv: color spave 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 color_space_conv;
> +};
> +
> +/**
> + * Atmel HLCDC GEM 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 the reference GEM objects anymore
> + * (i.e. the layer was disabled or updated).
> + *
> + * @node: list element structure. Used to attach the GEM flip structure to
> + * the garbage collector when the layer no longer need the referenced
> + * GEM objects.
> + * @gems: the referenced GEM objects. The number of GEM object referenced
> + * depends on the chosen format.
> + * @remaining_gems: the number of GEM object still referenced by the layer.
> + * When no more objects are referenced the GEM flip structure
> + * is added to the garbage collector.
> + */
> +struct atmel_hlcdc_layer_gem_flip {
> + struct list_head node;
> + struct drm_gem_object *gems[ATMEL_HLCDC_MAX_PLANES];
> + int remaining_gems;
> +};
> +
> +/**
> + * 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;
> + struct atmel_hlcdc_layer_gem_flip *gem_flip;
> +} __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
> + *@gem_flip: gem_flip object
> + *@updated_configs: bitmask used to record modified configs
> + *@configs: new config values
> + */
> +struct atmel_hlcdc_layer_update_slot {
> + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
> + struct atmel_hlcdc_layer_gem_flip *gem_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
> + *@pending_lock: lock to access the pending field
> + *@lock: layer update lock
> + */
> +struct atmel_hlcdc_layer_update {
> + struct atmel_hlcdc_layer_update_slot slots[2];
> + int pending;
> + int next;
> + spinlock_t pending_lock;
> + struct mutex lock;
> +};
> +
> +/**
> + * Atmel HLCDC Layer GEM flip garbage collector structure
> + *
> + * This structure is used to schedule GEM object release when we are in
> + * interrupt context (within atmel_hlcdc_layer_irq function).
> + *
> + *@list: GEM flip objects to release
> + *@list_lock: lock to access the GEM flip list
> + *@work: work queue scheduled when there are GEM flip to collect
> + *@finished: action to execute the GEM flip and all attached objects have been
> + * released
> + *@finished_data: data passed to the finished callback
> + *@finished_lock: lock to access finished related fields
> + */
> +struct atmel_hlcdc_layer_gem_flip_gc {
> + struct list_head list;
> + spinlock_t list_lock;
> + struct work_struct work;
> + void (*finished)(void *data);
> + void *finished_data;
> + struct mutex finished_lock;
> +};
> +
> +/**
> + * Atmel HLCDC Layer DMA channel structure
> + *
> + * This structure stores informations on the DMA channel associated to a
> + * given layer.
> + *
> + *@enabled: DMA channel status
> + *@lock: lock to access the DMA channel fields
> + *@cur: current DMA transfers (one for each plane/buffer)
> + *@queue: next DMA transfers, to be launch on next frame update
> + *@dscrs: allocated DMA descriptors
> + */
> +struct atmel_hlcdc_layer_dma_channel {
> + bool enabled;
> + spinlock_t lock;
> + struct atmel_hlcdc_dma_channel_dscr *cur[ATMEL_HLCDC_MAX_PLANES];
> + struct atmel_hlcdc_dma_channel_dscr *queue[ATMEL_HLCDC_MAX_PLANES];
> + 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: GEM flip garbage collector
> + *@update: update handler
> + */
> +struct atmel_hlcdc_layer {
> + const struct atmel_hlcdc_layer_desc *desc;
> + int max_planes;
> + struct atmel_hlcdc *hlcdc;
> + struct atmel_hlcdc_layer_dma_channel dma;
> + struct atmel_hlcdc_layer_gem_flip_gc gc;
> + struct atmel_hlcdc_layer_update update;
> +};
> +
> +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);
> +
> +int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
> + int plane_id,
> + struct drm_gem_cma_object *gem,
> + unsigned int offset);
> +
> +void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer);
> +
> +void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer);
> +
> +static inline bool
> +atmel_hlcdc_layer_dma_channel_busy(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (dma->queue[i])
> + return true;
> + }
> +
> + return false;
> +}
> +
> +#endif /* DRM_ATMEL_HLCDC_LAYER_H */
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
> new file mode 100644
> index 0000000..3295021
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
> @@ -0,0 +1,351 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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 <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,
> +};
> +
> +/**
> + * Atmel HLCDC Panel structure
> + *
> + * This structure stores informations about an DRM panel connected through
> + * the RGB connector.
> + *
> + * @mode: RGB output mode
> + * @connector: DRM connector
> + * @encoder: DRM encoder
> + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
> + * @panel: pointer to the attached DRM panel
> + * @pinctrl: pinctrl state used by the current RGB output mode
> + * @np: DRM panel DT node
> + * @dpms: current DPMS mode
> + */
> +struct atmel_hlcdc_panel {
> + enum atmel_hlcdc_connector_rgb_mode mode;
> + struct drm_connector connector;
> + struct drm_encoder encoder;
> + struct atmel_hlcdc *hlcdc;
> + struct drm_panel *panel;
> + struct pinctrl *pinctrl;
> + struct device_node *np;
> + int dpms;
> +};
> +
> +static const char * const pin_state_names[] = {
> + [ATMEL_HLCDC_CONNECTOR_RGB444] = "rgb-444",
> + [ATMEL_HLCDC_CONNECTOR_RGB565] = "rgb-565",
> + [ATMEL_HLCDC_CONNECTOR_RGB666] = "rgb-666",
> + [ATMEL_HLCDC_CONNECTOR_RGB888] = "rgb-888",
> +};
> +
> +static inline struct atmel_hlcdc_panel *
> +drm_connector_to_atmel_hlcdc_panel(struct drm_connector *connector)
> +{
> + return container_of(connector, struct atmel_hlcdc_panel, connector);
> +}
> +
> +static inline struct atmel_hlcdc_panel *
> +drm_encoder_to_atmel_hlcdc_panel(struct drm_encoder *encoder)
> +{
> + return container_of(encoder, struct atmel_hlcdc_panel, encoder);
> +}
> +
> +static void atmel_hlcdc_panel_encoder_dpms(struct drm_encoder *encoder,
> + int mode)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_encoder_to_atmel_hlcdc_panel(encoder);
> + struct regmap *regmap = panel->hlcdc->regmap;
> + unsigned int status;
> +
> + if (mode != DRM_MODE_DPMS_ON)
> + mode = DRM_MODE_DPMS_OFF;
> +
> + if (mode == panel->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(panel->hlcdc->sys_clk);
> + if (panel->panel)
> + drm_panel_disable(panel->panel);
> + } else {
> + if (panel->panel)
> + drm_panel_enable(panel->panel);
> + clk_prepare_enable(panel->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();
> + }
> +
> + panel->dpms = mode;
> +}
> +
> +static bool
> +atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder,
> + const struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted)
> +{
> + return true;
> +}
> +
> +static void atmel_hlcdc_panel_encoder_prepare(struct drm_encoder *encoder)
> +{
> + atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
> +}
> +
> +static void atmel_hlcdc_panel_encoder_commit(struct drm_encoder *encoder)
> +{
> + atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
> +}
> +
> +static void
> +atmel_hlcdc_panel_encoder_mode_set(struct drm_encoder *encoder,
> + struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_encoder_to_atmel_hlcdc_panel(encoder);
> + unsigned long prate = clk_get_rate(panel->hlcdc->sys_clk);
> + unsigned long mode_rate = mode->clock * 1000;
> + int div;
> + u32 cfg0 = 0;
> +
> + if ((prate / 2) < mode_rate) {
> + prate *= 2;
> + cfg0 |= ATMEL_HLCDC_CLKSEL;
> + }
> +
> + div = DIV_ROUND_UP(prate, mode_rate);
> + if (div < 2)
> + div = 2;
> +
> + cfg0 |= ATMEL_HLCDC_CLKDIV(div);
> +
> + regmap_update_bits(panel->hlcdc->regmap, ATMEL_HLCDC_CFG(0),
> + ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK, cfg0);
> +}
> +
> +static struct drm_encoder_helper_funcs encoder_helper_funcs = {
> + .dpms = atmel_hlcdc_panel_encoder_dpms,
> + .mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup,
> + .prepare = atmel_hlcdc_panel_encoder_prepare,
> + .commit = atmel_hlcdc_panel_encoder_commit,
> + .mode_set = atmel_hlcdc_panel_encoder_mode_set,
> +};
> +
> +static void atmel_hlcdc_panel_encoder_destroy(struct drm_encoder *encoder)
> +{
> + drm_encoder_cleanup(encoder);
> + memset(encoder, 0, sizeof(*encoder));
> +}
> +
> +static const struct drm_encoder_funcs encoder_funcs = {
> + .destroy = atmel_hlcdc_panel_encoder_destroy,
> +};
> +
> +static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> + int ret;
> +
> + if (!panel->panel)
> + return -ENOENT;
> +
> + ret = panel->panel->funcs->get_modes(panel->panel);
> + return ret;
> +}
> +
> +static int atmel_hlcdc_panel_mode_valid(struct drm_connector *connector,
> + struct drm_display_mode *mode)
> +{
> + return MODE_OK;
> +}
> +
> +static struct drm_encoder *
> +atmel_hlcdc_panel_best_encoder(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + return &panel->encoder;
> +}
> +
> +static struct drm_connector_helper_funcs connector_helper_funcs = {
> + .get_modes = atmel_hlcdc_panel_get_modes,
> + .mode_valid = atmel_hlcdc_panel_mode_valid,
> + .best_encoder = atmel_hlcdc_panel_best_encoder,
> +};
> +
> +static enum drm_connector_status
> +atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + if (!panel->panel) {
> + panel->panel = of_drm_find_panel(panel->np);
> + if (panel->panel)
> + drm_panel_attach(panel->panel, &panel->connector);
> + }
> +
> + if (panel->panel)
> + return connector_status_connected;
> +
> + return connector_status_disconnected;
> +}
> +
> +static void
> +atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + if (panel->panel)
> + drm_panel_detach(panel->panel);
> +
> + drm_sysfs_connector_remove(connector);
> + drm_connector_cleanup(connector);
> +
> + pinctrl_put(panel->pinctrl);
> +}
> +
> +static const struct drm_connector_funcs connector_funcs = {
> + .dpms = drm_helper_connector_dpms,
> + .detect = atmel_hlcdc_panel_connector_detect,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = atmel_hlcdc_panel_connector_destroy,
> +};
> +
> +int atmel_hlcdc_panel_create(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_dc *dc = dev->dev_private;
> + struct atmel_hlcdc_panel *panel;
> + struct of_phandle_args out_args;
> + u32 cfg;
> + int ret;
> +
> + panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL);
> + if (!panel)
> + return -ENOMEM;
> +
> + ret = of_parse_phandle_with_fixed_args(dev->dev->of_node,
> + "atmel,panel", 2, 0,
> + &out_args);
> + if (ret) {
> + dev_err(dev->dev, "failed to retrieve panel info from DT\n");
> + return ret;
> + }
> +
> + switch (out_args.args[0]) {
> + case ATMEL_HLCDC_CONNECTOR_RGB444:
> + case ATMEL_HLCDC_CONNECTOR_RGB565:
> + case ATMEL_HLCDC_CONNECTOR_RGB666:
> + case ATMEL_HLCDC_CONNECTOR_RGB888:
> + break;
> + default:
> + dev_err(dev->dev, "unknown RGB mode\n");
> + return -EINVAL;
> + }
> +
> + panel->np = out_args.np;
> + panel->mode = out_args.args[0];
> + panel->pinctrl = pinctrl_get_select(dev->dev,
> + pin_state_names[panel->mode]);
> + if (IS_ERR(panel->pinctrl)) {
> + dev_err(dev->dev, "could not request pinctrl state\n");
> + return PTR_ERR(panel->pinctrl);
> + }
> +
> + cfg = out_args.args[1] & (ATMEL_HLCDC_HSPOL |
> + ATMEL_HLCDC_VSPOL |
> + ATMEL_HLCDC_VSPDLYS |
> + ATMEL_HLCDC_VSPDLYE |
> + ATMEL_HLCDC_DISPPOL |
> + ATMEL_HLCDC_DISPDLY |
> + ATMEL_HLCDC_VSPSU |
> + ATMEL_HLCDC_VSPHO);
> + cfg |= panel->mode << 8;
> +
> + regmap_write(dc->hlcdc->regmap,
> + ATMEL_HLCDC_CFG(5),
> + cfg);
> +
> + panel->dpms = DRM_MODE_DPMS_OFF;
> +
> + panel->hlcdc = dc->hlcdc;
> +
> + drm_connector_init(dev, &panel->connector, &connector_funcs,
> + DRM_MODE_CONNECTOR_LVDS);
> + drm_connector_helper_add(&panel->connector, &connector_helper_funcs);
> + panel->connector.dpms = DRM_MODE_DPMS_OFF;
> + panel->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
> +
> + drm_encoder_init(dev, &panel->encoder, &encoder_funcs,
> + DRM_MODE_ENCODER_LVDS);
> + drm_encoder_helper_add(&panel->encoder, &encoder_helper_funcs);
> +
> + drm_mode_connector_attach_encoder(&panel->connector, &panel->encoder);
> + drm_sysfs_connector_add(&panel->connector);
> +
> + panel->encoder.possible_crtcs = 0x1;
> +
> + 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..f428b47
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> @@ -0,0 +1,658 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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_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_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_NV61,
> + DRM_FORMAT_YUV422,
> + DRM_FORMAT_NV21,
> +};
> +
> +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_NV61:
> + *mode = ATMEL_HLCDC_NV61_MODE;
> + break;
> + case DRM_FORMAT_YUV422:
> + *mode = ATMEL_HLCDC_YUV422_MODE;
> + break;
> + case DRM_FORMAT_NV21:
> + *mode = ATMEL_HLCDC_NV21_MODE;
> + break;
> + case DRM_FORMAT_YUV420:
> + *mode = ATMEL_HLCDC_YUV420_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,
> +};
> +
> +int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
> + struct drm_crtc *crtc,
> + 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)
> +{
> + const struct atmel_hlcdc_layer_cfg_layout *layout =
> + &plane->layer.desc->layout;
> +
> + if (!crtc_h || !crtc_w)
> + return -EINVAL;
> +
> + if (!layout->pos && (crtc_x || crtc_y))
> + return -EINVAL;
> +
> + if ((crtc_x + crtc_w) > crtc->mode.crtc_hdisplay ||
> + (crtc_y + crtc_h) > crtc->mode.crtc_vdisplay)
> + return -EINVAL;
> +
> + if (!layout->size &&
> + (crtc->mode.crtc_hdisplay != crtc_w ||
> + crtc->mode.crtc_vdisplay != crtc_h))
> + return -EINVAL;
> +
> + if (plane->layer.desc->max_height &&
> + crtc_h > plane->layer.desc->max_height)
> + return -EINVAL;
> +
> + if (plane->layer.desc->max_width &&
> + crtc_w > plane->layer.desc->max_width)
> + return -EINVAL;
> +
> + if (!layout->memsize && (crtc_h != src_h || crtc_w != src_w))
> + return -EINVAL;
> +
> + if (layout->size)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->size,
> + 0xffffffff,
> + (crtc_w - 1) |
> + ((crtc_h - 1) << 16));
> +
> + if (layout->memsize)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->memsize,
> + 0xffffffff,
> + (src_w - 1) |
> + ((src_h - 1) << 16));
> +
> + if (layout->pos)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->pos,
> + 0xffffffff,
> + crtc_x | (crtc_y << 16));
> +
> + /* TODO: rework the rescaling part */
> + if (crtc_w != src_w || crtc_h != src_h) {
> + u32 factor_reg = 0;
> +
> + if (crtc_w != src_w) {
> + int i;
> + u32 factor;
> + u32 *coeff_tab = heo_upscaling_xcoef;
> + u32 max_memsize;
> +
> + if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
> + factor++;
> + max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
> + if (max_memsize > src_w)
> + factor--;
> + factor_reg |= factor | 0x80000000;
> + }
> +
> + if (crtc_h != src_h) {
> + int i;
> + u32 factor;
> + u32 *coeff_tab = heo_upscaling_ycoef;
> + u32 max_memsize;
> +
> + if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
> + factor++;
> + max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
> + if (max_memsize > src_w)
> + factor--;
> + factor_reg |= (factor << 16) | 0x80000000;
> + }
> +
> + atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff,
> + factor_reg);
> + }
> +
> + return 0;
> +}
> +
> +void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
> + u32 format)
> +{
> + 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(format))
> + cfg |= ATMEL_HLCDC_LAYER_LAEN;
> + else
> + cfg |= (plane->alpha << ATMEL_HLCDC_LAYER_GA_SHIFT) |
> + 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 |
> + ATMEL_HLCDC_LAYER_GA_MASK, cfg);
> +}
> +
> +int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
> + u32 format)
> +{
> + u32 mode;
> + int ret;
> +
> + ret = atmel_hlcdc_format_to_plane_mode(format, &mode);
> + if (ret)
> + return ret;
> +
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + ATMEL_HLCDC_LAYER_FORMAT_CFG_ID,
> + 0xffffffff,
> + mode);
> +
> + return 0;
> +}
> +
> +int atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
> + u32 pixel_format,
> + struct drm_gem_cma_object **gems,
> + unsigned int *pitches,
> + unsigned int *offsets,
> + uint32_t src_x, uint32_t src_y,
> + uint32_t src_w, uint32_t src_h)
> +{
> + struct atmel_hlcdc_layer *layer = &plane->layer;
> + const struct atmel_hlcdc_layer_cfg_layout *layout =
> + &layer->desc->layout;
> + int bpp[ATMEL_HLCDC_MAX_PLANES];
> + int nplanes;
> + int ret = 0;
> + int i;
> +
> + nplanes = drm_format_num_planes(pixel_format);
> +
> + for (i = 0; i < nplanes; i++) {
> + bpp[i] = drm_format_plane_cpp(pixel_format, i);
> + if (!bpp[i])
> + return -EINVAL;
> +
> + if (!gems[i])
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < nplanes; i++) {
> + int offset;
> +
> + offset = offsets[i] + (src_y * pitches[i]) +
> + (src_x * bpp[i]);
> +
> + ret = atmel_hlcdc_layer_update_set_gem(&plane->layer, i,
> + gems[i], offset);
> + if (ret)
> + return ret;
> +
> + if (layout->xstride[i])
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->xstride[i],
> + 0xffffffff,
> + pitches[i] - (bpp[i] * src_w));
> + }
> +
> + 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 drm_gem_cma_object *gems[ATMEL_HLCDC_MAX_PLANES];
> + int nplanes;
> + int ret = 0;
> + int i;
> +
> + /* Subpixel positioning is not supported */
> + if ((src_x | src_y | src_w | src_h) & SUBPIXEL_MASK) {
> + DRM_DEBUG_KMS("Primary base does not support subpixel positioning\n");
> + return -EINVAL;
> + }
> +
> + src_h >>= 16;
> + src_w >>= 16;
> + src_x >>= 16;
> + src_y >>= 16;
> +
> + nplanes = drm_format_num_planes(fb->pixel_format);
> + if (nplanes > ATMEL_HLCDC_MAX_PLANES)
> + return -EINVAL;
> +
> + for (i = 0; i < nplanes; i++)
> + gems[i] = drm_fb_cma_get_gem_obj(fb, i);
> +
> + atmel_hlcdc_layer_update_start(&plane->layer);
> + ret = atmel_hlcdc_plane_update_pos_and_size(plane, crtc,
> + crtc_x, crtc_y,
> + crtc_w, crtc_h,
> + src_x, src_y,
> + src_w, src_h);
> + if (ret)
> + goto out;
> +
> + atmel_hlcdc_plane_update_general_settings(plane, fb->pixel_format);
> +
> + ret = atmel_hlcdc_plane_update_format(plane, fb->pixel_format);
> + if (ret)
> + goto out;
> +
> +
> + ret = atmel_hlcdc_plane_update_buffers(plane, fb->pixel_format,
> + gems,
> + fb->pitches, fb->offsets,
> + src_x, src_y,
> + src_w, src_h);
> +
> + if (ret)
> + goto out;
> +
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> + drm_framebuffer_reference(fb);
> + if (plane->base.fb)
> + drm_framebuffer_unreference(plane->base.fb);
> + plane->base.fb = fb;
> +
> + return 0;
> +
> +out:
> + atmel_hlcdc_layer_update_rollback(&plane->layer);
> +
> + return ret;
> +}
> +
> +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);
> + plane->alpha = alpha;
> + atmel_hlcdc_plane_update_general_settings(plane,
> + plane->base.fb->pixel_format);
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> + 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
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +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 *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);
> +
> + /* Set default property values*/
> + plane->alpha = 0xff;
> +
> + 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);
> +
> + return plane;
> +}
> +
> +static struct atmel_hlcdc_plane_properties *
> +atmel_hlcdc_plane_create_properties(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_plane_properties *props;
> +
> + 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);
> +
> + 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]);
> + 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;
> + drm_object_attach_property(&plane->base.base,
> + props->alpha, 255);
> + break;
> +
> + case ATMEL_HLCDC_CURSOR_LAYER:
> + if (planes->cursor)
> + return ERR_PTR(-EINVAL);
> + planes->cursor = plane;
> + drm_object_attach_property(&plane->base.base,
> + props->alpha, 255);
> + break;
> +
> + default:
> + break;
> + }
> + }
> +
> + return planes;
> +}
> diff --git a/drivers/gpu/drm/atmel_hlcdc/Kconfig b/drivers/gpu/drm/atmel_hlcdc/Kconfig
> new file mode 100644
> index 0000000..59c8eeb
> --- /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 && ARM && 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..08de8d7
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel_hlcdc/Makefile
> @@ -0,0 +1,8 @@
> +atmel_hlcdc-y := atmel_hlcdc_crtc.o \
> + atmel_hlcdc_drv.o \
> + atmel_hlcdc_layer.o \
> + atmel_hlcdc_panel.o \
> + atmel_hlcdc_plane.o \
> + atmel_hlcdc_pwm.o
> +
> +obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel_hlcdc.o
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
Hi Boris,
On 06/10/2014 12:04 AM, Boris BREZILLON wrote:
> The HLCDC (High LCD Controller) IP supports 4 different output mode
> (RGB444, RGB565, RGB666 and RGB888) and the pin muxing depends on the
> chosen RGB mode.
>
> Split the pin definition to be able to set the pin config according to the
> selected mode.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> arch/arm/boot/dts/sama5d3_lcd.dtsi | 127 ++++++++++++++++++++++++++++---------
> 1 file changed, 96 insertions(+), 31 deletions(-)
On sama5d3xek board, it only works in 24bits output mode. And it depends
on the hardware design. So, I think only keep only one pinctrl
configuration.
Best Regards,
Bo Shen
> 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 */
> };
> };
> };
>
Hi Boris,
On 06/10/2014 12:04 AM, Boris BREZILLON wrote:
> diff --git a/arch/arm/boot/dts/sama5d33ek.dts b/arch/arm/boot/dts/sama5d33ek.dts
> index cbd6a3f..f2ab41d 100644
> --- a/arch/arm/boot/dts/sama5d33ek.dts
> +++ b/arch/arm/boot/dts/sama5d33ek.dts
> @@ -36,9 +36,33 @@
> macb0: ethernet@f0028000 {
> status = "okay";
> };
> +
> + hlcdc: hlcdc@f0030000 {
> + status = "okay";
> +
> + hlcdc-display-controller {
> + atmel,panel = <&panel 3 0>;
One question here, in the driver code, it will configuration the frame
buffer mode depends on this parameter.
So, my question is if the framebuffer bits per pixel is different with
output bits per pixel, how to setting it?
For example, frame buffer use 16 bits/pixel, while output 24 bits/pixel.
> + };
> + };
> };
> };
>
Best Regards,
Bo Shen
Hello Bo,
On 19/06/2014 09:07, Bo Shen wrote:
> Hi Boris,
>
> On 06/10/2014 12:04 AM, Boris BREZILLON wrote:
>> The HLCDC (High LCD Controller) IP supports 4 different output mode
>> (RGB444, RGB565, RGB666 and RGB888) and the pin muxing depends on the
>> chosen RGB mode.
>>
>> Split the pin definition to be able to set the pin config according
>> to the
>> selected mode.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> arch/arm/boot/dts/sama5d3_lcd.dtsi | 127
>> ++++++++++++++++++++++++++++---------
>> 1 file changed, 96 insertions(+), 31 deletions(-)
>
> On sama5d3xek board, it only works in 24bits output mode. And it
> depends on the hardware design. So, I think only keep only one pinctrl
> configuration.
I'm not describing a specific board design but rather SoC capabilities
(this dtsi is SoC related not board related), and the sama5d3 SoC
supports 4 different RGB output modes through the RGB connector.
If you take a look at patch 7, you'll see that I chose mode 3 (which is
RGB888), and given this mode the HLCDC driver (atmel_hlcdc_panel.c) will
request the appropriate pin state:
atmel,panel = <&panel 3 0>;
Best Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
On 19/06/2014 09:12, Bo Shen wrote:
> Hi Boris,
>
> On 06/10/2014 12:04 AM, Boris BREZILLON wrote:
>> diff --git a/arch/arm/boot/dts/sama5d33ek.dts
>> b/arch/arm/boot/dts/sama5d33ek.dts
>> index cbd6a3f..f2ab41d 100644
>> --- a/arch/arm/boot/dts/sama5d33ek.dts
>> +++ b/arch/arm/boot/dts/sama5d33ek.dts
>> @@ -36,9 +36,33 @@
>> macb0: ethernet@f0028000 {
>> status = "okay";
>> };
>> +
>> + hlcdc: hlcdc@f0030000 {
>> + status = "okay";
>> +
>> + hlcdc-display-controller {
>> + atmel,panel = <&panel 3 0>;
>
> One question here, in the driver code, it will configuration the frame
> buffer mode depends on this parameter.
> So, my question is if the framebuffer bits per pixel is different with
> output bits per pixel, how to setting it?
>
> For example, frame buffer use 16 bits/pixel, while output 24 bits/pixel.
Actually the HLCDC is responsible for converting input format (either
RGB or YUV) to output format (one of the four supported RGB formats).
AFAICT, the HLCDC always converts the input format in RGB888 and then
only use the relevant bits (i.e. if the output is RGB565, it will only
takes MSB for each color).
The REP field (available in all layer, e.g. LCDC_BASECFG4 for the base
layer) is here to tell how the HLCDC should expand to 24 bits format.
All this means that we don't have to bother about input to output format
conversion.
>
>> + };
>> + };
>> };
>> };
>>
>
> Best Regards,
> Bo Shen
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
On 19/06/2014 09:07, Bo Shen wrote:
> Hi Boris,
>
> On 06/10/2014 12:04 AM, Boris BREZILLON wrote:
>> The HLCDC (High LCD Controller) IP supports 4 different output mode
>> (RGB444, RGB565, RGB666 and RGB888) and the pin muxing depends on the
>> chosen RGB mode.
>>
>> Split the pin definition to be able to set the pin config according
>> to the
>> selected mode.
>>
>> Signed-off-by: Boris BREZILLON <[email protected]>
>> ---
>> arch/arm/boot/dts/sama5d3_lcd.dtsi | 127
>> ++++++++++++++++++++++++++++---------
>> 1 file changed, 96 insertions(+), 31 deletions(-)
>
> On sama5d3xek board, it only works in 24bits output mode. And it
> depends on the hardware design. So, I think only keep only one pinctrl
> configuration.
As you pointed out (during our discussion on IRC) sama5d3 SoCs support
several pin muxing for LCDDAT pins.
I'll take care to define all these pin mux here and select the
appropriate ones in sama5d3xdm.dtsi (patch 6) instead of
sama5d3_lcd.dtsi (patch 5).
Best Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
Hello,
It's been almost a month and I only got reviews pointing minor issues.
I'd really like to get feedback/reviews from DRM/KMS maintainers and/or
experienced developers (this is my first DRM/KMS driver I'm pretty sure
there are things to fix).
Best Regards,
Boris
On Mon, 9 Jun 2014 18:04:16 +0200
Boris BREZILLON <[email protected]> wrote:
> The Atmel HLCDC (High LCD Controller) IP available on some Atmel SoCs (i.e.
> at91sam9n12, at91sam9x5 family or sama5d3 family) provides a display
> controller device.
>
> This display controller support at least one primary plane and might
> provide several overlays and an hardware cursor depending on the IP
> version.
>
> Signed-off-by: Boris BREZILLON <[email protected]>
> ---
> .../devicetree/bindings/drm/atmel-hlcdc-dc.txt | 59 ++
> 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 | 529 ++++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 477 ++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 178 ++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c | 701 +++++++++++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h | 417 ++++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c | 351 +++++++++++
> drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 658 +++++++++++++++++++
> drivers/gpu/drm/atmel_hlcdc/Kconfig | 11 +
> drivers/gpu/drm/atmel_hlcdc/Makefile | 8 +
> 14 files changed, 3410 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.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_panel.c
> create mode 100644 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> create mode 100644 drivers/gpu/drm/atmel_hlcdc/Kconfig
> create mode 100644 drivers/gpu/drm/atmel_hlcdc/Makefile
>
> 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..594bdb2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/drm/atmel-hlcdc-dc.txt
> @@ -0,0 +1,59 @@
> +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 Documentation/devicetree/bindings/mfd/atmel-hlcdc.txt for more details.
> +
> +Required properties:
> + - compatible: value should be one of the following:
> + "atmel,hlcdc-dc"
> + - interrupts: the HLCDC interrupt definition
> + - pinctrl-names: the pin control state names. Should contain "default",
> + "rgb-444", "rgb-565", "rgb-666" and "rgb-888".
> + - pinctrl-[0-4]: should contain the pinctrl states described by pinctrl
> + names.
> + - atmel,panel: Should contain a phandle with 2 parameters.
> + The first cell is a phandle to a DRM panel device
> + The second cell encodes the RGB mode, which can take the following values:
> + * 0: RGB444
> + * 1: RGB565
> + * 2: RGB666
> + * 3: RGB888
> + The third cell encodes specific flags describing LCD signals configuration
> + (see Atmel's datasheet for a full description of these fields):
> + * bit 0: HSPOL: Horizontal Synchronization Pulse Polarity
> + * bit 1: VSPOL: Vertical Synchronization Pulse Polarity
> + * bit 2: VSPDLYS: Vertical Synchronization Pulse Start
> + * bit 3: VSPDLYE: Vertical Synchronization Pulse End
> + * bit 4: DISPPOL: Display Signal Polarity
> + * bit 7: DISPDLY: LCD Controller Display Power Signal Synchronization
> + * bit 12: VSPSU: LCD Controller Vertical synchronization Pulse Setup Configuration
> + * bit 13: VSPHO: LCD Controller Vertical synchronization Pulse Hold Configuration
> + * bit 16-20: GUARDTIME: LCD DISPLAY Guard Time
> +
> +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-dc";
> + interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
> + pinctrl-names = "default", "rgb-444", "rgb-565", "rgb-666", "rgb-888";
> + pinctrl-0 = <&pinctrl_lcd_base>;
> + pinctrl-1 = <&pinctrl_lcd_base &pinctrl_lcd_rgb444>;
> + pinctrl-2 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
> + pinctrl-3 = <&pinctrl_lcd_base &pinctrl_lcd_rgb666>;
> + pinctrl-4 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
> + };
> +
> + hlcdc_pwm: hlcdc-pwm {
> + compatible = "atmel,hlcdc-pwm";
> + pinctrl-names = "default";
> + pinctrl-0 = <&pinctrl_lcd_pwm>;
> + #pwm-cells = <3>;
> + };
> + };
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index d1cc2f6..df6f0c1 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -182,6 +182,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 48e38ba..28c8a61 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -54,6 +54,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..bf9fe0b
> --- /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_panel.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..a18492e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
> @@ -0,0 +1,529 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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"
> +
> +/**
> + * Structure storing hardware cursor informations like its position, its size
> + * or the GEM object currently used to display the HW cursor.
> + *
> + * @gem: the cursor GEM object
> + * @pitch: GEM object pitch
> + * @height: cursor height in pixels
> + * @width: cursor width in pixels
> + * @x: cursor x position
> + * @y: cursor y position
> + * @offset: start offset from the provided buffer
> + * @patched_height: patched height value after adapting the image size
> + * depending on cursor position
> + * @patched_width: patched width value after adapting the image size depending
> + * on cursor position
> + * @patched_x: patched x value after adapting to cursor position (positive
> + * value)
> + * @patched_y: patched x value after adapting to cursor position (positive
> + * value)
> + */
> +struct atmel_hlcdc_crtc_cursor_info {
> + struct drm_gem_cma_object *gem;
> + unsigned int pitch;
> + u32 height;
> + u32 width;
> + int x;
> + int y;
> +
> + /*
> + * These fields are automatically calculated by
> + * atmel_hlcdc_crtc_cursor_prepare_req.
> + */
> + unsigned int offset;
> + u32 patched_height;
> + u32 patched_width;
> + int patched_x;
> + int patched_y;
> +};
> +
> +/**
> + * Structure storing HW cursor status.
> + *
> + * @status: the current cursor status
> + * @req: the requested cursor changes
> + * @plane: the hardware cursor plane
> + * @lock: cursor lock held when modifying cursor req or status
> + */
> +struct atmel_hlcdc_crtc_cursor {
> + struct atmel_hlcdc_crtc_cursor_info status;
> + struct atmel_hlcdc_crtc_cursor_info req;
> + struct atmel_hlcdc_plane *plane;
> + struct mutex lock;
> +};
> +
> +/**
> + * 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
> + * @cursor: hardware cursor status
> + */
> +struct atmel_hlcdc_crtc {
> + struct drm_crtc base;
> + struct atmel_hlcdc *hlcdc;
> + struct drm_pending_vblank_event *event;
> + int id;
> + int dpms;
> + struct atmel_hlcdc_crtc_cursor cursor;
> +};
> +
> +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 < 0 ||
> + vm.vsync_len > 0x40 || vm.vsync_len < 0 ||
> + vm.vfront_porch > 0x40 || vm.vfront_porch < 0 ||
> + vm.vback_porch > 0x40 || vm.vback_porch < 0 ||
> + vm.hfront_porch > 0x200 || vm.hfront_porch < 0 ||
> + vm.hback_porch > 0x200 || vm.hback_porch < 0 ||
> + mode->hdisplay > 2048 || mode->hdisplay < 0 ||
> + mode->vdisplay > 2048 || mode->vdisplay < 0)
> + 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 - 1) << 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 drm_plane *plane = c->primary;
> +
> + if (crtc->event)
> + return -EBUSY;
> +
> + if (event) {
> + crtc->event = event;
> + drm_vblank_get(c->dev, crtc->id);
> + }
> +
> + return plane->funcs->update_plane(plane, c, fb,
> + 0, 0,
> + c->mode.hdisplay, c->mode.vdisplay,
> + c->x << 16, c->y << 16,
> + c->mode.hdisplay << 16,
> + c->mode.vdisplay << 16);
> +}
> +
> +static void atmel_hlcdc_crtc_cursor_prepare_req(struct atmel_hlcdc_crtc *crtc)
> +{
> + int bpp = drm_format_plane_cpp(DRM_FORMAT_ARGB8888, 0);
> + struct atmel_hlcdc_crtc_cursor_info *info = &crtc->cursor.req;
> + int x_offset = 0;
> + int y_offset = 0;
> +
> + if (!info->gem) {
> + info->width = 0;
> + info->height = 0;
> + info->pitch = 0;
> + info->offset = 0;
> + return;
> + }
> +
> + info->pitch = info->width * bpp;
> +
> + if (info->x + info->width > crtc->base.mode.hdisplay)
> + info->patched_width = crtc->base.mode.hdisplay - info->x;
> + else
> + info->patched_width = info->width;
> +
> + if (info->x < 0) {
> + info->patched_width += info->x;
> + x_offset = -info->x;
> + info->patched_x = 0;
> + } else {
> + info->patched_x = info->x;
> + }
> +
> + if (info->y + info->height > crtc->base.mode.vdisplay)
> + info->patched_height = crtc->base.mode.vdisplay - info->y;
> + else
> + info->patched_height = info->height;
> +
> + if (info->y < 0) {
> + info->patched_height += info->y;
> + y_offset = -info->y;
> + info->patched_y = 0;
> + } else {
> + info->patched_y = info->y;
> + }
> +
> + info->offset = (x_offset * bpp) +
> + (y_offset * info->pitch);
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_apply_req(struct atmel_hlcdc_crtc *crtc)
> +{
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + struct atmel_hlcdc_crtc_cursor_info *req = &crtc->cursor.req;
> + struct atmel_hlcdc_crtc_cursor_info *status = &crtc->cursor.status;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + if (!req->gem ||
> + !req->patched_width || !req->patched_height) {
> + ret = plane->base.funcs->disable_plane(&plane->base);
> +
> + if (!ret)
> + goto out;
> + else
> + goto err;
> + }
> +
> + ret = atmel_hlcdc_layer_update_start(&plane->layer);
> + if (ret)
> + return ret;
> +
> + atmel_hlcdc_plane_update_general_settings(plane, DRM_FORMAT_ARGB8888);
> +
> + ret = atmel_hlcdc_plane_update_format(plane, DRM_FORMAT_ARGB8888);
> + if (ret)
> + goto err;
> +
> + ret = atmel_hlcdc_plane_update_pos_and_size(plane, &crtc->base,
> + req->patched_x,
> + req->patched_y,
> + req->patched_width,
> + req->patched_height,
> + 0, 0,
> + req->patched_width,
> + req->patched_height);
> + if (ret)
> + goto err;
> +
> + ret = atmel_hlcdc_plane_update_buffers(plane,
> + DRM_FORMAT_ARGB8888,
> + &req->gem,
> + &req->pitch,
> + &req->offset,
> + 0, 0,
> + req->patched_width,
> + req->height);
> + if (ret)
> + goto err;
> +
> + if (!plane->base.crtc)
> + plane->base.crtc = &crtc->base;
> +
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> +out:
> + if (req->gem)
> + drm_gem_object_reference(&req->gem->base);
> +
> + if (status->gem)
> + drm_gem_object_unreference_unlocked(&status->gem->base);
> +
> + *status = *req;
> +
> + return 0;
> +
> +err:
> + atmel_hlcdc_layer_update_rollback(&plane->layer);
> + return ret;
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_set(struct drm_crtc *c,
> + struct drm_file *file_priv,
> + uint32_t handle,
> + uint32_t width, uint32_t height)
> +{
> + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + struct drm_gem_cma_object *cma_gem = NULL;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + mutex_lock(&crtc->cursor.lock);
> +
> + if (handle) {
> + struct drm_gem_object *gem;
> +
> + gem = drm_gem_object_lookup(c->dev, file_priv, handle);
> + if (unlikely(!gem)) {
> + ret = -ENOENT;
> + goto out;
> + }
> +
> + cma_gem = to_drm_gem_cma_obj(gem);
> + }
> +
> + crtc->cursor.req = crtc->cursor.status;
> +
> + crtc->cursor.req.gem = cma_gem;
> + crtc->cursor.req.width = width;
> + crtc->cursor.req.height = height;
> + atmel_hlcdc_crtc_cursor_prepare_req(crtc);
> +
> + ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
> + if (cma_gem)
> + drm_gem_object_unreference_unlocked(&cma_gem->base);
> +
> +out:
> + mutex_unlock(&crtc->cursor.lock);
> +
> + return ret;
> +}
> +
> +static int atmel_hlcdc_crtc_cursor_move(struct drm_crtc *c, int x, int y)
> +{
> + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
> + struct atmel_hlcdc_plane *plane = crtc->cursor.plane;
> + int ret;
> +
> + if (unlikely(!plane))
> + return -ENOTSUPP;
> +
> + mutex_lock(&crtc->cursor.lock);
> + /*
> + * If there's no cursor update pending, get the current
> + * cursor size and buffer.
> + */
> + crtc->cursor.req = crtc->cursor.status;
> +
> + crtc->cursor.req.x = x;
> + crtc->cursor.req.y = y;
> + atmel_hlcdc_crtc_cursor_prepare_req(crtc);
> +
> + ret = atmel_hlcdc_crtc_cursor_apply_req(crtc);
> + mutex_unlock(&crtc->cursor.lock);
> +
> + 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,
> +};
> +
> +static const struct drm_crtc_funcs atmel_hlcdc_crtc_with_cursor_funcs = {
> + .page_flip = atmel_hlcdc_crtc_page_flip,
> + .set_config = drm_crtc_helper_set_config,
> + .destroy = atmel_hlcdc_crtc_destroy,
> + .cursor_set = atmel_hlcdc_crtc_cursor_set,
> + .cursor_move = atmel_hlcdc_crtc_cursor_move,
> +};
> +
> +int atmel_hlcdc_crtc_create(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_dc *dc = dev->dev_private;
> + struct atmel_hlcdc_planes *planes = dc->planes;
> + const struct drm_crtc_funcs *funcs;
> + 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;
> + }
> +
> + mutex_init(&crtc->cursor.lock);
> + crtc->hlcdc = dc->hlcdc;
> + crtc->cursor.plane = planes->cursor;
> +
> + if (planes->cursor)
> + funcs = &atmel_hlcdc_crtc_with_cursor_funcs;
> + else
> + funcs = &atmel_hlcdc_crtc_funcs;
> +
> + ret = drm_crtc_init_with_planes(dev, &crtc->base,
> + &planes->primary->base,
> + planes->cursor ? &planes->cursor->base : NULL,
> + funcs);
> + if (ret < 0)
> + goto fail;
> +
> + atmel_hlcdc_layer_set_finished(&planes->primary->layer,
> + atmel_hlcdc_crtc_finish_page_flip,
> + crtc);
> +
> + 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..e4ce24e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
> @@ -0,0 +1,477 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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 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);
> +}
> +
> +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_panel_create(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 atmel_hlcdc_dc_desc *desc;
> + struct atmel_hlcdc_dc *dc;
> + int ret;
> +
> + desc = platform_get_drvdata(pdev);
> +
> + dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL);
> + if (!dc) {
> + dev_err(dev->dev, "failed to allocate private data\n");
> + return -ENOMEM;
> + }
> +
> + dc->desc = platform_get_drvdata(pdev);
> + 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");
> + return ret;
> + }
> +
> + 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);
> + 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);
> +
> + dc->fbdev = drm_fbdev_cma_init(dev, 24,
> + dev->mode_config.num_crtc,
> + dev->mode_config.num_connector);
> +
> + if (IS_ERR(dc->fbdev)) {
> + ret = PTR_ERR(dc->fbdev);
> + goto err_periph_clk_disable;
> + }
> +
> + return 0;
> +
> +err_periph_clk_disable:
> + clk_disable_unprepare(dc->hlcdc->periph_clk);
> +
> + 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);
> +
> + 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 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,
> + },
> + },
> + {
> + .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 int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev)
> +{
> + const struct of_device_id *match;
> + int ret;
> +
> + match = of_match_node(atmel_hlcdc_of_match, pdev->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;
> + }
> +
> + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, (void *)match->data);
> +
> + 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-dc" },
> + { },
> +};
> +
> +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-dc",
> + .owner = THIS_MODULE,
> + .of_match_table = atmel_hlcdc_dc_of_match,
> + },
> +};
> +module_platform_driver(atmel_hlcdc_dc_platform_driver);
> +
> +MODULE_AUTHOR("Jean-Jacques Hiblot <[email protected]>");
> +MODULE_AUTHOR("Boris BREZILLON <[email protected]>");
> +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..5d2919e
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
> @@ -0,0 +1,178 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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
> + */
> +struct atmel_hlcdc_plane_properties {
> + struct drm_property *alpha;
> +};
> +
> +/**
> + * 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;
> + u8 alpha;
> +};
> +
> +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 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
> + */
> +struct atmel_hlcdc_dc {
> + 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];
> +};
> +
> +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_update_buffers(struct atmel_hlcdc_plane *plane,
> + u32 pixel_format,
> + struct drm_gem_cma_object **gems,
> + unsigned int *pitches,
> + unsigned int *offsets,
> + uint32_t src_x, uint32_t src_y,
> + uint32_t src_w, uint32_t src_h);
> +
> +void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
> + u32 format);
> +
> +int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
> + u32 format);
> +
> +int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
> + struct drm_crtc *crtc,
> + 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);
> +
> +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_panel_create(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..b449fe1
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
> @@ -0,0 +1,701 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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_gem_flip_release(struct atmel_hlcdc_layer *layer,
> + struct atmel_hlcdc_layer_gem_flip *flip)
> +{
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!flip->gems[i])
> + break;
> +
> + drm_gem_object_unreference_unlocked(flip->gems[i]);
> + }
> +
> + kfree(flip);
> +}
> +
> +static void atmel_hlcdc_layer_gc_work(struct work_struct *work)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc =
> + container_of(work,
> + struct atmel_hlcdc_layer_gem_flip_gc,
> + work);
> + struct atmel_hlcdc_layer *layer =
> + container_of(gc , struct atmel_hlcdc_layer, gc);
> +
> + while (true) {
> + struct atmel_hlcdc_layer_gem_flip *flip;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&gc->list_lock, flags);
> + flip = list_first_entry_or_null(&gc->list,
> + struct atmel_hlcdc_layer_gem_flip,
> + node);
> + if (flip)
> + list_del(&flip->node);
> + spin_unlock_irqrestore(&gc->list_lock, flags);
> +
> + if (!flip)
> + break;
> +
> + atmel_hlcdc_layer_gem_flip_release(layer, flip);
> +
> + mutex_lock(&gc->finished_lock);
> + if (gc->finished)
> + gc->finished(gc->finished_data);
> + mutex_unlock(&gc->finished_lock);
> + }
> +}
> +
> +static void
> +atmel_hlcdc_layer_gem_flip_put(struct atmel_hlcdc_layer *layer,
> + struct atmel_hlcdc_layer_gem_flip *flip)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> + unsigned long flags;
> +
> + if (--flip->remaining_gems <= 0) {
> + spin_lock_irqsave(&gc->list_lock, flags);
> + list_add_tail(&flip->node,
> + &gc->list);
> + spin_unlock_irqrestore(&gc->list_lock, flags);
> + schedule_work(&gc->work);
> + }
> +}
> +
> +static void atmel_hlcdc_layer_start_queue(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;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + dma->cur[i] = dma->queue[i];
> + if (!dma->queue[i])
> + continue;
> + dma->queue[i] = NULL;
> +
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_ADDR(i),
> + dma->cur[i]->addr);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_CTRL(i),
> + dma->cur[i]->ctrl);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_NEXT(i),
> + dma->cur[i]->next);
> + }
> +}
> +
> +static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer)
> +{
> + 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;
> + unsigned int cfg;
> + u32 action = 0;
> + int i;
> +
> + if (upd->pending < 0 || upd->pending > 1)
> + 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;
> + }
> +
> + if (slot->gem_flip && slot->gem_flip->remaining_gems) {
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + struct atmel_hlcdc_dma_channel_dscr *dscr;
> +
> + if (!slot->gem_flip->gems[i])
> + break;
> +
> + dscr = slot->dscrs[i];
> + slot->dscrs[i] = NULL;
> +
> + if (!dma->enabled) {
> + action |= ATMEL_HLCDC_LAYER_DMA_CHAN;
> + 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);
> + dma->cur[i] = dscr;
> + } else {
> + action |= ATMEL_HLCDC_LAYER_A2Q;
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_PLANE_HEAD(i),
> + dscr->next);
> + dma->queue[i] = dscr;
> + }
> + }
> +
> + dma->enabled = true;
> + slot->gem_flip = NULL;
> + }
> +
> + if (action)
> + regmap_write(regmap,
> + desc->regs_offset + ATMEL_HLCDC_LAYER_CHER,
> + action);
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!slot->dscrs[i])
> + continue;
> +
> + slot->dscrs[i]->gem_flip = NULL;
> + slot->dscrs[i] = NULL;
> + }
> +
> + if (slot->gem_flip) {
> + atmel_hlcdc_layer_gem_flip_put(layer, slot->gem_flip);
> + slot->gem_flip = NULL;
> + }
> +
> + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
> + memset(slot->configs, 0,
> + sizeof(*slot->configs) * layer->desc->nconfigs);
> +
> + upd->pending = -1;
> +}
> +
> +static bool
> +atmel_hlcdc_layer_dma_channel_active(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (dma->cur[i])
> + return true;
> + }
> +
> + return false;
> +}
> +
> +void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip *flip_gc[ATMEL_HLCDC_MAX_PLANES];
> + 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_dma_channel_dscr *dscr;
> + unsigned long flags;
> + unsigned int isr, imr;
> + unsigned int 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;
> +
> + memset(flip_gc, 0, sizeof(flip_gc));
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if ((status & ATMEL_HLCDC_LAYER_DONE_IRQ(i)) ||
> + (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i))) {
> + dscr = dma->cur[i];
> + dma->cur[i] = NULL;
> + flip_gc[i] = dscr->gem_flip;
> + dscr->gem_flip = NULL;
> + }
> +
> + if (status & ATMEL_HLCDC_LAYER_ADD_IRQ(i)) {
> + dma->cur[i] = dma->queue[i];
> + dma->queue[i] = NULL;
> + }
> + }
> +
> + /*
> + * The DMA channel might have been disabled before we were able to
> + * add the new frame to the DMA transfer queue.
> + * Try to re-enable the channel in this case.
> + */
> + if (!atmel_hlcdc_layer_dma_channel_active(layer)) {
> + if (atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + atmel_hlcdc_layer_start_queue(layer);
> +
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_CHDR,
> + ATMEL_HLCDC_LAYER_A2Q);
> + regmap_write(regmap,
> + desc->regs_offset +
> + ATMEL_HLCDC_LAYER_CHER,
> + ATMEL_HLCDC_LAYER_DMA_CHAN);
> + } else {
> + dma->enabled = false;
> + }
> + }
> +
> + if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + struct atmel_hlcdc_layer_update *upd = &layer->update;
> +
> + spin_lock(&upd->pending_lock);
> + atmel_hlcdc_layer_update_apply(layer);
> + spin_unlock(&upd->pending_lock);
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!flip_gc[i])
> + break;
> +
> + atmel_hlcdc_layer_gem_flip_put(layer, flip_gc[i]);
> + }
> +}
> +
> +int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + unsigned long flags;
> + int i;
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!dma->cur[i])
> + break;
> +
> + dma->cur[i]->ctrl = 0;
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + return 0;
> +}
> +
> +void atmel_hlcdc_layer_set_finished(struct atmel_hlcdc_layer *layer,
> + void (*finished)(void *data),
> + void *data)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + mutex_lock(&gc->finished_lock);
> + gc->finished = finished;
> + gc->finished_data = data;
> + mutex_unlock(&gc->finished_lock);
> +}
> +
> +static void atmel_hlcdc_layer_update_reset(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;
> + int i;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return;
> +
> + slot = &upd->slots[upd->next];
> + bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
> + memset(slot->configs, 0,
> + sizeof(*slot->configs) * layer->desc->nconfigs);
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes; i++) {
> + if (!slot->dscrs[i])
> + break;
> + slot->dscrs[i]->gem_flip = NULL;
> + slot->dscrs[i] = NULL;
> + }
> + spin_unlock_irqrestore(&layer->dma.lock, flags);
> +
> + if (slot->gem_flip) {
> + atmel_hlcdc_layer_gem_flip_release(layer, slot->gem_flip);
> + slot->gem_flip = NULL;
> + }
> +
> + upd->next = -1;
> +}
> +
> +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_gem_flip *gem_flip;
> + struct atmel_hlcdc_layer_update_slot *slot;
> + unsigned long flags;
> + int i, j = 0;
> + int pending;
> +
> + gem_flip = kzalloc(sizeof(*gem_flip), GFP_KERNEL);
> + if (!gem_flip)
> + return -ENOMEM;
> +
> + mutex_lock(&upd->lock);
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + upd->next = pending ? 0 : 1;
> +
> + slot = &upd->slots[upd->next];
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + for (i = 0; i < layer->max_planes * 4; i++) {
> + if (!dma->dscrs[i].gem_flip) {
> + slot->dscrs[j++] = &dma->dscrs[i];
> + dma->dscrs[i].gem_flip = gem_flip;
> + if (j == layer->max_planes)
> + break;
> + }
> + }
> +
> + if (j < layer->max_planes) {
> + for (i = 0; i < j; i++)
> + slot->dscrs[i]->gem_flip = NULL;
> + }
> + spin_unlock_irqrestore(&layer->dma.lock, flags);
> +
> + if (j < layer->max_planes) {
> + mutex_unlock(&upd->lock);
> + kfree(gem_flip);
> + return -EBUSY;
> + }
> +
> + slot->gem_flip = gem_flip;
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + if (pending >= 0) {
> + memcpy(upd->slots[upd->next].configs,
> + upd->slots[upd->pending].configs,
> + layer->desc->nconfigs * sizeof(u32));
> + memcpy(upd->slots[upd->next].updated_configs,
> + upd->slots[upd->pending].updated_configs,
> + DIV_ROUND_UP(layer->desc->nconfigs,
> + BITS_PER_BYTE * sizeof(unsigned long)) *
> + sizeof(unsigned long));
> + }
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + if (pending < 0)
> + regmap_bulk_read(regmap, ATMEL_HLCDC_LAYER_CFG(layer, 0),
> + upd->slots[upd->next].configs,
> + layer->desc->nconfigs);
> +
> + 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);
> + mutex_unlock(&upd->lock);
> +}
> +
> +int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
> + int plane_id,
> + struct drm_gem_cma_object *gem,
> + unsigned int offset)
> +{
> + struct atmel_hlcdc_layer_update *upd = &layer->update;
> + struct atmel_hlcdc_layer_gem_flip *gem_flip;
> + struct atmel_hlcdc_layer_update_slot *slot;
> + struct atmel_hlcdc_dma_channel_dscr *dscr;
> + struct drm_gem_object *old_gem;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return -EINVAL;
> +
> + if (plane_id >= layer->max_planes || plane_id < 0)
> + return -EINVAL;
> +
> + slot = &upd->slots[upd->next];
> + dscr = slot->dscrs[plane_id];
> + dscr->addr = gem->paddr + offset;
> + dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH;
> + gem_flip = dscr->gem_flip;
> +
> + old_gem = gem_flip->gems[plane_id];
> +
> + if (gem) {
> + drm_gem_object_reference(&gem->base);
> + gem_flip->gems[plane_id] = &gem->base;
> + gem_flip->remaining_gems++;
> + } else {
> + gem_flip->gems[plane_id] = NULL;
> + }
> +
> + if (old_gem) {
> + drm_gem_object_unreference_unlocked(old_gem);
> + gem_flip->remaining_gems--;
> + }
> +
> + return 0;
> +}
> +
> +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;
> + int pending;
> +
> + if (upd->next < 0 || upd->next > 1)
> + return;
> +
> + slot = &upd->slots[upd->next];
> +
> + spin_lock_irqsave(&upd->pending_lock, flags);
> + pending = upd->pending;
> + upd->pending = upd->next;
> + upd->next = pending;
> + if (pending >= 0 && !slot->gem_flip->remaining_gems) {
> + struct atmel_hlcdc_layer_gem_flip *gem_flip = slot->gem_flip;
> + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
> +
> + memcpy(dscrs, slot->dscrs, sizeof(dscrs));
> + slot->gem_flip = upd->slots[pending].gem_flip;
> + memcpy(slot->dscrs, upd->slots[pending].dscrs,
> + sizeof(slot->dscrs));
> + upd->slots[pending].gem_flip = gem_flip;
> + memcpy(upd->slots[pending].dscrs, dscrs, sizeof(dscrs));
> + }
> + spin_unlock_irqrestore(&upd->pending_lock, flags);
> +
> + if (pending >= 0) {
> + atmel_hlcdc_layer_update_reset(layer);
> + mutex_unlock(&upd->lock);
> + return;
> + }
> +
> + spin_lock_irqsave(&dma->lock, flags);
> + if (!atmel_hlcdc_layer_dma_channel_busy(layer)) {
> + spin_lock(&upd->pending_lock);
> + atmel_hlcdc_layer_update_apply(layer);
> + spin_unlock(&upd->pending_lock);
> + }
> + spin_unlock_irqrestore(&dma->lock, flags);
> +
> + atmel_hlcdc_layer_update_reset(layer);
> + mutex_unlock(&upd->lock);
> +}
> +
> +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));
> + }
> +
> + spin_lock_init(&dma->lock);
> +
> + 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];
> +
> + if (!dscr->gem_flip)
> + continue;
> +
> + atmel_hlcdc_layer_gem_flip_put(layer, dscr->gem_flip);
> + }
> +
> + dma_free_coherent(dev->dev, layer->max_planes * 4 *
> + sizeof(*dma->dscrs), dma->dscrs,
> + dma->dscrs[0].next);
> +}
> +
> +static void atmel_hlcdc_layer_gc_init(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + INIT_LIST_HEAD(&gc->list);
> + spin_lock_init(&gc->list_lock);
> + INIT_WORK(&gc->work, atmel_hlcdc_layer_gc_work);
> + mutex_init(&gc->finished_lock);
> +}
> +
> +static void atmel_hlcdc_layer_gc_cleanup(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_gem_flip_gc *gc = &layer->gc;
> +
> + flush_work(&gc->work);
> +}
> +
> +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;
> + spin_lock_init(&upd->pending_lock);
> + mutex_init(&upd->lock);
> +
> + 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->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;
> + }
> +
> + atmel_hlcdc_layer_gc_init(layer);
> + 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);
> +
> + for (i = 0; i < layer->max_planes; i++)
> + regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER,
> + ATMEL_HLCDC_LAYER_ADD_IRQ(i) |
> + ATMEL_HLCDC_LAYER_DONE_IRQ(i));
> +
> + 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);
> + atmel_hlcdc_layer_gc_cleanup(layer);
> +}
> 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..868f444
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
> @@ -0,0 +1,417 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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/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(n) BIT(2 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_DSCR_IRQ(n) BIT(3 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_ADD_IRQ(n) BIT(4 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_DONE_IRQ(n) BIT(5 + ((n) * 8))
> +#define ATMEL_HLCDC_LAYER_OVR_IRQ(n) BIT(6 + ((n) * 8))
> +
> +#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_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
> +
> +/**
> + * 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
> + * @color_space_conv: color spave 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 color_space_conv;
> +};
> +
> +/**
> + * Atmel HLCDC GEM 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 the reference GEM objects anymore
> + * (i.e. the layer was disabled or updated).
> + *
> + * @node: list element structure. Used to attach the GEM flip structure to
> + * the garbage collector when the layer no longer need the referenced
> + * GEM objects.
> + * @gems: the referenced GEM objects. The number of GEM object referenced
> + * depends on the chosen format.
> + * @remaining_gems: the number of GEM object still referenced by the layer.
> + * When no more objects are referenced the GEM flip structure
> + * is added to the garbage collector.
> + */
> +struct atmel_hlcdc_layer_gem_flip {
> + struct list_head node;
> + struct drm_gem_object *gems[ATMEL_HLCDC_MAX_PLANES];
> + int remaining_gems;
> +};
> +
> +/**
> + * 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;
> + struct atmel_hlcdc_layer_gem_flip *gem_flip;
> +} __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
> + *@gem_flip: gem_flip object
> + *@updated_configs: bitmask used to record modified configs
> + *@configs: new config values
> + */
> +struct atmel_hlcdc_layer_update_slot {
> + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
> + struct atmel_hlcdc_layer_gem_flip *gem_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
> + *@pending_lock: lock to access the pending field
> + *@lock: layer update lock
> + */
> +struct atmel_hlcdc_layer_update {
> + struct atmel_hlcdc_layer_update_slot slots[2];
> + int pending;
> + int next;
> + spinlock_t pending_lock;
> + struct mutex lock;
> +};
> +
> +/**
> + * Atmel HLCDC Layer GEM flip garbage collector structure
> + *
> + * This structure is used to schedule GEM object release when we are in
> + * interrupt context (within atmel_hlcdc_layer_irq function).
> + *
> + *@list: GEM flip objects to release
> + *@list_lock: lock to access the GEM flip list
> + *@work: work queue scheduled when there are GEM flip to collect
> + *@finished: action to execute the GEM flip and all attached objects have been
> + * released
> + *@finished_data: data passed to the finished callback
> + *@finished_lock: lock to access finished related fields
> + */
> +struct atmel_hlcdc_layer_gem_flip_gc {
> + struct list_head list;
> + spinlock_t list_lock;
> + struct work_struct work;
> + void (*finished)(void *data);
> + void *finished_data;
> + struct mutex finished_lock;
> +};
> +
> +/**
> + * Atmel HLCDC Layer DMA channel structure
> + *
> + * This structure stores informations on the DMA channel associated to a
> + * given layer.
> + *
> + *@enabled: DMA channel status
> + *@lock: lock to access the DMA channel fields
> + *@cur: current DMA transfers (one for each plane/buffer)
> + *@queue: next DMA transfers, to be launch on next frame update
> + *@dscrs: allocated DMA descriptors
> + */
> +struct atmel_hlcdc_layer_dma_channel {
> + bool enabled;
> + spinlock_t lock;
> + struct atmel_hlcdc_dma_channel_dscr *cur[ATMEL_HLCDC_MAX_PLANES];
> + struct atmel_hlcdc_dma_channel_dscr *queue[ATMEL_HLCDC_MAX_PLANES];
> + 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: GEM flip garbage collector
> + *@update: update handler
> + */
> +struct atmel_hlcdc_layer {
> + const struct atmel_hlcdc_layer_desc *desc;
> + int max_planes;
> + struct atmel_hlcdc *hlcdc;
> + struct atmel_hlcdc_layer_dma_channel dma;
> + struct atmel_hlcdc_layer_gem_flip_gc gc;
> + struct atmel_hlcdc_layer_update update;
> +};
> +
> +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);
> +
> +int atmel_hlcdc_layer_update_set_gem(struct atmel_hlcdc_layer *layer,
> + int plane_id,
> + struct drm_gem_cma_object *gem,
> + unsigned int offset);
> +
> +void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer);
> +
> +void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer);
> +
> +static inline bool
> +atmel_hlcdc_layer_dma_channel_busy(struct atmel_hlcdc_layer *layer)
> +{
> + struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
> + int i;
> +
> + for (i = 0; i < layer->max_planes; i++) {
> + if (dma->queue[i])
> + return true;
> + }
> +
> + return false;
> +}
> +
> +#endif /* DRM_ATMEL_HLCDC_LAYER_H */
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
> new file mode 100644
> index 0000000..3295021
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_panel.c
> @@ -0,0 +1,351 @@
> +/*
> + * Copyright (C) 2014 Traphandler
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Jean-Jacques Hiblot <[email protected]>
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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 <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,
> +};
> +
> +/**
> + * Atmel HLCDC Panel structure
> + *
> + * This structure stores informations about an DRM panel connected through
> + * the RGB connector.
> + *
> + * @mode: RGB output mode
> + * @connector: DRM connector
> + * @encoder: DRM encoder
> + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
> + * @panel: pointer to the attached DRM panel
> + * @pinctrl: pinctrl state used by the current RGB output mode
> + * @np: DRM panel DT node
> + * @dpms: current DPMS mode
> + */
> +struct atmel_hlcdc_panel {
> + enum atmel_hlcdc_connector_rgb_mode mode;
> + struct drm_connector connector;
> + struct drm_encoder encoder;
> + struct atmel_hlcdc *hlcdc;
> + struct drm_panel *panel;
> + struct pinctrl *pinctrl;
> + struct device_node *np;
> + int dpms;
> +};
> +
> +static const char * const pin_state_names[] = {
> + [ATMEL_HLCDC_CONNECTOR_RGB444] = "rgb-444",
> + [ATMEL_HLCDC_CONNECTOR_RGB565] = "rgb-565",
> + [ATMEL_HLCDC_CONNECTOR_RGB666] = "rgb-666",
> + [ATMEL_HLCDC_CONNECTOR_RGB888] = "rgb-888",
> +};
> +
> +static inline struct atmel_hlcdc_panel *
> +drm_connector_to_atmel_hlcdc_panel(struct drm_connector *connector)
> +{
> + return container_of(connector, struct atmel_hlcdc_panel, connector);
> +}
> +
> +static inline struct atmel_hlcdc_panel *
> +drm_encoder_to_atmel_hlcdc_panel(struct drm_encoder *encoder)
> +{
> + return container_of(encoder, struct atmel_hlcdc_panel, encoder);
> +}
> +
> +static void atmel_hlcdc_panel_encoder_dpms(struct drm_encoder *encoder,
> + int mode)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_encoder_to_atmel_hlcdc_panel(encoder);
> + struct regmap *regmap = panel->hlcdc->regmap;
> + unsigned int status;
> +
> + if (mode != DRM_MODE_DPMS_ON)
> + mode = DRM_MODE_DPMS_OFF;
> +
> + if (mode == panel->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(panel->hlcdc->sys_clk);
> + if (panel->panel)
> + drm_panel_disable(panel->panel);
> + } else {
> + if (panel->panel)
> + drm_panel_enable(panel->panel);
> + clk_prepare_enable(panel->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();
> + }
> +
> + panel->dpms = mode;
> +}
> +
> +static bool
> +atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder,
> + const struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted)
> +{
> + return true;
> +}
> +
> +static void atmel_hlcdc_panel_encoder_prepare(struct drm_encoder *encoder)
> +{
> + atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
> +}
> +
> +static void atmel_hlcdc_panel_encoder_commit(struct drm_encoder *encoder)
> +{
> + atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
> +}
> +
> +static void
> +atmel_hlcdc_panel_encoder_mode_set(struct drm_encoder *encoder,
> + struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_encoder_to_atmel_hlcdc_panel(encoder);
> + unsigned long prate = clk_get_rate(panel->hlcdc->sys_clk);
> + unsigned long mode_rate = mode->clock * 1000;
> + int div;
> + u32 cfg0 = 0;
> +
> + if ((prate / 2) < mode_rate) {
> + prate *= 2;
> + cfg0 |= ATMEL_HLCDC_CLKSEL;
> + }
> +
> + div = DIV_ROUND_UP(prate, mode_rate);
> + if (div < 2)
> + div = 2;
> +
> + cfg0 |= ATMEL_HLCDC_CLKDIV(div);
> +
> + regmap_update_bits(panel->hlcdc->regmap, ATMEL_HLCDC_CFG(0),
> + ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK, cfg0);
> +}
> +
> +static struct drm_encoder_helper_funcs encoder_helper_funcs = {
> + .dpms = atmel_hlcdc_panel_encoder_dpms,
> + .mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup,
> + .prepare = atmel_hlcdc_panel_encoder_prepare,
> + .commit = atmel_hlcdc_panel_encoder_commit,
> + .mode_set = atmel_hlcdc_panel_encoder_mode_set,
> +};
> +
> +static void atmel_hlcdc_panel_encoder_destroy(struct drm_encoder *encoder)
> +{
> + drm_encoder_cleanup(encoder);
> + memset(encoder, 0, sizeof(*encoder));
> +}
> +
> +static const struct drm_encoder_funcs encoder_funcs = {
> + .destroy = atmel_hlcdc_panel_encoder_destroy,
> +};
> +
> +static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> + int ret;
> +
> + if (!panel->panel)
> + return -ENOENT;
> +
> + ret = panel->panel->funcs->get_modes(panel->panel);
> + return ret;
> +}
> +
> +static int atmel_hlcdc_panel_mode_valid(struct drm_connector *connector,
> + struct drm_display_mode *mode)
> +{
> + return MODE_OK;
> +}
> +
> +static struct drm_encoder *
> +atmel_hlcdc_panel_best_encoder(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + return &panel->encoder;
> +}
> +
> +static struct drm_connector_helper_funcs connector_helper_funcs = {
> + .get_modes = atmel_hlcdc_panel_get_modes,
> + .mode_valid = atmel_hlcdc_panel_mode_valid,
> + .best_encoder = atmel_hlcdc_panel_best_encoder,
> +};
> +
> +static enum drm_connector_status
> +atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + if (!panel->panel) {
> + panel->panel = of_drm_find_panel(panel->np);
> + if (panel->panel)
> + drm_panel_attach(panel->panel, &panel->connector);
> + }
> +
> + if (panel->panel)
> + return connector_status_connected;
> +
> + return connector_status_disconnected;
> +}
> +
> +static void
> +atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
> +{
> + struct atmel_hlcdc_panel *panel =
> + drm_connector_to_atmel_hlcdc_panel(connector);
> +
> + if (panel->panel)
> + drm_panel_detach(panel->panel);
> +
> + drm_sysfs_connector_remove(connector);
> + drm_connector_cleanup(connector);
> +
> + pinctrl_put(panel->pinctrl);
> +}
> +
> +static const struct drm_connector_funcs connector_funcs = {
> + .dpms = drm_helper_connector_dpms,
> + .detect = atmel_hlcdc_panel_connector_detect,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = atmel_hlcdc_panel_connector_destroy,
> +};
> +
> +int atmel_hlcdc_panel_create(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_dc *dc = dev->dev_private;
> + struct atmel_hlcdc_panel *panel;
> + struct of_phandle_args out_args;
> + u32 cfg;
> + int ret;
> +
> + panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL);
> + if (!panel)
> + return -ENOMEM;
> +
> + ret = of_parse_phandle_with_fixed_args(dev->dev->of_node,
> + "atmel,panel", 2, 0,
> + &out_args);
> + if (ret) {
> + dev_err(dev->dev, "failed to retrieve panel info from DT\n");
> + return ret;
> + }
> +
> + switch (out_args.args[0]) {
> + case ATMEL_HLCDC_CONNECTOR_RGB444:
> + case ATMEL_HLCDC_CONNECTOR_RGB565:
> + case ATMEL_HLCDC_CONNECTOR_RGB666:
> + case ATMEL_HLCDC_CONNECTOR_RGB888:
> + break;
> + default:
> + dev_err(dev->dev, "unknown RGB mode\n");
> + return -EINVAL;
> + }
> +
> + panel->np = out_args.np;
> + panel->mode = out_args.args[0];
> + panel->pinctrl = pinctrl_get_select(dev->dev,
> + pin_state_names[panel->mode]);
> + if (IS_ERR(panel->pinctrl)) {
> + dev_err(dev->dev, "could not request pinctrl state\n");
> + return PTR_ERR(panel->pinctrl);
> + }
> +
> + cfg = out_args.args[1] & (ATMEL_HLCDC_HSPOL |
> + ATMEL_HLCDC_VSPOL |
> + ATMEL_HLCDC_VSPDLYS |
> + ATMEL_HLCDC_VSPDLYE |
> + ATMEL_HLCDC_DISPPOL |
> + ATMEL_HLCDC_DISPDLY |
> + ATMEL_HLCDC_VSPSU |
> + ATMEL_HLCDC_VSPHO);
> + cfg |= panel->mode << 8;
> +
> + regmap_write(dc->hlcdc->regmap,
> + ATMEL_HLCDC_CFG(5),
> + cfg);
> +
> + panel->dpms = DRM_MODE_DPMS_OFF;
> +
> + panel->hlcdc = dc->hlcdc;
> +
> + drm_connector_init(dev, &panel->connector, &connector_funcs,
> + DRM_MODE_CONNECTOR_LVDS);
> + drm_connector_helper_add(&panel->connector, &connector_helper_funcs);
> + panel->connector.dpms = DRM_MODE_DPMS_OFF;
> + panel->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
> +
> + drm_encoder_init(dev, &panel->encoder, &encoder_funcs,
> + DRM_MODE_ENCODER_LVDS);
> + drm_encoder_helper_add(&panel->encoder, &encoder_helper_funcs);
> +
> + drm_mode_connector_attach_encoder(&panel->connector, &panel->encoder);
> + drm_sysfs_connector_add(&panel->connector);
> +
> + panel->encoder.possible_crtcs = 0x1;
> +
> + 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..f428b47
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
> @@ -0,0 +1,658 @@
> +/*
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + *
> + * Author: Boris BREZILLON <[email protected]>
> + *
> + * 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_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_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_NV61,
> + DRM_FORMAT_YUV422,
> + DRM_FORMAT_NV21,
> +};
> +
> +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_NV61:
> + *mode = ATMEL_HLCDC_NV61_MODE;
> + break;
> + case DRM_FORMAT_YUV422:
> + *mode = ATMEL_HLCDC_YUV422_MODE;
> + break;
> + case DRM_FORMAT_NV21:
> + *mode = ATMEL_HLCDC_NV21_MODE;
> + break;
> + case DRM_FORMAT_YUV420:
> + *mode = ATMEL_HLCDC_YUV420_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,
> +};
> +
> +int atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
> + struct drm_crtc *crtc,
> + 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)
> +{
> + const struct atmel_hlcdc_layer_cfg_layout *layout =
> + &plane->layer.desc->layout;
> +
> + if (!crtc_h || !crtc_w)
> + return -EINVAL;
> +
> + if (!layout->pos && (crtc_x || crtc_y))
> + return -EINVAL;
> +
> + if ((crtc_x + crtc_w) > crtc->mode.crtc_hdisplay ||
> + (crtc_y + crtc_h) > crtc->mode.crtc_vdisplay)
> + return -EINVAL;
> +
> + if (!layout->size &&
> + (crtc->mode.crtc_hdisplay != crtc_w ||
> + crtc->mode.crtc_vdisplay != crtc_h))
> + return -EINVAL;
> +
> + if (plane->layer.desc->max_height &&
> + crtc_h > plane->layer.desc->max_height)
> + return -EINVAL;
> +
> + if (plane->layer.desc->max_width &&
> + crtc_w > plane->layer.desc->max_width)
> + return -EINVAL;
> +
> + if (!layout->memsize && (crtc_h != src_h || crtc_w != src_w))
> + return -EINVAL;
> +
> + if (layout->size)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->size,
> + 0xffffffff,
> + (crtc_w - 1) |
> + ((crtc_h - 1) << 16));
> +
> + if (layout->memsize)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->memsize,
> + 0xffffffff,
> + (src_w - 1) |
> + ((src_h - 1) << 16));
> +
> + if (layout->pos)
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->pos,
> + 0xffffffff,
> + crtc_x | (crtc_y << 16));
> +
> + /* TODO: rework the rescaling part */
> + if (crtc_w != src_w || crtc_h != src_h) {
> + u32 factor_reg = 0;
> +
> + if (crtc_w != src_w) {
> + int i;
> + u32 factor;
> + u32 *coeff_tab = heo_upscaling_xcoef;
> + u32 max_memsize;
> +
> + if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
> + factor++;
> + max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
> + if (max_memsize > src_w)
> + factor--;
> + factor_reg |= factor | 0x80000000;
> + }
> +
> + if (crtc_h != src_h) {
> + int i;
> + u32 factor;
> + u32 *coeff_tab = heo_upscaling_ycoef;
> + u32 max_memsize;
> +
> + if (crtc_w < 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 * src_w) - (256 * 4)) / crtc_w;
> + factor++;
> + max_memsize = ((factor * crtc_w) + (256 * 4)) / 2048;
> + if (max_memsize > src_w)
> + factor--;
> + factor_reg |= (factor << 16) | 0x80000000;
> + }
> +
> + atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff,
> + factor_reg);
> + }
> +
> + return 0;
> +}
> +
> +void atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
> + u32 format)
> +{
> + 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(format))
> + cfg |= ATMEL_HLCDC_LAYER_LAEN;
> + else
> + cfg |= (plane->alpha << ATMEL_HLCDC_LAYER_GA_SHIFT) |
> + 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 |
> + ATMEL_HLCDC_LAYER_GA_MASK, cfg);
> +}
> +
> +int atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
> + u32 format)
> +{
> + u32 mode;
> + int ret;
> +
> + ret = atmel_hlcdc_format_to_plane_mode(format, &mode);
> + if (ret)
> + return ret;
> +
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + ATMEL_HLCDC_LAYER_FORMAT_CFG_ID,
> + 0xffffffff,
> + mode);
> +
> + return 0;
> +}
> +
> +int atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
> + u32 pixel_format,
> + struct drm_gem_cma_object **gems,
> + unsigned int *pitches,
> + unsigned int *offsets,
> + uint32_t src_x, uint32_t src_y,
> + uint32_t src_w, uint32_t src_h)
> +{
> + struct atmel_hlcdc_layer *layer = &plane->layer;
> + const struct atmel_hlcdc_layer_cfg_layout *layout =
> + &layer->desc->layout;
> + int bpp[ATMEL_HLCDC_MAX_PLANES];
> + int nplanes;
> + int ret = 0;
> + int i;
> +
> + nplanes = drm_format_num_planes(pixel_format);
> +
> + for (i = 0; i < nplanes; i++) {
> + bpp[i] = drm_format_plane_cpp(pixel_format, i);
> + if (!bpp[i])
> + return -EINVAL;
> +
> + if (!gems[i])
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < nplanes; i++) {
> + int offset;
> +
> + offset = offsets[i] + (src_y * pitches[i]) +
> + (src_x * bpp[i]);
> +
> + ret = atmel_hlcdc_layer_update_set_gem(&plane->layer, i,
> + gems[i], offset);
> + if (ret)
> + return ret;
> +
> + if (layout->xstride[i])
> + atmel_hlcdc_layer_update_cfg(&plane->layer,
> + layout->xstride[i],
> + 0xffffffff,
> + pitches[i] - (bpp[i] * src_w));
> + }
> +
> + 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 drm_gem_cma_object *gems[ATMEL_HLCDC_MAX_PLANES];
> + int nplanes;
> + int ret = 0;
> + int i;
> +
> + /* Subpixel positioning is not supported */
> + if ((src_x | src_y | src_w | src_h) & SUBPIXEL_MASK) {
> + DRM_DEBUG_KMS("Primary base does not support subpixel positioning\n");
> + return -EINVAL;
> + }
> +
> + src_h >>= 16;
> + src_w >>= 16;
> + src_x >>= 16;
> + src_y >>= 16;
> +
> + nplanes = drm_format_num_planes(fb->pixel_format);
> + if (nplanes > ATMEL_HLCDC_MAX_PLANES)
> + return -EINVAL;
> +
> + for (i = 0; i < nplanes; i++)
> + gems[i] = drm_fb_cma_get_gem_obj(fb, i);
> +
> + atmel_hlcdc_layer_update_start(&plane->layer);
> + ret = atmel_hlcdc_plane_update_pos_and_size(plane, crtc,
> + crtc_x, crtc_y,
> + crtc_w, crtc_h,
> + src_x, src_y,
> + src_w, src_h);
> + if (ret)
> + goto out;
> +
> + atmel_hlcdc_plane_update_general_settings(plane, fb->pixel_format);
> +
> + ret = atmel_hlcdc_plane_update_format(plane, fb->pixel_format);
> + if (ret)
> + goto out;
> +
> +
> + ret = atmel_hlcdc_plane_update_buffers(plane, fb->pixel_format,
> + gems,
> + fb->pitches, fb->offsets,
> + src_x, src_y,
> + src_w, src_h);
> +
> + if (ret)
> + goto out;
> +
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> + drm_framebuffer_reference(fb);
> + if (plane->base.fb)
> + drm_framebuffer_unreference(plane->base.fb);
> + plane->base.fb = fb;
> +
> + return 0;
> +
> +out:
> + atmel_hlcdc_layer_update_rollback(&plane->layer);
> +
> + return ret;
> +}
> +
> +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);
> + plane->alpha = alpha;
> + atmel_hlcdc_plane_update_general_settings(plane,
> + plane->base.fb->pixel_format);
> + atmel_hlcdc_layer_update_commit(&plane->layer);
> +
> + 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
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +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 *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);
> +
> + /* Set default property values*/
> + plane->alpha = 0xff;
> +
> + 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);
> +
> + return plane;
> +}
> +
> +static struct atmel_hlcdc_plane_properties *
> +atmel_hlcdc_plane_create_properties(struct drm_device *dev)
> +{
> + struct atmel_hlcdc_plane_properties *props;
> +
> + 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);
> +
> + 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]);
> + 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;
> + drm_object_attach_property(&plane->base.base,
> + props->alpha, 255);
> + break;
> +
> + case ATMEL_HLCDC_CURSOR_LAYER:
> + if (planes->cursor)
> + return ERR_PTR(-EINVAL);
> + planes->cursor = plane;
> + drm_object_attach_property(&plane->base.base,
> + props->alpha, 255);
> + break;
> +
> + default:
> + break;
> + }
> + }
> +
> + return planes;
> +}
> diff --git a/drivers/gpu/drm/atmel_hlcdc/Kconfig b/drivers/gpu/drm/atmel_hlcdc/Kconfig
> new file mode 100644
> index 0000000..59c8eeb
> --- /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 && ARM && 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..08de8d7
> --- /dev/null
> +++ b/drivers/gpu/drm/atmel_hlcdc/Makefile
> @@ -0,0 +1,8 @@
> +atmel_hlcdc-y := atmel_hlcdc_crtc.o \
> + atmel_hlcdc_drv.o \
> + atmel_hlcdc_layer.o \
> + atmel_hlcdc_panel.o \
> + atmel_hlcdc_plane.o \
> + atmel_hlcdc_pwm.o
> +
> +obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel_hlcdc.o
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
On Sun, 15 Jun 2014 11:11:04 +0200
Jean-Jacques Hiblot <[email protected]> wrote:
> >
> > +config PWM_ATMEL_HLCDC_PWM
> > + tristate "Atmel HLCDC PWM support"
> > + depends on MFD_ATMEL_HLCDC
> I'd personnaly prefer a 'select' instead of 'depends on' here. Or maybe
> the MFD driver should enabled y defaut for platforms supporting the hlcdc.
After taking a closer look at other drivers, it seems most of sub
drivers "depends on" the MFD driver, so, unless other people complain
about that, I'll keep this definition.
Lee, any opinion on this point ?
Best Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com