This patch series includes the followings: - Generic command mode interface - FIMD I80 interface - DSI command mode interface for Exynos5420 SoC - S6E3FA0 command mode type panel driver
This patch series is based on exynos-drm-next branch.
Previous patch set, RFC v1: http://www.spinics.net/lists/dri-devel/msg57513.html RFC v2: http://www.spinics.net/lists/dri-devel/msg57945.html RFC v3: http://www.spinics.net/lists/dri-devel/msg58364.html
Changelog v2: - Moves panel delays and size DT properties to panel probe routine. - Moves CPU timings relevant DT properties from FIMD to panel DT.
Changelog v3: - Splits core and driver patchset. - Renames CPU timings to CPU mode timings. - Enhances panel code readability.
Changelog v4: - Implements generic command mode interface. - Adds command mode information to drm_display_mode. - Removes CPU mode timings.
Thank you. Best regards YJ
YoungJun Cho (18): drm/exynos: dsi: move the EoT packets configuration point drm/exynos: use wait_event_timeout() for safety usage ARM: dts: video: add command mode display timing DT bindings video: add command mode and command mode display timing drm_modes: add command mode helpers ARM: dts: sysreg: add exynos5 compatible to DT bindings ARM: dts: samsung-fimd: add I80 specific properties drm/exynos: add TE handler to support MIPI DSI command mode interface drm/exynos: dsi: add TE handler to support command mode interface drm/exynos: fimd: support I80 interface ARM: dts: exynos_dsim: add exynos5420 compatible to DT bindings drm/exynos: dsi: add driver data to support Exynos5420 ARM: dts: s6e3fa0: add DT bindings drm/panel: add S6E3FA0 driver ARM: dts: exynos4: add system register node ARM: dts: exynos5: add system register support ARM: dts: exynos5420: add mipi-phy node ARM: dts: exynos5420: add dsi node
.../devicetree/bindings/arm/samsung/sysreg.txt | 1 + .../devicetree/bindings/panel/samsung,s6e3fa0.txt | 45 ++ .../bindings/video/cmdmode-display-timing.txt | 64 +++ .../devicetree/bindings/video/exynos_dsim.txt | 4 +- .../devicetree/bindings/video/samsung-fimd.txt | 2 + arch/arm/boot/dts/exynos4.dtsi | 1 + arch/arm/boot/dts/exynos5.dtsi | 6 + arch/arm/boot/dts/exynos5420.dtsi | 21 + drivers/gpu/drm/drm_modes.c | 59 ++ drivers/gpu/drm/exynos/Kconfig | 1 + drivers/gpu/drm/exynos/exynos_drm_crtc.c | 16 +- drivers/gpu/drm/exynos/exynos_drm_crtc.h | 7 + drivers/gpu/drm/exynos/exynos_drm_drv.h | 3 + drivers/gpu/drm/exynos/exynos_drm_dsi.c | 175 +++++- drivers/gpu/drm/exynos/exynos_drm_fimd.c | 277 ++++++++-- drivers/gpu/drm/panel/Kconfig | 7 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-s6e3fa0.c | 570 ++++++++++++++++++++ drivers/video/Kconfig | 3 + drivers/video/Makefile | 2 + drivers/video/cmdmode.c | 42 ++ drivers/video/cmdmode_display_timing.c | 26 + drivers/video/of_cmdmode.c | 55 ++ drivers/video/of_cmdmode_display_timing.c | 212 ++++++++ include/drm/drm_mipi_dsi.h | 8 + include/drm/drm_modes.h | 12 + include/video/cmdmode.h | 67 +++ include/video/cmdmode_display_timing.h | 59 ++ include/video/of_cmdmode.h | 19 + include/video/of_cmdmode_display_timing.h | 26 + include/video/samsung_fimd.h | 3 +- 31 files changed, 1723 insertions(+), 71 deletions(-) create mode 100644 Documentation/devicetree/bindings/panel/samsung,s6e3fa0.txt create mode 100644 Documentation/devicetree/bindings/video/cmdmode-display-timing.txt create mode 100644 drivers/gpu/drm/panel/panel-s6e3fa0.c create mode 100644 drivers/video/cmdmode.c create mode 100644 drivers/video/cmdmode_display_timing.c create mode 100644 drivers/video/of_cmdmode.c create mode 100644 drivers/video/of_cmdmode_display_timing.c create mode 100644 include/video/cmdmode.h create mode 100644 include/video/cmdmode_display_timing.h create mode 100644 include/video/of_cmdmode.h create mode 100644 include/video/of_cmdmode_display_timing.h
This configuration could be used in MIPI DSI command mode also.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index eb73e3b..956e5f3 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -472,8 +472,6 @@ static int exynos_dsi_init_link(struct exynos_dsi *dsi)
if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH)) reg |= DSIM_MFLUSH_VS; - if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)) - reg |= DSIM_EOT_DISABLE; if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) reg |= DSIM_SYNC_INFORM; if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) @@ -490,6 +488,9 @@ static int exynos_dsi_init_link(struct exynos_dsi *dsi) reg |= DSIM_HSA_MODE; }
+ if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)) + reg |= DSIM_EOT_DISABLE; + switch (dsi->format) { case MIPI_DSI_FMT_RGB888: reg |= DSIM_MAIN_PIX_FORMAT_RGB888;
There could be the case that the page flip operation isn't finished correctly with some abnormal condition such as panel reset. So this patch replaces wait_event() with wait_event_timeout() to avoid waiting for page flip completion infinitely.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/gpu/drm/exynos/exynos_drm_crtc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index e930d4f..1419d11 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -69,8 +69,9 @@ static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
if (mode > DRM_MODE_DPMS_ON) { /* wait for the completion of page flip. */ - wait_event(exynos_crtc->pending_flip_queue, - atomic_read(&exynos_crtc->pending_flip) == 0); + wait_event_timeout(exynos_crtc->pending_flip_queue, + !atomic_read(&exynos_crtc->pending_flip), + HZ/20); drm_vblank_off(crtc->dev, exynos_crtc->pipe); }
This patch adds DT bindings for command mode display timing.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- .../bindings/video/cmdmode-display-timing.txt | 64 ++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Documentation/devicetree/bindings/video/cmdmode-display-timing.txt
diff --git a/Documentation/devicetree/bindings/video/cmdmode-display-timing.txt b/Documentation/devicetree/bindings/video/cmdmode-display-timing.txt new file mode 100644 index 0000000..7cedfe4 --- /dev/null +++ b/Documentation/devicetree/bindings/video/cmdmode-display-timing.txt @@ -0,0 +1,64 @@ +cmdmode-display-timing bindings +=================================== + +cmdmode-display-timings node +-------------------------------- + +required properties: + - none + +optional properties: + - native-mode: The native mode for the display, in case multiple modes are + provided. When omitted, assume the first node is the native. + +timing subnode +-------------- + +required properties: + - clock-frequency: display clock in Hz + - hactive, vactive: display resolution + - cs-setup: clock cycles for the active period of address signal is enabled + until chip select is enabled. + - wr-setup: clock cycles for the active period of CS signal is enabled until + write signal is enabled. + - wr-active: clock cycles for the active period of CS is enabled. + - wr-hold: clock cycles for the active period of CS is disabled until write + signal is disabled. + +optional properties: + +There are different ways of describing the capabilities of a display. The +devicetree representation corresponds to the one commonly found in datasheets +for displays. If a display supports multiple signal timings, the native-mode +can be specified. + +The parameters are defined as: + + VCLK(internal) __|¯¯¯¯¯¯|_____|¯¯¯¯¯¯|_____|¯¯¯¯¯¯|_____|¯¯¯¯¯¯|_____|¯¯ + : : : : : + Address Output --:<XXXXXXXXXXX:XXXXXXXXXXXX:XXXXXXXXXXXX:XXXXXXXXXXXX:XX + | CS-SETUP+1 | : : : + |<---------->| : : : + Chip Select ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|____________:____________:____________|¯¯ + | WR-SETUP+1 | | WR-HOLD+1 | + |<---------->| |<---------->| + Write Enable ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|____________|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + | WR-ACTIVE+1| + |<---------->| + Video Data ----------------------------<XXXXXXXXXXXXXXXXXXXXXXXXX>-- + +Example: + + cmdmode-display-timings { + native-mode = <&timing0>; + timing0: 1080p24 { + /* 1920x1080p24 */ + clock-frequency = <52000000>; + hactive = <1920>; + vactive = <1080>; + cs-setup = <0>; + wr-setup = <0>; + wr-active = <1>; + wr-hold = <0>; + }; + };
This patch is based on videomode and display_timing relevant codes. To support command mode panel, it does not need to guide its timing information to the display controller like video mode panel, but it requires signal timings to transfer video data. So this patch adds cmdmode struct, cmdmode_display_timing struct and the according helper functions to convert cmdmode_display_timing to a generic cmdmode.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/video/Kconfig | 3 + drivers/video/Makefile | 2 + drivers/video/cmdmode.c | 42 ++++++ drivers/video/cmdmode_display_timing.c | 26 ++++ drivers/video/of_cmdmode.c | 55 ++++++++ drivers/video/of_cmdmode_display_timing.c | 212 +++++++++++++++++++++++++++++ include/video/cmdmode.h | 67 +++++++++ include/video/cmdmode_display_timing.h | 59 ++++++++ include/video/of_cmdmode.h | 19 +++ include/video/of_cmdmode_display_timing.h | 26 ++++ 10 files changed, 511 insertions(+) create mode 100644 drivers/video/cmdmode.c create mode 100644 drivers/video/cmdmode_display_timing.c create mode 100644 drivers/video/of_cmdmode.c create mode 100644 drivers/video/of_cmdmode_display_timing.c create mode 100644 include/video/cmdmode.h create mode 100644 include/video/cmdmode_display_timing.h create mode 100644 include/video/of_cmdmode.h create mode 100644 include/video/of_cmdmode_display_timing.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index dade5b7..2022244 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -36,6 +36,9 @@ config VIDEO_OUTPUT_CONTROL config VIDEOMODE_HELPERS bool
+config CMDMODE_HELPERS + bool + config HDMI bool
diff --git a/drivers/video/Makefile b/drivers/video/Makefile index ae17ddf..922edbd 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -175,6 +175,8 @@ obj-$(CONFIG_FB_VIRTUAL) += vfb.o #video output switch sysfs driver obj-$(CONFIG_VIDEO_OUTPUT_CONTROL) += output.o obj-$(CONFIG_VIDEOMODE_HELPERS) += display_timing.o videomode.o +obj-$(CONFIG_CMDMODE_HELPERS) += cmdmode_display_timing.o cmdmode.o ifeq ($(CONFIG_OF),y) obj-$(CONFIG_VIDEOMODE_HELPERS) += of_display_timing.o of_videomode.o +obj-$(CONFIG_CMDMODE_HELPERS) += of_cmdmode_display_timing.o of_cmdmode.o endif diff --git a/drivers/video/cmdmode.c b/drivers/video/cmdmode.c new file mode 100644 index 0000000..3d3eeb8 --- /dev/null +++ b/drivers/video/cmdmode.c @@ -0,0 +1,42 @@ +/* + * generic cmdmode display timing functions + * + * Copyright (c) 2014 YoungJun Cho yj44.cho@samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/errno.h> +#include <linux/export.h> +#include <video/cmdmode_display_timing.h> +#include <video/cmdmode.h> + +void cmdmode_from_timing(const struct cmdmode_display_timing *cmdt, + struct cmdmode *cm) +{ + cm->pixelclock = cmdt->pixelclock; + cm->hactive = cmdt->hactive; + cm->vactive = cmdt->vactive; + cm->cs_setup = cmdt->cs_setup; + cm->wr_setup = cmdt->wr_setup; + cm->wr_active = cmdt->wr_active; + cm->wr_hold = cmdt->wr_hold; +} +EXPORT_SYMBOL_GPL(cmdmode_from_timing); + +int cmdmode_from_timings(const struct cmdmode_display_timings *cmdts, + struct cmdmode *cm, unsigned int index) +{ + struct cmdmode_display_timing *cmdt; + + cmdt = cmdmode_display_timings_get(cmdts, index); + if (!cmdt) + return -EINVAL; + + cmdmode_from_timing(cmdt, cm); + + return 0; +} +EXPORT_SYMBOL_GPL(cmdmode_from_timings); diff --git a/drivers/video/cmdmode_display_timing.c b/drivers/video/cmdmode_display_timing.c new file mode 100644 index 0000000..88bab08 --- /dev/null +++ b/drivers/video/cmdmode_display_timing.c @@ -0,0 +1,26 @@ +/* + * generic cmdmode display timing functions + * + * Copyright (c) 2014 YoungJun Cho yj44.cho@samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/export.h> +#include <linux/slab.h> +#include <video/cmdmode_display_timing.h> + +void cmdmode_display_timings_release(struct cmdmode_display_timings *cmdts) +{ + if (cmdts->timings) { + unsigned int i; + + for (i = 0; i < cmdts->num_timings; i++) + kfree(cmdts->timings[i]); + kfree(cmdts->timings); + } + kfree(cmdts); +} +EXPORT_SYMBOL_GPL(cmdmode_display_timings_release); diff --git a/drivers/video/of_cmdmode.c b/drivers/video/of_cmdmode.c new file mode 100644 index 0000000..d63294e --- /dev/null +++ b/drivers/video/of_cmdmode.c @@ -0,0 +1,55 @@ +/* + * generic cmdmode helper + * + * Copyright (c) 2014 YoungJun Cho yj44.cho@samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/of.h> +#include <video/cmdmode_display_timing.h> +#include <video/of_cmdmode_display_timing.h> +#include <video/of_cmdmode.h> +#include <video/cmdmode.h> + +/** + * of_get_cmdmode - get the cmdmode #<index> from devicetree + * @np - devicenode with the cmdmode_display_timings + * @cm - set to return value + * @index - index into list of cmdmode_display_timings + * (Set this to OF_USE_CMDMODE_NATIVE_MODE to use whatever mode is + * specified as native mode in the DT.) + * + * DESCRIPTION: + * Get a list of all display timings and put the one + * specified by index into *cm. This function should only be used, if + * only one cmdmode is to be retrieved. A driver that needs to work + * with multiple/all cmdmodes should work with + * of_get_cmdmode_display_timings instead. + **/ +int of_get_cmdmode(struct device_node *np, struct cmdmode *cm, int index) +{ + struct cmdmode_display_timings *cmdts; + int ret; + + cmdts = of_get_cmdmode_display_timings(np); + if (!cmdts) { + pr_err("%s: no timings specified\n", of_node_full_name(np)); + return -EINVAL; + } + + if (index == OF_USE_CMDMODE_NATIVE_MODE) + index = cmdts->native_mode; + + ret = cmdmode_from_timings(cmdts, cm, index); + if (ret) + return ret; + + cmdmode_display_timings_release(cmdts); + + return 0; +} +EXPORT_SYMBOL_GPL(of_get_cmdmode); diff --git a/drivers/video/of_cmdmode_display_timing.c b/drivers/video/of_cmdmode_display_timing.c new file mode 100644 index 0000000..fcf2b35 --- /dev/null +++ b/drivers/video/of_cmdmode_display_timing.c @@ -0,0 +1,212 @@ +/* + * OF helpers for parsing cmdmode display timings + * + * Copyright (c) 2014 YoungJun Cho yj44.cho@samsung.com + * + * based on of_cmdmode.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/export.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <video/cmdmode_display_timing.h> +#include <video/of_cmdmode_display_timing.h> + +/** + * of_parse_cmdmode_display_timing - parse cmdmode_display_timing entry + * from device_node + * @np: device_node with the properties + **/ +static int of_parse_cmdmode_display_timing(const struct device_node *np, + struct cmdmode_display_timing *cmdt) +{ + int ret = 0; + + memset(cmdt, 0, sizeof(*cmdt)); + + ret |= of_property_read_u32(np, "clock-frequency", &cmdt->pixelclock); + ret |= of_property_read_u32(np, "hactive", &cmdt->hactive); + ret |= of_property_read_u32(np, "vactive", &cmdt->vactive); + ret |= of_property_read_u32(np, "cs-setup", &cmdt->cs_setup); + ret |= of_property_read_u32(np, "wr-setup", &cmdt->wr_setup); + ret |= of_property_read_u32(np, "wr-active", &cmdt->wr_active); + ret |= of_property_read_u32(np, "wr-hold", &cmdt->wr_hold); + + if (ret) { + pr_err("%s: error reading cmdmode timing properties\n", + of_node_full_name(np)); + return -EINVAL; + } + + return 0; +} + +/** + * of_get_cmdmode_display_timing - parse a cmdmode_display_timing entry + * @np: device_node with the timing subnode + * @name: name of the timing node + * @cmdt: cmdmode_display_timing struct to fill + **/ +int of_get_cmdmode_display_timing(struct device_node *np, const char *name, + struct cmdmode_display_timing *cmdt) +{ + struct device_node *timing_np; + + if (!np) { + pr_err("%s: no devicenode given\n", of_node_full_name(np)); + return -EINVAL; + } + + timing_np = of_get_child_by_name(np, name); + if (!timing_np) { + pr_err("%s: could not find node '%s'\n", + of_node_full_name(np), name); + return -ENOENT; + } + + return of_parse_cmdmode_display_timing(timing_np, cmdt); +} +EXPORT_SYMBOL_GPL(of_get_cmdmode_display_timing); + +/** + * of_get_cmdmode_display_timings - parse all cmdmode_display_timing + * entries from a device_node + * @np: device_node with the subnodes + **/ +struct cmdmode_display_timings* +of_get_cmdmode_display_timings(struct device_node *np) +{ + struct device_node *timings_np; + struct device_node *entry; + struct device_node *native_mode; + struct cmdmode_display_timings *cmdts; + + if (!np) { + pr_err("%s: no device node given\n", of_node_full_name(np)); + return NULL; + } + + timings_np = of_get_child_by_name(np, "cmdmode-display-timings"); + if (!timings_np) { + pr_err("%s: could not find cmdmode-display-timings node\n", + of_node_full_name(np)); + return NULL; + } + + cmdts = kzalloc(sizeof(*cmdts), GFP_KERNEL); + if (!cmdts) { + pr_err("%s: could not allocate struct cmdts'\n", + of_node_full_name(np)); + goto cmdtsfail; + } + + entry = of_parse_phandle(timings_np, "native-mode", 0); + /* assume first child as native mode if none provided */ + if (!entry) + entry = of_get_next_child(np, NULL); + /* if there is no child, it is useless to go on */ + if (!entry) { + pr_err("%s: no timing specifications given\n", + of_node_full_name(np)); + goto entryfail; + } + + pr_debug("%s: using %s as default timing\n", + of_node_full_name(np), entry->name); + + native_mode = entry; + + cmdts->num_timings = of_get_child_count(timings_np); + if (cmdts->num_timings == 0) { + /* should never happen, as entry was already found above */ + pr_err("%s: no timings specified\n", of_node_full_name(np)); + goto entryfail; + } + + cmdts->timings = kzalloc(sizeof(struct cmdmode_display_timing *) * + cmdts->num_timings, GFP_KERNEL); + if (!cmdts->timings) { + pr_err("%s: could not allocate timings array\n", + of_node_full_name(np)); + goto entryfail; + } + + cmdts->num_timings = 0; + cmdts->native_mode = 0; + + for_each_child_of_node(timings_np, entry) { + struct cmdmode_display_timing *cmdt; + int r; + + cmdt = kzalloc(sizeof(*cmdt), GFP_KERNEL); + if (!cmdt) { + pr_err("%s: could not allocate cmdmode_display_timing\n" + , of_node_full_name(np)); + goto timingfail; + } + + r = of_parse_cmdmode_display_timing(entry, cmdt); + if (r) { + /* + * to not encourage wrong devicetrees, fail in case of + * an error + */ + pr_err("%s: error in timing %d\n", + of_node_full_name(np), cmdts->num_timings + 1); + goto timingfail; + } + + if (native_mode == entry) + cmdts->native_mode = cmdts->num_timings; + + cmdts->timings[cmdts->num_timings] = cmdt; + cmdts->num_timings++; + } + of_node_put(timings_np); + /* + * native_mode points to the device_node returned by of_parse_phandle + * therefore call of_node_put on it + */ + of_node_put(native_mode); + + pr_debug("%s: got %d timings. Using timing #%d as default\n", + of_node_full_name(np), cmdts->num_timings, + cmdts->native_mode + 1); + + return cmdts; + +timingfail: + if (native_mode) + of_node_put(native_mode); + cmdmode_display_timings_release(cmdts); +entryfail: + kfree(cmdts); +cmdtsfail: + of_node_put(timings_np); + return NULL; +} +EXPORT_SYMBOL_GPL(of_get_cmdmode_display_timings); + +/** + * of_cmdmode_display_timings_exist - check if a display-timings node is + * provided + * @np: device_node with the timing + **/ +int of_cmdmode_display_timings_exist(struct device_node *np) +{ + struct device_node *timings_np; + + if (!np) + return -EINVAL; + + timings_np = of_parse_phandle(np, "cmdmode-display-timings", 0); + if (!timings_np) + return -EINVAL; + + of_node_put(timings_np); + return 1; +} +EXPORT_SYMBOL_GPL(of_cmdmode_display_timings_exist); diff --git a/include/video/cmdmode.h b/include/video/cmdmode.h new file mode 100644 index 0000000..61ee71e --- /dev/null +++ b/include/video/cmdmode.h @@ -0,0 +1,67 @@ +/* + * generic cmdmode description + * + * Copyright 2014 YoungJun Cho yj44.cho@samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_CMDMODE_H +#define __LINUX_CMDMODE_H + +#include <linux/types.h> +#include <video/cmdmode_display_timing.h> + +/* + * Subsystem independent description of a cmdmode. + * Can be generated from struct cmdmode_display_timing. + * @pixelclock: display clock in Hz + * @hactive: horizontal active video + * @vactive: vertical active video + * @cs_setup: clock cycles for the active period of address signal is enabled + * until chip select is enabled + * @wr_setup: clock cycles for the active period of CS signal is enabled until + * write signal is enabled + * @wr_active: clock cycles for the active period of CS is enabled + * @wr_hold: clock cycles for the active period of CS is disabled until write + * signal is disabled + */ +struct cmdmode { + unsigned long pixelclock; + + u32 hactive; + u32 vactive; + + u32 cs_setup; + u32 wr_setup; + u32 wr_active; + u32 wr_hold; +}; + +/** + * cmdmode_from_timing - convert display timing to cmdmode + * @cmdt: cmdmode_display_timing structure + * @cm: return value + * + * DESCRIPTION: + * This function converts a struct cmdmode_display_timing to a struct cmdmode. + */ +void cmdmode_from_timing(const struct cmdmode_display_timing *cmdt, + struct cmdmode *cm); + +/** + * cmdmode_from_timings - convert one display timings entry to cmdmode + * @disp: structure with all possible timing entries + * @cm: return value + * @index: index into the list of display timings in devicetree + * + * DESCRIPTION: + * This function converts one struct cmdmode_display_timing entry to a + * struct cmdmode. + */ +int cmdmode_from_timings(const struct cmdmode_display_timings *cmdts, + struct cmdmode *cm, unsigned int index); + +#endif /* __LINUX_CMDMODE_H */ diff --git a/include/video/cmdmode_display_timing.h b/include/video/cmdmode_display_timing.h new file mode 100644 index 0000000..5005660 --- /dev/null +++ b/include/video/cmdmode_display_timing.h @@ -0,0 +1,59 @@ +/* + * description of cmdmode display timings + * + * Copyright 2014 YoungJun Cho yj44.cho@samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_CMDMODE_DISPLAY_TIMING_H +#define __LINUX_CMDMODE_DISPLAY_TIMING_H + +#include <linux/types.h> + +/* + * Single "mode" entry. This describes one set of signal timings a display can + * have in one setting. This struct can later be converted to struct cmdmode + * (see include/video/cmdmode.h). + */ +struct cmdmode_display_timing { + u32 pixelclock; + + u32 hactive; + u32 vactive; + + u32 cs_setup; + u32 wr_setup; + u32 wr_active; + u32 wr_hold; +}; + +/* + * This describes all timing settings a display provides. + * The native_mode is the default setting for this display. + * Drivers that can handle multiple cmdmodes should work with this struct + * and convert each entry to the desired end result. + */ +struct cmdmode_display_timings { + unsigned int num_timings; + unsigned int native_mode; + + struct cmdmode_display_timing **timings; +}; + +/* get one entry from struct cmdmode_display_timings */ +static inline struct cmdmode_display_timing* +cmdmode_display_timings_get(const struct cmdmode_display_timings *cmdts, + unsigned int index) +{ + if (cmdts->num_timings > index) + return cmdts->timings[index]; + else + return NULL; +} + +void cmdmode_display_timings_release(struct cmdmode_display_timings *cmdts); + +#endif /* __LINUX_CMDDMODE_DISPLAY_TIMING_H */ diff --git a/include/video/of_cmdmode.h b/include/video/of_cmdmode.h new file mode 100644 index 0000000..fb7c6c7 --- /dev/null +++ b/include/video/of_cmdmode.h @@ -0,0 +1,19 @@ +/* + * Copyright 2014 YoungJun Cho yj44.cho@samsung.com + * + * cmdmode of-helpers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_OF_CMDMODE_H +#define __LINUX_OF_CMDMODE_H + +struct device_node; +struct cmdmode; + +int of_get_cmdmode(struct device_node *np, struct cmdmode *cm, int index); + +#endif /* __LINUX_OF_CMDMODE_H */ diff --git a/include/video/of_cmdmode_display_timing.h b/include/video/of_cmdmode_display_timing.h new file mode 100644 index 0000000..6be91ba --- /dev/null +++ b/include/video/of_cmdmode_display_timing.h @@ -0,0 +1,26 @@ +/* + * Copyright 2014 YoungJun Cho yj44.cho@samsung.com + * + * cmdmode display timings of helpers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_OF_CMDMODE_DISPLAY_TIMING_H +#define __LINUX_OF_CMDMODE_DISPLAY_TIMING_H + +struct device_node; +struct cmdmode_display_timing; +struct cmdmode_display_timings; + +#define OF_USE_CMDMODE_NATIVE_MODE -1 + +int of_get_cmdmode_display_timing(struct device_node *np, const char *name, + struct cmdmode_display_timing *cmdt); +struct cmdmode_display_timings* + of_get_cmdmode_display_timings(struct device_node *np); +int of_cmdmode_display_timings_exist(struct device_node *np); + +#endif /* __LINUX_OF_CMDMODE_DISPLAY_TIMING_H */
This patch adds helper functions to convert cmdmode to drm_display_mode
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/gpu/drm/drm_modes.c | 59 +++++++++++++++++++++++++++++++++++++++++++ include/drm/drm_modes.h | 12 +++++++++ 2 files changed, 71 insertions(+)
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index 8b41057..7451893 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -37,6 +37,8 @@ #include <drm/drm_crtc.h> #include <video/of_videomode.h> #include <video/videomode.h> +#include <video/of_cmdmode.h> +#include <video/cmdmode.h> #include <drm/drm_modes.h>
#include "drm_crtc_internal.h" @@ -651,6 +653,63 @@ EXPORT_SYMBOL_GPL(of_get_drm_display_mode); #endif /* CONFIG_OF */ #endif /* CONFIG_VIDEOMODE_HELPERS */
+#ifdef CONFIG_CMDMODE_HELPERS +int drm_display_mode_from_cmdmode(const struct cmdmode *cm, + struct drm_display_mode *dmode) +{ + dmode->hdisplay = cm->hactive; + dmode->htotal = dmode->hsync_end = dmode->hsync_start = dmode->hdisplay; + + dmode->vdisplay = cm->vactive; + dmode->vtotal = dmode->vsync_end = dmode->vsync_start = dmode->vdisplay; + + dmode->clock = cm->pixelclock / 1000; + + dmode->cs_setup = cm->cs_setup; + dmode->wr_setup = cm->wr_setup; + dmode->wr_active = cm->wr_active; + dmode->wr_hold = cm->wr_hold; + + dmode->flags = 0; + drm_mode_set_name(dmode); + + return 0; +} +EXPORT_SYMBOL_GPL(drm_display_mode_from_cmdmode); + +#ifdef CONFIG_OF +/** + * of_get_drm_cmdmode_display_mode - get a drm_display_mode from devicetree + * @np: device_node with the timing specification + * @dmode: will be set to the return value + * @index: index into the list of display timings in devicetree + * + * This function is expensive and should only be used, if only one mode is to be + * read from DT. To get multiple modes start with + * of_get_cmdmode_display_timings and work with that instead. + */ +int of_get_drm_cmdmode_display_mode(struct device_node *np, + struct drm_display_mode *dmode, int index) +{ + struct cmdmode cm; + int ret; + + ret = of_get_cmdmode(np, &cm, index); + if (ret) + return ret; + + drm_display_mode_from_cmdmode(&cm, dmode); + + pr_debug("%s: got %dx%d display mode from %s\n", + of_node_full_name(np), cm.hactive, cm.vactive, np->name); + drm_mode_debug_printmodeline(dmode); + + return 0; +} +EXPORT_SYMBOL_GPL(of_get_drm_cmdmode_display_mode); +#endif /* CONFIG_OF */ +#endif /* CONFIG_CMDMODE_HELPERS */ + /** * drm_mode_set_name - set the name on a mode * @mode: name will be set in this mode diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h index 2dbbf99..53ecd03 100644 --- a/include/drm/drm_modes.h +++ b/include/drm/drm_modes.h @@ -144,6 +144,12 @@ struct drm_display_mode { int vrefresh; /* in Hz */ int hsync; /* in kHz */ enum hdmi_picture_aspect picture_aspect_ratio; + + /* Command mode info - refers to video/cmdmode.h */ + int cs_setup; + int wr_setup; + int wr_active; + int wr_hold; };
/* mode specified on the command line */ @@ -176,6 +182,7 @@ static inline bool drm_mode_is_stereo(const struct drm_display_mode *mode)
struct drm_connector; struct drm_cmdline_mode; +struct cmdmode;
struct drm_display_mode *drm_mode_create(struct drm_device *dev); void drm_mode_destroy(struct drm_device *dev, struct drm_display_mode *mode); @@ -200,6 +207,11 @@ void drm_display_mode_from_videomode(const struct videomode *vm, int of_get_drm_display_mode(struct device_node *np, struct drm_display_mode *dmode, int index); +extern int drm_display_mode_from_cmdmode(const struct cmdmode *cm, + struct drm_display_mode *dmode); +extern int of_get_drm_cmdmode_display_mode(struct device_node *np, + struct drm_display_mode *dmode, + int index);
void drm_mode_set_name(struct drm_display_mode *mode); int drm_mode_hsync(const struct drm_display_mode *mode);
This patch adds relevant to exynos5 compatible for exynos5 SoCs.
Changelog v2: - Changes title and description (commented by Sachin Kamat)
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- .../devicetree/bindings/arm/samsung/sysreg.txt | 1 + 1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/arm/samsung/sysreg.txt b/Documentation/devicetree/bindings/arm/samsung/sysreg.txt index 0ab3251..fd71581 100644 --- a/Documentation/devicetree/bindings/arm/samsung/sysreg.txt +++ b/Documentation/devicetree/bindings/arm/samsung/sysreg.txt @@ -3,6 +3,7 @@ SAMSUNG S5P/Exynos SoC series System Registers (SYSREG) Properties: - compatible : should contain "samsung,<chip name>-sysreg", "syscon"; For Exynos4 SoC series it should be "samsung,exynos4-sysreg", "syscon"; + For Exynos5 SoC series it should be "samsung,exynos5-sysreg", "syscon"; - reg : offset and length of the register set.
Example:
In case of using MIPI command mode interface panel, the relevant registers should be set. So this patch adds relevant DT bindings.
Changelog v2: - Changes "samsung,sysreg-phandle" to "samsung,sysreg" Changelog v3: - Moves CPU timings relevant properties to panel DT (commented by Laurent Pinchart, Andrzej Hajda)
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- .../devicetree/bindings/video/samsung-fimd.txt | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/video/samsung-fimd.txt b/Documentation/devicetree/bindings/video/samsung-fimd.txt index 2dad41b..6bf93e9 100644 --- a/Documentation/devicetree/bindings/video/samsung-fimd.txt +++ b/Documentation/devicetree/bindings/video/samsung-fimd.txt @@ -44,6 +44,8 @@ Optional Properties: - display-timings: timing settings for FIMD, as described in document [1]. Can be used in case timings cannot be provided otherwise or to override timings provided by the panel. +- samsung,sysreg: handle to syscon used to control the system registers +- vidout-i80-ldi: boolean to support i80 interface instead of rgb one
The device node can contain 'port' child nodes according to the bindings defined in [2]. The following are properties specific to those nodes:
To support MIPI DSI command mode interface, the panel should generates Tearing Effect synchronization signal between MCU and FB to display video images. And the display controller should trigger to transfer video image at this signal. So the panel receives the TE IRQ, then calls this handler chains to notify it to the display controller.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/gpu/drm/exynos/exynos_drm_crtc.c | 11 +++++++++++ drivers/gpu/drm/exynos/exynos_drm_crtc.h | 7 +++++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 3 +++ include/drm/drm_mipi_dsi.h | 8 ++++++++ 4 files changed, 29 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index 1419d11..d902d64 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -491,3 +491,14 @@ void exynos_drm_crtc_complete_scanout(struct drm_framebuffer *fb) manager->ops->wait_for_vblank(manager); } } + +int exynos_drm_crtc_te_handler(struct drm_crtc *crtc) +{ + struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager; + int ret = 0; + + if (manager->ops->te_handler) + ret = manager->ops->te_handler(manager); + + return ret; +} diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.h b/drivers/gpu/drm/exynos/exynos_drm_crtc.h index c27b66c..eb78c16 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.h +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.h @@ -32,4 +32,11 @@ void exynos_drm_crtc_plane_commit(struct drm_crtc *crtc, int zpos); void exynos_drm_crtc_plane_enable(struct drm_crtc *crtc, int zpos); void exynos_drm_crtc_plane_disable(struct drm_crtc *crtc, int zpos);
+/* + * This function calls the crtc device(manager)'s te_handler() callback + * to trigger to transfer video image at the tearing effect synchronization + * signal. + */ +int exynos_drm_crtc_te_handler(struct drm_crtc *crtc); + #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index 4c5cf68..4d8865d 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -186,6 +186,8 @@ struct exynos_drm_display { * @win_commit: apply hardware specific overlay data to registers. * @win_enable: enable hardware specific overlay. * @win_disable: disable hardware specific overlay. + * @te_handler: trigger to transfer video image at the tearing effect + * synchronization signal if there is a page flip request. */ struct exynos_drm_manager; struct exynos_drm_manager_ops { @@ -207,6 +209,7 @@ struct exynos_drm_manager_ops { void (*win_commit)(struct exynos_drm_manager *mgr, int zpos); void (*win_enable)(struct exynos_drm_manager *mgr, int zpos); void (*win_disable)(struct exynos_drm_manager *mgr, int zpos); + int (*te_handler)(struct exynos_drm_manager *mgr); };
/* diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 7209df1..f6d4c85 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -49,6 +49,13 @@ struct mipi_dsi_msg { * @detach: detach DSI device from DSI host * @transfer: send and/or receive DSI packet, return number of received bytes, * or error + * @te_handler: call the crtc te_handler() callback from DSI host. + * The panel generates tearing effect synchronization signal + * between MCU and FB to display video images. + * And the display controller should trigger to transfer video + * image at this signal. + * So the panel receives the TE IRQ, then calls this handler + * to notify it to the display controller. */ struct mipi_dsi_host_ops { int (*attach)(struct mipi_dsi_host *host, @@ -57,6 +64,7 @@ struct mipi_dsi_host_ops { struct mipi_dsi_device *dsi); ssize_t (*transfer)(struct mipi_dsi_host *host, struct mipi_dsi_msg *msg); + int (*te_handler)(struct mipi_dsi_host *host); };
/**
To support command mode interface, the DSI host calls this handler to notify the panel tearing effect synchronization signal to the CRTC device manager to trigger to transfer video image.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 956e5f3..4a918ec 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -23,6 +23,7 @@ #include <video/mipi_display.h> #include <video/videomode.h>
+#include "exynos_drm_crtc.h" #include "exynos_drm_drv.h"
/* returns true iff both arguments logically differs */ @@ -1032,10 +1033,22 @@ static ssize_t exynos_dsi_host_transfer(struct mipi_dsi_host *host, return (ret < 0) ? ret : xfer.rx_done; }
+static int exynos_dsi_host_te_handler(struct mipi_dsi_host *host) +{ + struct exynos_dsi *dsi = host_to_dsi(host); + struct drm_encoder *encoder = dsi->encoder; + + if (!(dsi->state & DSIM_STATE_ENABLED)) + return -EPERM; + + return exynos_drm_crtc_te_handler(encoder->crtc); +} + static const struct mipi_dsi_host_ops exynos_dsi_ops = { .attach = exynos_dsi_host_attach, .detach = exynos_dsi_host_detach, .transfer = exynos_dsi_host_transfer, + .te_handler = exynos_dsi_host_te_handler, };
static int exynos_dsi_poweron(struct exynos_dsi *dsi)
To support MIPI DSI command mode interface, FIMD should do followings: - Sets LCD block configuration for I80 interface. - Uses "lcd_sys" as an IRQ resource and sets relevant IRQ configuration. - Implements trigger feature which transfers image date if there is page flip request, and implements TE handler to call trigger function. - Sets command mode timings configuration. - Sets ideal(pixel) clock is 2 times faster than the original one to generate frame done IRQ prior to the next TE signal.
Changelog v2: - Uses macros for TRIGCON register for readability (commented by Sachin) Changelog v3: - Removes using mode->private routine
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/gpu/drm/exynos/Kconfig | 1 + drivers/gpu/drm/exynos/exynos_drm_fimd.c | 277 +++++++++++++++++++++++++----- include/video/samsung_fimd.h | 3 +- 3 files changed, 237 insertions(+), 44 deletions(-)
diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 5bf5bca..f4d34f0 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -28,6 +28,7 @@ config DRM_EXYNOS_FIMD bool "Exynos DRM FIMD" depends on DRM_EXYNOS && !FB_S3C && !ARCH_MULTIPLATFORM select FB_MODE_HELPERS + select MFD_SYSCON help Choose this option if you want to use Exynos FIMD for DRM.
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 40fd6cc..50098e5 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -19,11 +19,14 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/pm_runtime.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h>
#include <video/of_display_timing.h> #include <video/of_videomode.h> #include <video/samsung_fimd.h> #include <drm/exynos_drm.h> +#include <drm/drm_panel.h>
#include "exynos_drm_drv.h" #include "exynos_drm_fbdev.h" @@ -59,6 +62,24 @@ /* color key value register for hardware window 1 ~ 4. */ #define WKEYCON1_BASE(x) ((WKEYCON1 + 0x140) + ((x - 1) * 8))
+/* i80 / RGB trigger control register */ +#define TRIGCON 0x1A4 +#define TRGMODE_I80_RGB_ENABLE_I80 (1 << 0) +#define SWTRGCMD_I80_RGB_ENABLE (1 << 1) + +/* display mode change control register except exynos4 */ +#define VIDOUT_CON 0x000 +#define VIDOUT_CON_F_I80_LDI0 (0x2 << 8) + +/* i80 interface control for main LDI register */ +#define I80IFCONFAx(x) (0x1B0 + (x) * 4) +#define I80IFCONFBx(x) (0x1B8 + (x) * 4) +#define LCD_CS_SETUP(x) ((x) << 16) +#define LCD_WR_SETUP(x) ((x) << 12) +#define LCD_WR_ACT(x) ((x) << 8) +#define LCD_WR_HOLD(x) ((x) << 4) +#define I80IFEN_ENABLE (1 << 0) + /* FIMD has totally five hardware windows. */ #define WINDOWS_NR 5
@@ -66,10 +87,14 @@
struct fimd_driver_data { unsigned int timing_base; + unsigned int lcdblk_off; + unsigned int lcdblk_vt_shift; + unsigned int lcdblk_bypass_shift;
unsigned int has_shadowcon:1; unsigned int has_clksel:1; unsigned int has_limited_fmt:1; + unsigned int has_vidoutcon:1; };
static struct fimd_driver_data s3c64xx_fimd_driver_data = { @@ -80,12 +105,19 @@ static struct fimd_driver_data s3c64xx_fimd_driver_data = {
static struct fimd_driver_data exynos4_fimd_driver_data = { .timing_base = 0x0, + .lcdblk_off = 0x210, + .lcdblk_vt_shift = 10, + .lcdblk_bypass_shift = 1, .has_shadowcon = 1, };
static struct fimd_driver_data exynos5_fimd_driver_data = { .timing_base = 0x20000, + .lcdblk_off = 0x214, + .lcdblk_vt_shift = 24, + .lcdblk_bypass_shift = 15, .has_shadowcon = 1, + .has_vidoutcon = 1, };
struct fimd_win_data { @@ -110,15 +142,23 @@ struct fimd_context { struct clk *bus_clk; struct clk *lcd_clk; void __iomem *regs; + struct regmap *sysreg; struct drm_display_mode mode; struct fimd_win_data win_data[WINDOWS_NR]; unsigned int default_win; unsigned long irq_flags; + u32 vidcon0; u32 vidcon1; + u32 vidout_con; + u32 i80ifcon; + bool i80_if; bool suspended; int pipe; wait_queue_head_t wait_vsync_queue; atomic_t wait_vsync_event; + atomic_t win_updated; + atomic_t triggering; + spinlock_t win_updated_lock;
struct exynos_drm_panel_info panel; struct fimd_driver_data *driver_data; @@ -190,6 +230,14 @@ static u32 fimd_calc_clkdiv(struct fimd_context *ctx, unsigned long ideal_clk = mode->htotal * mode->vtotal * mode->vrefresh; u32 clkdiv;
+ if (ctx->i80_if) { + /* + * The frame done interrupt should be occurred prior to the + * next TE signal. + */ + ideal_clk *= 2; + } + /* Find the clock divider value that gets us closest to ideal_clk */ clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->lcd_clk), ideal_clk);
@@ -212,17 +260,23 @@ static void fimd_mode_set(struct exynos_drm_manager *mgr, struct fimd_context *ctx = mgr->ctx;
drm_mode_copy(&ctx->mode, in_mode); + + if (ctx->i80_if) { + ctx->i80ifcon = LCD_CS_SETUP(in_mode->cs_setup); + ctx->i80ifcon |= LCD_WR_SETUP(in_mode->wr_setup); + ctx->i80ifcon |= LCD_WR_ACT(in_mode->wr_active); + ctx->i80ifcon |= LCD_WR_HOLD(in_mode->wr_hold); + } }
static void fimd_commit(struct exynos_drm_manager *mgr) { struct fimd_context *ctx = mgr->ctx; struct drm_display_mode *mode = &ctx->mode; - struct fimd_driver_data *driver_data; - u32 val, clkdiv, vidcon1; - int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd; + struct fimd_driver_data *driver_data = ctx->driver_data; + void *timing_base = ctx->regs + driver_data->timing_base; + u32 val, clkdiv;
- driver_data = ctx->driver_data; if (ctx->suspended) return;
@@ -230,33 +284,65 @@ static void fimd_commit(struct exynos_drm_manager *mgr) if (mode->htotal == 0 || mode->vtotal == 0) return;
- /* setup polarity values */ - vidcon1 = ctx->vidcon1; - if (mode->flags & DRM_MODE_FLAG_NVSYNC) - vidcon1 |= VIDCON1_INV_VSYNC; - if (mode->flags & DRM_MODE_FLAG_NHSYNC) - vidcon1 |= VIDCON1_INV_HSYNC; - writel(vidcon1, ctx->regs + driver_data->timing_base + VIDCON1); - - /* setup vertical timing values. */ - vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; - vbpd = mode->crtc_vtotal - mode->crtc_vsync_end; - vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay; - - val = VIDTCON0_VBPD(vbpd - 1) | - VIDTCON0_VFPD(vfpd - 1) | - VIDTCON0_VSPW(vsync_len - 1); - writel(val, ctx->regs + driver_data->timing_base + VIDTCON0); - - /* setup horizontal timing values. */ - hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; - hbpd = mode->crtc_htotal - mode->crtc_hsync_end; - hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay; - - val = VIDTCON1_HBPD(hbpd - 1) | - VIDTCON1_HFPD(hfpd - 1) | - VIDTCON1_HSPW(hsync_len - 1); - writel(val, ctx->regs + driver_data->timing_base + VIDTCON1); + if (ctx->i80_if) { + val = ctx->i80ifcon | I80IFEN_ENABLE; + writel(val, timing_base + I80IFCONFAx(0)); + + /* disable auto frame rate */ + writel(0, timing_base + I80IFCONFBx(0)); + + if (ctx->vidout_con) + writel(ctx->vidout_con, timing_base + VIDOUT_CON); + + /* set video type selection to i80 interface */ + if (ctx->sysreg && regmap_update_bits(ctx->sysreg, + driver_data->lcdblk_off, + 0x3 << driver_data->lcdblk_vt_shift, + 0x1 << driver_data->lcdblk_vt_shift)) { + DRM_ERROR("Failed to update sysreg for i80 i/f.\n"); + return; + } + } else { + int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd; + u32 vidcon1; + + /* setup polarity values */ + vidcon1 = ctx->vidcon1; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + vidcon1 |= VIDCON1_INV_VSYNC; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + vidcon1 |= VIDCON1_INV_HSYNC; + writel(vidcon1, ctx->regs + driver_data->timing_base + VIDCON1); + + /* setup vertical timing values. */ + vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; + vbpd = mode->crtc_vtotal - mode->crtc_vsync_end; + vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay; + + val = VIDTCON0_VBPD(vbpd - 1) | + VIDTCON0_VFPD(vfpd - 1) | + VIDTCON0_VSPW(vsync_len - 1); + writel(val, ctx->regs + driver_data->timing_base + VIDTCON0); + + /* setup horizontal timing values. */ + hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + hbpd = mode->crtc_htotal - mode->crtc_hsync_end; + hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay; + + val = VIDTCON1_HBPD(hbpd - 1) | + VIDTCON1_HFPD(hfpd - 1) | + VIDTCON1_HSPW(hsync_len - 1); + writel(val, ctx->regs + driver_data->timing_base + VIDTCON1); + } + + /* set bypass selection */ + if (ctx->sysreg && regmap_update_bits(ctx->sysreg, + driver_data->lcdblk_off, + 0x1 << driver_data->lcdblk_bypass_shift, + 0x1 << driver_data->lcdblk_bypass_shift)) { + DRM_ERROR("Failed to update sysreg for bypass setting.\n"); + return; + }
/* setup horizontal and vertical display size. */ val = VIDTCON2_LINEVAL(mode->vdisplay - 1) | @@ -613,6 +699,14 @@ static void fimd_win_commit(struct exynos_drm_manager *mgr, int zpos) }
win_data->enabled = true; + + if (ctx->i80_if) { + unsigned long flags; + + spin_lock_irqsave(&ctx->win_updated_lock, flags); + atomic_set(&ctx->win_updated, 1); + spin_unlock_irqrestore(&ctx->win_updated_lock, flags); + } }
static void fimd_win_disable(struct exynos_drm_manager *mgr, int zpos) @@ -802,6 +896,68 @@ static void fimd_dpms(struct exynos_drm_manager *mgr, int mode) } }
+static void fimd_trigger(struct device *dev) +{ + struct exynos_drm_manager *mgr = get_fimd_manager(dev); + struct fimd_context *ctx = mgr->ctx; + struct fimd_driver_data *driver_data = ctx->driver_data; + void *timing_base = ctx->regs + driver_data->timing_base; + u32 reg; + + atomic_set(&ctx->triggering, 1); + + reg = readl(ctx->regs + VIDINTCON0); + reg |= (VIDINTCON0_INT_ENABLE | VIDINTCON0_INT_I80IFDONE | + VIDINTCON0_INT_SYSMAINCON); + writel(reg, ctx->regs + VIDINTCON0); + + reg = readl(timing_base + TRIGCON); + reg |= (TRGMODE_I80_RGB_ENABLE_I80 | SWTRGCMD_I80_RGB_ENABLE); + writel(reg, timing_base + TRIGCON); +} + +static int fimd_te_handler(struct exynos_drm_manager *mgr) +{ + struct fimd_context *ctx = mgr->ctx; + unsigned long flags; + + /* check the crtc is detached already from encoder */ + if (ctx->pipe < 0 || !ctx->drm_dev) + return -EINVAL; + + /* + * Skips to trigger if in triggering state, because multiple triggering + * requests can cause panel reset. + */ + if (atomic_read(&ctx->triggering)) + return 0; + + spin_lock_irqsave(&ctx->win_updated_lock, flags); + + /* + * If there is a page flip request, triggers and handles the page flip + * event so that current fb can be updated into panel GRAM. + */ + if (atomic_read(&ctx->win_updated)) { + atomic_set(&ctx->win_updated, 0); + + fimd_trigger(ctx->dev); + } + + spin_unlock_irqrestore(&ctx->win_updated_lock, flags); + + /* wake up vsync event queue */ + if (atomic_read(&ctx->wait_vsync_event)) { + atomic_set(&ctx->wait_vsync_event, 0); + wake_up(&ctx->wait_vsync_queue); + + if (!atomic_read(&ctx->triggering)) + drm_handle_vblank(ctx->drm_dev, ctx->pipe); + } + + return 0; +} + static struct exynos_drm_manager_ops fimd_manager_ops = { .initialize = fimd_mgr_initialize, .remove = fimd_mgr_remove, @@ -815,6 +971,7 @@ static struct exynos_drm_manager_ops fimd_manager_ops = { .win_mode_set = fimd_win_mode_set, .win_commit = fimd_win_commit, .win_disable = fimd_win_disable, + .te_handler = fimd_te_handler, };
static struct exynos_drm_manager fimd_manager = { @@ -825,26 +982,40 @@ static struct exynos_drm_manager fimd_manager = { static irqreturn_t fimd_irq_handler(int irq, void *dev_id) { struct fimd_context *ctx = (struct fimd_context *)dev_id; - u32 val; + u32 val, clear_bit;
val = readl(ctx->regs + VIDINTCON1);
- if (val & VIDINTCON1_INT_FRAME) - /* VSYNC interrupt */ - writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1); + clear_bit = ctx->i80_if ? VIDINTCON1_INT_I80 : VIDINTCON1_INT_FRAME; + if (val & clear_bit) + writel(clear_bit, ctx->regs + VIDINTCON1);
/* check the crtc is detached already from encoder */ if (ctx->pipe < 0 || !ctx->drm_dev) goto out;
- drm_handle_vblank(ctx->drm_dev, ctx->pipe); - exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); + if (ctx->i80_if) { + /* unset i80 frame done interrupt */ + val = readl(ctx->regs + VIDINTCON0); + val &= ~(VIDINTCON0_INT_I80IFDONE | VIDINTCON0_INT_SYSMAINCON); + writel(val, ctx->regs + VIDINTCON0);
- /* set wait vsync event to zero and wake up queue. */ - if (atomic_read(&ctx->wait_vsync_event)) { - atomic_set(&ctx->wait_vsync_event, 0); - wake_up(&ctx->wait_vsync_queue); + /* exit triggering mode */ + atomic_set(&ctx->triggering, 0); + + drm_handle_vblank(ctx->drm_dev, ctx->pipe); + exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); + } else { + drm_handle_vblank(ctx->drm_dev, ctx->pipe); + exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); + + /* set wait vsync event to zero and wake up queue. */ + if (atomic_read(&ctx->wait_vsync_event)) { + atomic_set(&ctx->wait_vsync_event, 0); + wake_up(&ctx->wait_vsync_queue); + } } + out: return IRQ_HANDLED; } @@ -866,12 +1037,32 @@ static int fimd_probe(struct platform_device *pdev)
ctx->dev = dev; ctx->suspended = true; + ctx->driver_data = drm_fimd_get_driver_data(pdev);
if (of_property_read_bool(dev->of_node, "samsung,invert-vden")) ctx->vidcon1 |= VIDCON1_INV_VDEN; if (of_property_read_bool(dev->of_node, "samsung,invert-vclk")) ctx->vidcon1 |= VIDCON1_INV_VCLK;
+ if (of_property_read_bool(dev->of_node, "vidout-i80-ldi")) { + ctx->i80_if = true; + + if (ctx->driver_data->has_vidoutcon) + ctx->vidout_con |= VIDOUT_CON_F_I80_LDI0; + else + ctx->vidcon0 |= VIDCON0_VIDOUT_I80_LDI0; + ctx->vidcon0 |= VIDCON0_DSI_EN; + + spin_lock_init(&ctx->win_updated_lock); + } + + ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,sysreg"); + if (IS_ERR(ctx->sysreg)) { + dev_warn(dev, "failed to get system register.\n"); + ctx->sysreg = NULL; + } + ctx->bus_clk = devm_clk_get(dev, "fimd"); if (IS_ERR(ctx->bus_clk)) { dev_err(dev, "failed to get bus clock\n"); @@ -890,7 +1081,8 @@ static int fimd_probe(struct platform_device *pdev) if (IS_ERR(ctx->regs)) return PTR_ERR(ctx->regs);
- res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "vsync"); + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + ctx->i80_if ? "lcd_sys" : "vsync"); if (!res) { dev_err(dev, "irq request failed.\n"); return -ENXIO; @@ -903,7 +1095,6 @@ static int fimd_probe(struct platform_device *pdev) return ret; }
- ctx->driver_data = drm_fimd_get_driver_data(pdev); init_waitqueue_head(&ctx->wait_vsync_queue); atomic_set(&ctx->wait_vsync_event, 0);
diff --git a/include/video/samsung_fimd.h b/include/video/samsung_fimd.h index b039320..eaad58b 100644 --- a/include/video/samsung_fimd.h +++ b/include/video/samsung_fimd.h @@ -19,6 +19,7 @@ /* VIDCON0 */
#define VIDCON0 0x00 +#define VIDCON0_DSI_EN (1 << 30) #define VIDCON0_INTERLACE (1 << 29) #define VIDCON0_VIDOUT_MASK (0x7 << 26) #define VIDCON0_VIDOUT_SHIFT 26 @@ -355,7 +356,7 @@ #define VIDINTCON0_INT_ENABLE (1 << 0)
#define VIDINTCON1 0x134 -#define VIDINTCON1_INT_I180 (1 << 2) +#define VIDINTCON1_INT_I80 (1 << 2) #define VIDINTCON1_INT_FRAME (1 << 1) #define VIDINTCON1_INT_FIFO (1 << 0)
This patch adds relevant to exynos5420 compatible for exynos5420 SoC support.
Changelog v2: - Changes title, description and fixes typo (commented by Sachin Kamat)
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- .../devicetree/bindings/video/exynos_dsim.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/video/exynos_dsim.txt b/Documentation/devicetree/bindings/video/exynos_dsim.txt index 33b5730..29bf3b2 100644 --- a/Documentation/devicetree/bindings/video/exynos_dsim.txt +++ b/Documentation/devicetree/bindings/video/exynos_dsim.txt @@ -1,7 +1,9 @@ Exynos MIPI DSI Master
Required properties: - - compatible: "samsung,exynos4210-mipi-dsi" + - compatible: value should be one of the following + "samsung,exynos4210-mipi-dsi" /* for Exynos4 SoCs */ + "samsung,exynos5420-mipi-dsi" /* for Exynos5420 SoCs */ - reg: physical base address and length of the registers set for the device - interrupts: should contain DSI interrupt - clocks: list of clock specifiers, must contain an entry for each required
The offset of register DSIM_PLLTMR_REG in Exynos5420 is different from the one in Exynos4 SoC.
In case of Exynos5420 SoC, there is no frequency band bit in DSIM_PLLCTRL_REG, and it uses DSIM_PHYCTRL_REG and DSIM_PHYTIMING*_REG instead. So this patch adds driver data to distinguish it.
Changelog v2: - Moves exynos_dsi_enable_clocks() after exynos_dsi_reset() (commented by Andrzej Hajda) - Splits D-PHY control setting routines from PLL setting one (commented by Andrzej Hajda) Changelog v3: - Uses macro for DSIM_PHYCTRL register (commented by Sachin)
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 157 ++++++++++++++++++++++++++----- 1 file changed, 135 insertions(+), 22 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 4a918ec..50eb8ac 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -17,6 +17,7 @@
#include <linux/clk.h> #include <linux/irq.h> +#include <linux/of_device.h> #include <linux/phy/phy.h> #include <linux/regulator/consumer.h>
@@ -54,9 +55,12 @@
/* FIFO memory AC characteristic register */ #define DSIM_PLLCTRL_REG 0x4c /* PLL control register */ -#define DSIM_PLLTMR_REG 0x50 /* PLL timer register */ #define DSIM_PHYACCHR_REG 0x54 /* D-PHY AC characteristic register */ #define DSIM_PHYACCHR1_REG 0x58 /* D-PHY AC characteristic register1 */ +#define DSIM_PHYCTRL_REG 0x5c +#define DSIM_PHYTIMING_REG 0x64 +#define DSIM_PHYTIMING1_REG 0x68 +#define DSIM_PHYTIMING2_REG 0x6c
/* DSIM_STATUS */ #define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) @@ -200,6 +204,24 @@ #define DSIM_PLL_M(x) ((x) << 4) #define DSIM_PLL_S(x) ((x) << 1)
+/* DSIM_PHYCTRL */ +#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) + +/* DSIM_PHYTIMING */ +#define DSIM_PHYTIMING_LPX(x) ((x) << 8) +#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0) + +/* DSIM_PHYTIMING1 */ +#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) +#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) +#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) +#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0) + +/* DSIM_PHYTIMING2 */ +#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) +#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) +#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0) + #define DSI_MAX_BUS_WIDTH 4 #define DSI_NUM_VIRTUAL_CHANNELS 4 #define DSI_TX_FIFO_SIZE 2048 @@ -233,6 +255,12 @@ struct exynos_dsi_transfer { #define DSIM_STATE_INITIALIZED BIT(1) #define DSIM_STATE_CMD_LPM BIT(2)
+struct exynos_dsi_driver_data { + unsigned int plltmr_reg; + + unsigned int has_freqband:1; +}; + struct exynos_dsi { struct mipi_dsi_host dsi_host; struct drm_connector connector; @@ -262,11 +290,39 @@ struct exynos_dsi {
spinlock_t transfer_lock; /* protects transfer_list */ struct list_head transfer_list; + + struct exynos_dsi_driver_data *driver_data; };
#define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) #define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector)
+static struct exynos_dsi_driver_data exynos4_dsi_driver_data = { + .plltmr_reg = 0x50, + .has_freqband = 1, +}; + +static struct exynos_dsi_driver_data exynos5_dsi_driver_data = { + .plltmr_reg = 0x58, +}; + +static struct of_device_id exynos_dsi_of_match[] = { + { .compatible = "samsung,exynos4210-mipi-dsi", + .data = &exynos4_dsi_driver_data }, + { .compatible = "samsung,exynos5420-mipi-dsi", + .data = &exynos5_dsi_driver_data }, + { } +}; + +static inline struct exynos_dsi_driver_data *exynos_dsi_get_driver_data( + struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(exynos_dsi_of_match, &pdev->dev); + + return (struct exynos_dsi_driver_data *)of_id->data; +} + static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) { if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300))) @@ -340,14 +396,9 @@ static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi, static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi, unsigned long freq) { - static const unsigned long freq_bands[] = { - 100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ, - 270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ, - 510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ, - 770 * MHZ, 870 * MHZ, 950 * MHZ, - }; + struct exynos_dsi_driver_data *driver_data = dsi->driver_data; unsigned long fin, fout; - int timeout, band; + int timeout; u8 p, s; u16 m; u32 reg; @@ -368,18 +419,30 @@ static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi, "failed to find PLL PMS for requested frequency\n"); return -EFAULT; } + dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s);
- for (band = 0; band < ARRAY_SIZE(freq_bands); ++band) - if (fout < freq_bands[band]) - break; + writel(500, dsi->reg_base + driver_data->plltmr_reg); + + reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s); + + if (driver_data->has_freqband) { + static const unsigned long freq_bands[] = { + 100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ, + 270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ, + 510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ, + 770 * MHZ, 870 * MHZ, 950 * MHZ, + }; + int band; + + for (band = 0; band < ARRAY_SIZE(freq_bands); ++band) + if (fout < freq_bands[band]) + break;
- dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d), band %d\n", fout, - p, m, s, band); + dev_dbg(dsi->dev, "band %d\n", band);
- writel(500, dsi->reg_base + DSIM_PLLTMR_REG); + reg |= DSIM_FREQ_BAND(band); + }
- reg = DSIM_FREQ_BAND(band) | DSIM_PLL_EN - | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s); writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG);
timeout = 1000; @@ -433,6 +496,59 @@ static int exynos_dsi_enable_clock(struct exynos_dsi *dsi) return 0; }
+static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) +{ + struct exynos_dsi_driver_data *driver_data = dsi->driver_data; + u32 reg; + + if (driver_data->has_freqband) + return; + + /* B D-PHY: D-PHY Master & Slave Analog Block control */ + reg = DSIM_PHYCTRL_ULPS_EXIT(0x0af); + writel(reg, dsi->reg_base + DSIM_PHYCTRL_REG); + + /* + * T LPX: Transmitted length of any Low-Power state period + * T HS-EXIT: Time that the transmitter drives LP-11 following a HS + * burst + */ + reg = DSIM_PHYTIMING_LPX(0x06) | DSIM_PHYTIMING_HS_EXIT(0x0b); + writel(reg, dsi->reg_base + DSIM_PHYTIMING_REG); + + /* + * T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00 + * Line state immediately before the HS-0 Line state starting the + * HS transmission + * T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to + * transmitting the Clock. + * T CLK_POST: Time that the transmitter continues to send HS clock + * after the last associated Data Lane has transitioned to LP Mode + * Interval is defined as the period from the end of T HS-TRAIL to + * the beginning of T CLK-TRAIL + * T CLK-TRAIL: Time that the transmitter drives the HS-0 state after + * the last payload clock bit of a HS transmission burst + */ + reg = DSIM_PHYTIMING1_CLK_PREPARE(0x07) | + DSIM_PHYTIMING1_CLK_ZERO(0x27) | + DSIM_PHYTIMING1_CLK_POST(0x0d) | + DSIM_PHYTIMING1_CLK_TRAIL(0x08); + writel(reg, dsi->reg_base + DSIM_PHYTIMING1_REG); + + /* + * T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00 + * Line state immediately before the HS-0 Line state starting the + * HS transmission + * T HS-ZERO: Time that the transmitter drives the HS-0 state prior to + * transmitting the Sync sequence. + * T HS-TRAIL: Time that the transmitter drives the flipped differential + * state after last payload data bit of a HS transmission burst + */ + reg = DSIM_PHYTIMING2_HS_PREPARE(0x09) | DSIM_PHYTIMING2_HS_ZERO(0x0d) | + DSIM_PHYTIMING2_HS_TRAIL(0x0b); + writel(reg, dsi->reg_base + DSIM_PHYTIMING2_REG); +} + static void exynos_dsi_disable_clock(struct exynos_dsi *dsi) { u32 reg; @@ -947,10 +1063,11 @@ static irqreturn_t exynos_dsi_irq(int irq, void *dev_id)
static int exynos_dsi_init(struct exynos_dsi *dsi) { - exynos_dsi_enable_clock(dsi); exynos_dsi_reset(dsi); enable_irq(dsi->irq); + exynos_dsi_enable_clock(dsi); exynos_dsi_wait_for_reset(dsi); + exynos_dsi_set_phy_ctrl(dsi); exynos_dsi_init_link(dsi);
return 0; @@ -1412,6 +1529,7 @@ static int exynos_dsi_probe(struct platform_device *pdev) dsi->dsi_host.dev = &pdev->dev;
dsi->dev = &pdev->dev; + dsi->driver_data = exynos_dsi_get_driver_data(pdev);
ret = exynos_dsi_parse_dt(dsi); if (ret) @@ -1516,11 +1634,6 @@ static const struct dev_pm_ops exynos_dsi_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume) };
-static struct of_device_id exynos_dsi_of_match[] = { - { .compatible = "samsung,exynos4210-mipi-dsi" }, - { } -}; - struct platform_driver dsi_driver = { .probe = exynos_dsi_probe, .remove = exynos_dsi_remove,
This patch adds DT bindings for s6e3fa0 panel. The bindings describes panel resources, display timings and cpu mode timings.
Changelog v2: - Adds unit address (commented by Sachin Kamat) Changelog v3: - Removes optional delay, size properties (commented by Laurent Pinchart) - Adds OLED detection, TE gpio properties Changelog v4: - Moves CPU timings relevant properties from FIMD DT (commented by Laurent Pinchart, Andrzej Hajda) Changelog v5: - Fixes gpio property names (commented by Andrzej Hajda) Changelog v6: - Renames CPU timings to CPU mode timings - Modifies CPU mode timings internal properties relevant things (commented by Laurent Pinchart, Andrzej Hajda) Changelog v7: - Uses cmdmode-display-timings only
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- .../devicetree/bindings/panel/samsung,s6e3fa0.txt | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/samsung,s6e3fa0.txt
diff --git a/Documentation/devicetree/bindings/panel/samsung,s6e3fa0.txt b/Documentation/devicetree/bindings/panel/samsung,s6e3fa0.txt new file mode 100644 index 0000000..c9a3fbd --- /dev/null +++ b/Documentation/devicetree/bindings/panel/samsung,s6e3fa0.txt @@ -0,0 +1,45 @@ +Samsung S6E3FA0 AMOLED LCD 5.7 inch panel + +Required properties: + - compatible: "samsung,s6e3fa0" + - reg: the virtual channel number of a DSI peripheral + - vdd3-supply: core voltage supply + - vci-supply: voltage supply for analog circuits + - reset-gpios: a GPIO spec for the reset pin + - det-gpios: a GPIO spec for the OLED detection pin + - te-gpios: a GPIO spec for the TE pin + - cmdmode-display-timings: command mode interface timings for the connected + panel as described by [1] + +Optional properties: + +The device node can contain one 'port' child node with one child 'endpoint' +node, according to the bindings defined in [2]. This node should describe +panel's video bus. + +[1]: Documentation/devicetree/bindings/video/cmdmode-display-timing.txt +[2]: Documentation/devicetree/bindings/media/video-interfaces.txt + +Example: + + panel@0 { + compatible = "samsung,s6e3fa0"; + reg = <0>; + vdd3-supply = <&vcclcd_reg>; + vci-supply = <&vlcd_reg>; + reset-gpios = <&gpy7 4 0>; + det-gpios = <&gpg0 6 0>; + te-gpios = <&gpd1 7 0>; + + cmdmode-display-timings { + timing-0 { + clock-frequency = <0>; + hactive = <1080>; + vactive = <1920>; + cs-setup = <0>; + wr-setup = <0>; + wr-active = <1>; + wr-hold = <0>; + }; + }; + };
This patch adds MIPI-DSI command mode based S6E3FA0 AMOLED LCD Panel driver.
Changelog v2: - Declares delay, size properties in probe routine instead of DT Changelog v3: - Moves CPU mode timings relevant properties from FIMD DT (commented by Laurent Pinchart, Andrzej Hajda) Changelog v4: - Enhances readability, vddm readi failure case and removes duplicated power off (commented by Andrzej Hajda) Changelog v5: - Uses command mode helper functions instead of videomode one
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- drivers/gpu/drm/panel/Kconfig | 7 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-s6e3fa0.c | 570 +++++++++++++++++++++++++++++++++ 3 files changed, 578 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-s6e3fa0.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 4ec874d..fa51237 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -30,4 +30,11 @@ config DRM_PANEL_S6E8AA0 select DRM_MIPI_DSI select VIDEOMODE_HELPERS
+config DRM_PANEL_S6E3FA0 + tristate "S6E3FA0 DSI command mode panel" + depends on DRM && DRM_PANEL + depends on OF + select DRM_MIPI_DSI + select CMDMODE_HELPERS + endmenu diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 8b92921..85c6738 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o obj-$(CONFIG_DRM_PANEL_LD9040) += panel-ld9040.o obj-$(CONFIG_DRM_PANEL_S6E8AA0) += panel-s6e8aa0.o +obj-$(CONFIG_DRM_PANEL_S6E3FA0) += panel-s6e3fa0.o diff --git a/drivers/gpu/drm/panel/panel-s6e3fa0.c b/drivers/gpu/drm/panel/panel-s6e3fa0.c new file mode 100644 index 0000000..a3b2d7e --- /dev/null +++ b/drivers/gpu/drm/panel/panel-s6e3fa0.c @@ -0,0 +1,570 @@ +/* + * MIPI-DSI based s6e3fa0 AMOLED LCD 5.7 inch panel driver. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * + * YoungJun Cho yj44.cho@samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <drm/drmP.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> +#include <video/of_cmdmode.h> +#include <video/cmdmode.h> + +/* Manufacturer Command Set */ +#define MCS_GLOBAL_PARAMETER 0xb0 +#define MCS_AID 0xb2 +#define MCS_ELVSSOPT 0xb6 +#define MCS_TEMPERATURE_SET 0xb8 +#define MCS_PENTILE_CTRL 0xc0 +#define MCS_GAMMA_MODE 0xca +#define MCS_VDDM 0xd7 +#define MCS_ALS 0xe3 +#define MCS_ERR_FG 0xed +#define MCS_KEY_LEV1 0xf0 +#define MCS_PANEL_UPDATE 0xf7 +#define MCS_KEY_LEV2 0xfc +#define MCS_RE 0xfe +#define MCS_TOUT2_HSYNC 0xff + +/* Content Adaptive Brightness Control */ +#define DCS_WRITE_CABC 0x55 + +#define MTP_ID_LEN 3 +#define GAMMA_LEVEL_NUM 30 + +#define DEFAULT_VDDM_VAL 0x15 + +struct s6e3fa0 { + struct device *dev; + struct drm_panel panel; + + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset_gpio; + struct gpio_desc *det_gpio; + struct gpio_desc *te_gpio; + struct cmdmode cm; + + unsigned int power_on_delay; + unsigned int reset_delay; + unsigned int init_delay; + unsigned int width_mm; + unsigned int height_mm; + + unsigned char id; + unsigned char vddm; + unsigned int brightness; +}; + +#define panel_to_s6e3fa0(p) container_of(p, struct s6e3fa0, panel) + +/* VDD Memory Lookup Table contains paires of {ReadValue, WriteValue} */ +static const unsigned char s6e3fa0_vddm_lut[][2] = { + {0x00, 0x0d}, {0x01, 0x0d}, {0x02, 0x0e}, {0x03, 0x0f}, {0x04, 0x10}, + {0x05, 0x11}, {0x06, 0x12}, {0x07, 0x13}, {0x08, 0x14}, {0x09, 0x15}, + {0x0a, 0x16}, {0x0b, 0x17}, {0x0c, 0x18}, {0x0d, 0x19}, {0x0e, 0x1a}, + {0x0f, 0x1b}, {0x10, 0x1c}, {0x11, 0x1d}, {0x12, 0x1e}, {0x13, 0x1f}, + {0x14, 0x20}, {0x15, 0x21}, {0x16, 0x22}, {0x17, 0x23}, {0x18, 0x24}, + {0x19, 0x25}, {0x1a, 0x26}, {0x1b, 0x27}, {0x1c, 0x28}, {0x1d, 0x29}, + {0x1e, 0x2a}, {0x1f, 0x2b}, {0x20, 0x2c}, {0x21, 0x2d}, {0x22, 0x2e}, + {0x23, 0x2f}, {0x24, 0x30}, {0x25, 0x31}, {0x26, 0x32}, {0x27, 0x33}, + {0x28, 0x34}, {0x29, 0x35}, {0x2a, 0x36}, {0x2b, 0x37}, {0x2c, 0x38}, + {0x2d, 0x39}, {0x2e, 0x3a}, {0x2f, 0x3b}, {0x30, 0x3c}, {0x31, 0x3d}, + {0x32, 0x3e}, {0x33, 0x3f}, {0x34, 0x3f}, {0x35, 0x3f}, {0x36, 0x3f}, + {0x37, 0x3f}, {0x38, 0x3f}, {0x39, 0x3f}, {0x3a, 0x3f}, {0x3b, 0x3f}, + {0x3c, 0x3f}, {0x3d, 0x3f}, {0x3e, 0x3f}, {0x3f, 0x3f}, {0x40, 0x0c}, + {0x41, 0x0b}, {0x42, 0x0a}, {0x43, 0x09}, {0x44, 0x08}, {0x45, 0x07}, + {0x46, 0x06}, {0x47, 0x05}, {0x48, 0x04}, {0x49, 0x03}, {0x4a, 0x02}, + {0x4b, 0x01}, {0x4c, 0x40}, {0x4d, 0x41}, {0x4e, 0x42}, {0x4f, 0x43}, + {0x50, 0x44}, {0x51, 0x45}, {0x52, 0x46}, {0x53, 0x47}, {0x54, 0x48}, + {0x55, 0x49}, {0x56, 0x4a}, {0x57, 0x4b}, {0x58, 0x4c}, {0x59, 0x4d}, + {0x5a, 0x4e}, {0x5b, 0x4f}, {0x5c, 0x50}, {0x5d, 0x51}, {0x5e, 0x52}, + {0x5f, 0x53}, {0x60, 0x54}, {0x61, 0x55}, {0x62, 0x56}, {0x63, 0x57}, + {0x64, 0x58}, {0x65, 0x59}, {0x66, 0x5a}, {0x67, 0x5b}, {0x68, 0x5c}, + {0x69, 0x5d}, {0x6a, 0x5e}, {0x6b, 0x5f}, {0x6c, 0x60}, {0x6d, 0x61}, + {0x6e, 0x62}, {0x6f, 0x63}, {0x70, 0x64}, {0x71, 0x65}, {0x72, 0x66}, + {0x73, 0x67}, {0x74, 0x68}, {0x75, 0x69}, {0x76, 0x6a}, {0x77, 0x6b}, + {0x78, 0x6c}, {0x79, 0x6d}, {0x7a, 0x6e}, {0x7b, 0x6f}, {0x7c, 0x70}, + {0x7d, 0x71}, {0x7e, 0x72}, {0x7f, 0x73}, +}; + +static int s6e3fa0_dcs_read(struct s6e3fa0 *ctx, unsigned char cmd, + void *data, size_t len) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + + return mipi_dsi_dcs_read(dsi, dsi->channel, cmd, data, len); +} + +static void s6e3fa0_dcs_write(struct s6e3fa0 *ctx, const void *data, size_t len) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + + mipi_dsi_dcs_write(dsi, dsi->channel, data, len); +} + +#define s6e3fa0_dcs_write_seq(ctx, seq...) \ +do { \ + const unsigned char d[] = { seq }; \ + BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "too big seq for stack"); \ + s6e3fa0_dcs_write(ctx, d, ARRAY_SIZE(d)); \ +} while (0) + +#define s6e3fa0_dcs_write_seq_static(ctx, seq...) \ +do { \ + static const unsigned char d[] = { seq }; \ + s6e3fa0_dcs_write(ctx, d, ARRAY_SIZE(d)); \ +} while (0) + +static void s6e3fa0_apply_level_1_key(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_KEY_LEV1, 0x5a, 0x5a); +} + +static void s6e3fa0_apply_level_2_key(struct s6e3fa0 *ctx, bool on) +{ + if (on) + s6e3fa0_dcs_write_seq_static(ctx, MCS_KEY_LEV2, 0x5a, 0x5a); + else + s6e3fa0_dcs_write_seq_static(ctx, MCS_KEY_LEV2, 0xa5, 0xa5); +} + +static void s6e3fa0_set_maximum_return_packet_size(struct s6e3fa0 *ctx, + unsigned int size) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + const struct mipi_dsi_host_ops *ops = dsi->host->ops; + + if (ops && ops->transfer) { + unsigned char buf[] = {size, 0}; + struct mipi_dsi_msg msg = { + .channel = dsi->channel, + .type = MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE, + .tx_len = sizeof(buf), + .tx_buf = buf + }; + + ops->transfer(dsi->host, &msg); + } +} + +static void s6e3fa0_read_mtp_id(struct s6e3fa0 *ctx) +{ + unsigned char id[MTP_ID_LEN]; + int ret; + + s6e3fa0_set_maximum_return_packet_size(ctx, MTP_ID_LEN); + ret = s6e3fa0_dcs_read(ctx, MIPI_DCS_GET_DISPLAY_ID, id, MTP_ID_LEN); + if (ret < MTP_ID_LEN || id[0] == 0x00) { + dev_err(ctx->dev, "failed to read id\n"); + return; + } + + dev_info(ctx->dev, "ID: 0x%02x, 0x%02x, 0x%02x\n", id[0], id[1], id[2]); + + ctx->id = id[2]; +} + +static void s6e3fa0_read_vddm(struct s6e3fa0 *ctx) +{ + unsigned char vddm; + int ret; + + s6e3fa0_apply_level_2_key(ctx, true); + s6e3fa0_dcs_write_seq_static(ctx, MCS_GLOBAL_PARAMETER, 0x16); + s6e3fa0_set_maximum_return_packet_size(ctx, 1); + ret = s6e3fa0_dcs_read(ctx, MCS_VDDM, &vddm, 1); + s6e3fa0_apply_level_2_key(ctx, false); + + if (ret < 1 || vddm > 0x7f) { + dev_err(ctx->dev, "failed to read vddm, use default val.\n"); + vddm = DEFAULT_VDDM_VAL; + } + + ctx->vddm = s6e3fa0_vddm_lut[vddm][1]; +} + +static void s6e3fa0_set_pentile_control(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_PENTILE_CTRL, 0x00, 0x02, 0x03, + 0x32, 0x03, 0x44, 0x44, 0xc0, 0x00, + 0x1c, 0x20, 0xe8); +} + +static void s6e3fa0_write_ambient_light_sensor(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_ALS, 0xff, 0xff, 0xff, 0xff); +} + +static void s6e3fa0_set_readability_enhancement(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_RE, 0x00, 0x03, + MCS_GLOBAL_PARAMETER, 0x2b, + MCS_RE, 0xe4); +} + +static void s6e3fa0_set_common_control(struct s6e3fa0 *ctx) +{ + s6e3fa0_set_pentile_control(ctx); + s6e3fa0_write_ambient_light_sensor(ctx); + s6e3fa0_set_readability_enhancement(ctx); +} + +static void s6e3fa0_set_gamma(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_GAMMA_MODE, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x00, 0x00, 0x00); +} + +static void s6e3fa0_set_amoled_impulse_driving(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_AID, 0x00, 0x06, 0x00, 0x06); +} + +static void s6e3fa0_set_elvss(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_ELVSSOPT, 0x88, 0x0a); +} + +static void s6e3fa0_update_panel(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_PANEL_UPDATE, 0x03); +} + +static void s6e3fa0_write_automatic_current_limit(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, DCS_WRITE_CABC, 0x02); +} + +static void s6e3fa0_set_brightness_control(struct s6e3fa0 *ctx) +{ + s6e3fa0_set_gamma(ctx); + s6e3fa0_set_amoled_impulse_driving(ctx); + s6e3fa0_set_elvss(ctx); + s6e3fa0_update_panel(ctx); + s6e3fa0_write_automatic_current_limit(ctx); +} + +static void s6e3fa0_set_temperature(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MCS_GLOBAL_PARAMETER, 0x05, + MCS_TEMPERATURE_SET, 0x19); +} + +static void s6e3fa0_set_elvss_control(struct s6e3fa0 *ctx) +{ + s6e3fa0_set_temperature(ctx); + s6e3fa0_set_elvss(ctx); +} + +static void s6e3fa0_set_te_on(struct s6e3fa0 *ctx) +{ + s6e3fa0_dcs_write_seq_static(ctx, MIPI_DCS_SET_TEAR_ON, 0x00); +} + +static void s6e3fa0_set_etc_and_write_vddm(struct s6e3fa0 *ctx) +{ + s6e3fa0_apply_level_2_key(ctx, true); + s6e3fa0_dcs_write_seq(ctx, MCS_ERR_FG, 0x0c, 0x04, + MCS_TOUT2_HSYNC, 0x01, + MCS_GLOBAL_PARAMETER, 0x16, + MCS_VDDM, ctx->vddm); + s6e3fa0_apply_level_2_key(ctx, false); +} + +static void s6e3fa0_set_etc_condition(struct s6e3fa0 *ctx) +{ + s6e3fa0_set_te_on(ctx); + s6e3fa0_set_etc_and_write_vddm(ctx); +} + +static void s6e3fa0_panel_init(struct s6e3fa0 *ctx) +{ + s6e3fa0_set_common_control(ctx); + s6e3fa0_set_brightness_control(ctx); + s6e3fa0_set_elvss_control(ctx); + s6e3fa0_set_etc_condition(ctx); + + msleep(ctx->init_delay); +} + +static int s6e3fa0_power_off(struct s6e3fa0 *ctx) +{ + gpiod_set_value(ctx->reset_gpio, 0); + + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); +} + +static int s6e3fa0_power_on(struct s6e3fa0 *ctx) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret) + return ret; + + msleep(ctx->power_on_delay); + + gpiod_set_value(ctx->reset_gpio, 1); + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(1000, 2000); + gpiod_set_value(ctx->reset_gpio, 1); + + msleep(ctx->reset_delay); + + return 0; +} + +static void s6e3fa0_set_sequence(struct s6e3fa0 *ctx) +{ + s6e3fa0_apply_level_1_key(ctx); + s6e3fa0_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(20); + + s6e3fa0_read_mtp_id(ctx); + s6e3fa0_read_vddm(ctx); + + s6e3fa0_panel_init(ctx); + + s6e3fa0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON); +} + +static int s6e3fa0_disable(struct drm_panel *panel) +{ + struct s6e3fa0 *ctx = panel_to_s6e3fa0(panel); + + s6e3fa0_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE); + s6e3fa0_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF); + msleep(35); + + return s6e3fa0_power_off(ctx); +} + +static int s6e3fa0_enable(struct drm_panel *panel) +{ + struct s6e3fa0 *ctx = panel_to_s6e3fa0(panel); + int ret; + + ret = s6e3fa0_power_on(ctx); + if (ret) + return ret; + + s6e3fa0_set_sequence(ctx); + + return ret; +} + +static int s6e3fa0_get_modes(struct drm_panel *panel) +{ + struct drm_connector *connector = panel->connector; + struct s6e3fa0 *ctx = panel_to_s6e3fa0(panel); + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (!mode) { + DRM_ERROR("failed to create a new display mode\n"); + return 0; + } + + drm_display_mode_from_cmdmode(&ctx->cm, mode); + mode->width_mm = ctx->width_mm; + mode->height_mm = ctx->height_mm; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs s6e3fa0_drm_funcs = { + .disable = s6e3fa0_disable, + .enable = s6e3fa0_enable, + .get_modes = s6e3fa0_get_modes, +}; + +static int s6e3fa0_parse_dt(struct s6e3fa0 *ctx) +{ + struct device *dev = ctx->dev; + int ret; + + ret = of_get_cmdmode(dev->of_node, &ctx->cm, 0); + if (ret) { + dev_err(dev, "failed to get command mode: %d\n", ret); + return ret; + } + + return ret; +} + +irqreturn_t s6e3fa0_det_interrupt(int irq, void *dev_id) +{ + /* TODO */ + return IRQ_HANDLED; +} + +irqreturn_t s6e3fa0_te_interrupt(int irq, void *dev_id) +{ + struct s6e3fa0 *ctx = dev_id; + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + struct mipi_dsi_host *host = dsi->host; + const struct mipi_dsi_host_ops *ops = host->ops; + + if (ops && ops->te_handler) + ops->te_handler(host); + + return IRQ_HANDLED; +} + +static int s6e3fa0_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct s6e3fa0 *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(struct s6e3fa0), GFP_KERNEL); + if (!ctx) { + dev_err(dev, "failed to allocate s6e3fa0 structure.\n"); + return -ENOMEM; + } + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + + ret = s6e3fa0_parse_dt(ctx); + if (ret) + return ret; + + ctx->supplies[0].supply = "vdd3"; + ctx->supplies[1].supply = "vci"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret) { + dev_err(dev, "failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset"); + if (IS_ERR(ctx->reset_gpio)) { + dev_err(dev, "failed to get reset gpio: %ld\n", + PTR_ERR(ctx->reset_gpio)); + return PTR_ERR(ctx->reset_gpio); + } + ret = gpiod_direction_output(ctx->reset_gpio, 1); + if (ret < 0) { + dev_err(dev, "failed to configure reset gpio: %d\n", ret); + return ret; + } + + ctx->det_gpio = devm_gpiod_get(dev, "det"); + if (IS_ERR(ctx->det_gpio)) { + dev_err(dev, "failed to get det gpio: %ld\n", + PTR_ERR(ctx->det_gpio)); + return PTR_ERR(ctx->det_gpio); + } + ret = gpiod_direction_input(ctx->det_gpio); + if (ret < 0) { + dev_err(dev, "failed to configure det gpio: %d\n", ret); + return ret; + } + ret = devm_request_irq(dev, gpiod_to_irq(ctx->det_gpio), + s6e3fa0_det_interrupt, + IRQF_TRIGGER_FALLING, + "oled-det", ctx); + if (ret) { + dev_err(dev, "failed to request det irq: %d\n", ret); + return ret; + } + + ctx->te_gpio = devm_gpiod_get(dev, "te"); + if (IS_ERR(ctx->te_gpio)) { + dev_err(dev, "failed to get te gpio: %ld\n", + PTR_ERR(ctx->te_gpio)); + return PTR_ERR(ctx->te_gpio); + } + ret = gpiod_direction_input(ctx->te_gpio); + if (ret < 0) { + dev_err(dev, "failed to configure te gpio: %d\n", ret); + return ret; + } + ret = devm_request_irq(dev, gpiod_to_irq(ctx->te_gpio), + s6e3fa0_te_interrupt, + IRQF_TRIGGER_RISING, + "TE", ctx); + if (ret) { + dev_err(dev, "failed to request det irq: %d\n", ret); + return ret; + } + + ctx->power_on_delay = 10; + ctx->reset_delay = 5; + ctx->init_delay = 120; + ctx->width_mm = 71; + ctx->height_mm = 126; + + ctx->brightness = GAMMA_LEVEL_NUM - 1; + + drm_panel_init(&ctx->panel); + ctx->panel.dev = dev; + ctx->panel.funcs = &s6e3fa0_drm_funcs; + + ret = drm_panel_add(&ctx->panel); + if (ret) + return ret; + + ret = mipi_dsi_attach(dsi); + if (ret) + drm_panel_remove(&ctx->panel); + + return ret; +} + +static int s6e3fa0_remove(struct mipi_dsi_device *dsi) +{ + struct s6e3fa0 *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); + + return 0; +} + +static struct of_device_id s6e3fa0_of_match[] = { + { .compatible = "samsung,s6e3fa0" }, + { } +}; +MODULE_DEVICE_TABLE(of, s6e3fa0_of_match); + +static struct mipi_dsi_driver s6e3fa0_driver = { + .probe = s6e3fa0_probe, + .remove = s6e3fa0_remove, + .driver = { + .name = "panel_s6e3fa0", + .owner = THIS_MODULE, + .of_match_table = s6e3fa0_of_match, + }, +}; +module_mipi_dsi_driver(s6e3fa0_driver); + +MODULE_AUTHOR("YoungJun Cho yj44.cho@samsung.com"); +MODULE_DESCRIPTION("MIPI-DSI based s6e3fa0 AMOLED LCD Panel Driver"); +MODULE_LICENSE("GPL v2");
This patch adds sysreg property to fimd device node which is required to use I80 interface.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- arch/arm/boot/dts/exynos4.dtsi | 1 + 1 file changed, 1 insertion(+)
diff --git a/arch/arm/boot/dts/exynos4.dtsi b/arch/arm/boot/dts/exynos4.dtsi index 3d14cdb..a10aa50 100644 --- a/arch/arm/boot/dts/exynos4.dtsi +++ b/arch/arm/boot/dts/exynos4.dtsi @@ -528,6 +528,7 @@ clocks = <&clock 140>, <&clock 283>; clock-names = "sclk_fimd", "fimd"; samsung,power-domain = <&pd_lcd0>; + samsung,sysreg = <&sys_reg>; status = "disabled"; }; };
This patch adds sysreg device node, and sysreg property to fimd device node which is required to use I80 interface.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- arch/arm/boot/dts/exynos5.dtsi | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5.dtsi b/arch/arm/boot/dts/exynos5.dtsi index 258dca4..f938bbb 100644 --- a/arch/arm/boot/dts/exynos5.dtsi +++ b/arch/arm/boot/dts/exynos5.dtsi @@ -88,12 +88,18 @@ status = "disabled"; };
+ sys_reg: syscon@10050000 { + compatible = "samsung,exynos5-sysreg", "syscon"; + reg = <0x10050000 0x500>; + }; + fimd@14400000 { compatible = "samsung,exynos5250-fimd"; interrupt-parent = <&combiner>; reg = <0x14400000 0x40000>; interrupt-names = "fifo", "vsync", "lcd_sys"; interrupts = <18 4>, <18 5>, <18 6>; + samsung,sysreg = <&sys_reg>; status = "disabled"; };
This patch adds mipi-phy node for MIPI-DSI device.
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- arch/arm/boot/dts/exynos5420.dtsi | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5420.dtsi b/arch/arm/boot/dts/exynos5420.dtsi index 8db792b..f0184c7 100644 --- a/arch/arm/boot/dts/exynos5420.dtsi +++ b/arch/arm/boot/dts/exynos5420.dtsi @@ -416,6 +416,12 @@ phy-names = "dp"; };
+ mipi_phy: video-phy@10040714 { + compatible = "samsung,s5pv210-mipi-video-phy"; + reg = <0x10040714 12>; + #phy-cells = <1>; + }; + fimd@14400000 { samsung,power-domain = <&disp_pd>; clocks = <&clock 147>, <&clock 421>;
This patch adds common part of dsi node.
Changelog v2: - Uses clock macros instead of numbers (commented by Sachin Kamat)
Signed-off-by: YoungJun Cho yj44.cho@samsung.com Acked-by: Inki Dae inki.dae@samsung.com Acked-by: Kyungmin Park kyungmin.park@samsung.com --- arch/arm/boot/dts/exynos5420.dtsi | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5420.dtsi b/arch/arm/boot/dts/exynos5420.dtsi index f0184c7..f1030f5 100644 --- a/arch/arm/boot/dts/exynos5420.dtsi +++ b/arch/arm/boot/dts/exynos5420.dtsi @@ -17,6 +17,7 @@ #include "exynos5420-pinctrl.dtsi"
#include <dt-bindings/clk/exynos-audss-clk.h> +#include <dt-bindings/clock/exynos5420.h>
/ { compatible = "samsung,exynos5420"; @@ -422,6 +423,20 @@ #phy-cells = <1>; };
+ dsi@14500000 { + compatible = "samsung,exynos5420-mipi-dsi"; + reg = <0x14500000 0x10000>; + interrupts = <0 82 0>; + samsung,power-domain = <&disp_pd>; + phys = <&mipi_phy 1>; + phy-names = "dsim"; + clocks = <&clock CLK_DSIM1>, <&clock CLK_SCLK_MIPI1>; + clock-names = "bus_clk", "pll_clk"; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + fimd@14400000 { samsung,power-domain = <&disp_pd>; clocks = <&clock 147>, <&clock 421>;
dri-devel@lists.freedesktop.org