Commit a5a2391e authored by Dave Airlie's avatar Dave Airlie

Merge branch 'exynos-drm-next' of...

Merge branch 'exynos-drm-next' of git://git.kernel.org/pub/scm/linux/kernel/git/daeinki/drm-exynos into drm-next

Highlights
----------

Re-factoring works over the exynos drm framework.
  - drm_crtc, drm_encoder/drm_connector are implemented by sub drivers
    directly.
  - Removing pm interfaces from each sub driver, and implementing them
    at top level of exynos drm.
Add DisplayPort Transmitter driver.
  - Just moving existing driver from drivers/vides/exynos into
    drivers/gpu/drm/exynos.
Add new LVDS bridge driver, PTN3460.
  - Placed in drivers/gpu/drm/bridge, and this device is used to transfer
    image signal from DP(DisplayPort) to LVDS Panel.
    So this driver will be used with DP driver moved into exynos drm.
Add parallel panel support
  - With the re-factoring patch series, existing parallel panel support was
    broken by moving exynos_drm_display ops into each real connector driver,
    DP. So this patch series adds a new parallel panel module,
    exynos_drm_dpi, for supporting parallel panel, and also adds relevant
    bindings.
Some fixups and cleanups.

* 'exynos-drm-next' of git://git.kernel.org/pub/scm/linux/kernel/git/daeinki/drm-exynos: (45 commits)
  drm/exynos: fimd: remove unused variable
  ARM: dts: exynos4210-universal: add exynos/fimd node
  drm/exynos: restore parallel output interface support
  exynos/fimd: add parallel output related bindings
  drm/exynos: correct timing porch conversion
  drm/exynos: init kms poll after creation of connectors
  drm/exynos: delay fbdev initialization until an output is connected
  drm/exynos: fix unnecessary resource cleanup
  drm/exynos: hdmi: use i2c_adapter instead of i2c_client
  drm/exynos: hdmi: consider APB PHY
  drm/exynos: Remove the exynos_drm_connector shim
  drm/exynos: Implement lvds bridge discovery to DP driver
  drm/bridge: Add PTN3460 bridge driver
  drm/exynos: Implement drm_connector directly in vidi driver
  drm/exynos: Implement drm_connector directly in dp driver
  drm/exynos: Implement drm_connector in hdmi directly
  drm/exynos: Add create_connector callback
  drm/exynos: Consolidate suspend/resume in drm_drv
  drm/exynos: Clean up FIMD power on/off routines
  drm/exynos: Implement dpms display callback in DP
  ...
parents c46145ae 1d531062
ptn3460 bridge bindings
Required properties:
- compatible: "nxp,ptn3460"
- reg: i2c address of the bridge
- powerdown-gpio: OF device-tree gpio specification
- reset-gpio: OF device-tree gpio specification
- edid-emulation: The EDID emulation entry to use
+-------+------------+------------------+
| Value | Resolution | Description |
| 0 | 1024x768 | NXP Generic |
| 1 | 1920x1080 | NXP Generic |
| 2 | 1920x1080 | NXP Generic |
| 3 | 1600x900 | Samsung LTM200KT |
| 4 | 1920x1080 | Samsung LTM230HT |
| 5 | 1366x768 | NXP Generic |
| 6 | 1600x900 | ChiMei M215HGE |
+-------+------------+------------------+
Example:
lvds-bridge@20 {
compatible = "nxp,ptn3460";
reg = <0x20>;
powerdown-gpio = <&gpy2 5 1 0 0>;
reset-gpio = <&gpx1 5 1 0 0>;
edid-emulation = <5>;
};
...@@ -49,6 +49,8 @@ Required properties for dp-controller: ...@@ -49,6 +49,8 @@ Required properties for dp-controller:
-samsung,lane-count: -samsung,lane-count:
number of lanes supported by the panel. number of lanes supported by the panel.
LANE_COUNT1 = 1, LANE_COUNT2 = 2, LANE_COUNT4 = 4 LANE_COUNT1 = 1, LANE_COUNT2 = 2, LANE_COUNT4 = 4
- display-timings: timings for the connected panel as described by
Documentation/devicetree/bindings/video/display-timing.txt
Optional properties for dp-controller: Optional properties for dp-controller:
-interlaced: -interlaced:
...@@ -84,4 +86,19 @@ Board Specific portion: ...@@ -84,4 +86,19 @@ Board Specific portion:
samsung,color-depth = <1>; samsung,color-depth = <1>;
samsung,link-rate = <0x0a>; samsung,link-rate = <0x0a>;
samsung,lane-count = <4>; samsung,lane-count = <4>;
display-timings {
native-mode = <&lcd_timing>;
lcd_timing: 1366x768 {
clock-frequency = <70589280>;
hactive = <1366>;
vactive = <768>;
hfront-porch = <40>;
hback-porch = <40>;
hsync-len = <32>;
vback-porch = <10>;
vfront-porch = <12>;
vsync-len = <6>;
};
};
}; };
...@@ -25,6 +25,9 @@ Required properties: ...@@ -25,6 +25,9 @@ Required properties:
sclk_pixel. sclk_pixel.
- clock-names: aliases as per driver requirements for above clock IDs: - clock-names: aliases as per driver requirements for above clock IDs:
"hdmi", "sclk_hdmi", "sclk_pixel", "sclk_hdmiphy" and "mout_hdmi". "hdmi", "sclk_hdmi", "sclk_pixel", "sclk_hdmiphy" and "mout_hdmi".
- ddc: phandle to the hdmi ddc node
- phy: phandle to the hdmi phy node
Example: Example:
hdmi { hdmi {
...@@ -32,4 +35,6 @@ Example: ...@@ -32,4 +35,6 @@ Example:
reg = <0x14530000 0x100000>; reg = <0x14530000 0x100000>;
interrupts = <0 95 0>; interrupts = <0 95 0>;
hpd-gpio = <&gpx3 7 1>; hpd-gpio = <&gpx3 7 1>;
ddc = <&hdmi_ddc_node>;
phy = <&hdmi_phy_node>;
}; };
...@@ -39,6 +39,23 @@ Required properties: ...@@ -39,6 +39,23 @@ Required properties:
Optional Properties: Optional Properties:
- samsung,power-domain: a phandle to FIMD power domain node. - samsung,power-domain: a phandle to FIMD power domain node.
- samsung,invert-vden: video enable signal is inverted
- samsung,invert-vclk: video clock signal is inverted
- 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.
The device node can contain 'port' child nodes according to the bindings defined
in [2]. The following are properties specific to those nodes:
- reg: (required) port index, can be:
0 - for CAMIF0 input,
1 - for CAMIF1 input,
2 - for CAMIF2 input,
3 - for parallel output,
4 - for write-back interface
[1]: Documentation/devicetree/bindings/video/display-timing.txt
[2]: Documentation/devicetree/bindings/media/video-interfaces.txt
Example: Example:
......
...@@ -3393,12 +3393,6 @@ S: Maintained ...@@ -3393,12 +3393,6 @@ S: Maintained
F: drivers/extcon/ F: drivers/extcon/
F: Documentation/extcon/ F: Documentation/extcon/
EXYNOS DP DRIVER
M: Jingoo Han <jg1.han@samsung.com>
L: linux-fbdev@vger.kernel.org
S: Maintained
F: drivers/video/exynos/exynos_dp*
EXYNOS MIPI DISPLAY DRIVERS EXYNOS MIPI DISPLAY DRIVERS
M: Inki Dae <inki.dae@samsung.com> M: Inki Dae <inki.dae@samsung.com>
M: Donghwa Lee <dh09.lee@samsung.com> M: Donghwa Lee <dh09.lee@samsung.com>
......
...@@ -225,6 +225,7 @@ ldo7_reg: LDO7 { ...@@ -225,6 +225,7 @@ ldo7_reg: LDO7 {
regulator-name = "VLCD+VMIPI_1.8V"; regulator-name = "VLCD+VMIPI_1.8V";
regulator-min-microvolt = <1800000>; regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>; regulator-max-microvolt = <1800000>;
regulator-always-on;
}; };
ldo8_reg: LDO8 { ldo8_reg: LDO8 {
...@@ -288,6 +289,7 @@ ldo17_reg: LDO17 { ...@@ -288,6 +289,7 @@ ldo17_reg: LDO17 {
regulator-name = "VCC_3.0V_LCD"; regulator-name = "VCC_3.0V_LCD";
regulator-min-microvolt = <3000000>; regulator-min-microvolt = <3000000>;
regulator-max-microvolt = <3000000>; regulator-max-microvolt = <3000000>;
regulator-always-on;
}; };
buck1_reg: BUCK1 { buck1_reg: BUCK1 {
...@@ -345,6 +347,31 @@ safeout2_reg: ESAFEOUT2 { ...@@ -345,6 +347,31 @@ safeout2_reg: ESAFEOUT2 {
}; };
}; };
fimd: fimd@11c00000 {
pinctrl-0 = <&lcd_clk>, <&lcd_data24>;
pinctrl-names = "default";
status = "okay";
samsung,invert-vden;
samsung,invert-vclk;
display-timings {
timing {
clock-frequency = <23492370>;
hactive = <480>;
vactive = <800>;
hback-porch = <16>;
hfront-porch = <16>;
vback-porch = <2>;
vfront-porch = <28>;
hsync-len = <2>;
vsync-len = <1>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
};
};
};
pwm@139D0000 { pwm@139D0000 {
compatible = "samsung,s5p6440-pwm"; compatible = "samsung,s5p6440-pwm";
status = "okay"; status = "okay";
......
...@@ -199,3 +199,5 @@ source "drivers/gpu/drm/msm/Kconfig" ...@@ -199,3 +199,5 @@ source "drivers/gpu/drm/msm/Kconfig"
source "drivers/gpu/drm/tegra/Kconfig" source "drivers/gpu/drm/tegra/Kconfig"
source "drivers/gpu/drm/panel/Kconfig" source "drivers/gpu/drm/panel/Kconfig"
source "drivers/gpu/drm/bridge/Kconfig"
...@@ -63,3 +63,4 @@ obj-$(CONFIG_DRM_MSM) += msm/ ...@@ -63,3 +63,4 @@ obj-$(CONFIG_DRM_MSM) += msm/
obj-$(CONFIG_DRM_TEGRA) += tegra/ obj-$(CONFIG_DRM_TEGRA) += tegra/
obj-y += i2c/ obj-y += i2c/
obj-y += panel/ obj-y += panel/
obj-y += bridge/
config DRM_PTN3460
tristate "PTN3460 DP/LVDS bridge"
depends on DRM && I2C
---help---
ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o
/*
* NXP PTN3460 DP/LVDS bridge driver
*
* Copyright (C) 2013 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include "drmP.h"
#include "drm_edid.h"
#include "drm_crtc.h"
#include "drm_crtc_helper.h"
#include "bridge/ptn3460.h"
#define PTN3460_EDID_ADDR 0x0
#define PTN3460_EDID_EMULATION_ADDR 0x84
#define PTN3460_EDID_ENABLE_EMULATION 0
#define PTN3460_EDID_EMULATION_SELECTION 1
#define PTN3460_EDID_SRAM_LOAD_ADDR 0x85
struct ptn3460_bridge {
struct drm_connector connector;
struct i2c_client *client;
struct drm_encoder *encoder;
struct drm_bridge *bridge;
struct edid *edid;
int gpio_pd_n;
int gpio_rst_n;
u32 edid_emulation;
bool enabled;
};
static int ptn3460_read_bytes(struct ptn3460_bridge *ptn_bridge, char addr,
u8 *buf, int len)
{
int ret;
ret = i2c_master_send(ptn_bridge->client, &addr, 1);
if (ret <= 0) {
DRM_ERROR("Failed to send i2c command, ret=%d\n", ret);
return ret;
}
ret = i2c_master_recv(ptn_bridge->client, buf, len);
if (ret <= 0) {
DRM_ERROR("Failed to recv i2c data, ret=%d\n", ret);
return ret;
}
return 0;
}
static int ptn3460_write_byte(struct ptn3460_bridge *ptn_bridge, char addr,
char val)
{
int ret;
char buf[2];
buf[0] = addr;
buf[1] = val;
ret = i2c_master_send(ptn_bridge->client, buf, ARRAY_SIZE(buf));
if (ret <= 0) {
DRM_ERROR("Failed to send i2c command, ret=%d\n", ret);
return ret;
}
return 0;
}
static int ptn3460_select_edid(struct ptn3460_bridge *ptn_bridge)
{
int ret;
char val;
/* Load the selected edid into SRAM (accessed at PTN3460_EDID_ADDR) */
ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_SRAM_LOAD_ADDR,
ptn_bridge->edid_emulation);
if (ret) {
DRM_ERROR("Failed to transfer edid to sram, ret=%d\n", ret);
return ret;
}
/* Enable EDID emulation and select the desired EDID */
val = 1 << PTN3460_EDID_ENABLE_EMULATION |
ptn_bridge->edid_emulation << PTN3460_EDID_EMULATION_SELECTION;
ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_EMULATION_ADDR, val);
if (ret) {
DRM_ERROR("Failed to write edid value, ret=%d\n", ret);
return ret;
}
return 0;
}
static void ptn3460_pre_enable(struct drm_bridge *bridge)
{
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
int ret;
if (ptn_bridge->enabled)
return;
if (gpio_is_valid(ptn_bridge->gpio_pd_n))
gpio_set_value(ptn_bridge->gpio_pd_n, 1);
if (gpio_is_valid(ptn_bridge->gpio_rst_n)) {
gpio_set_value(ptn_bridge->gpio_rst_n, 0);
udelay(10);
gpio_set_value(ptn_bridge->gpio_rst_n, 1);
}
/*
* There's a bug in the PTN chip where it falsely asserts hotplug before
* it is fully functional. We're forced to wait for the maximum start up
* time specified in the chip's datasheet to make sure we're really up.
*/
msleep(90);
ret = ptn3460_select_edid(ptn_bridge);
if (ret)
DRM_ERROR("Select edid failed ret=%d\n", ret);
ptn_bridge->enabled = true;
}
static void ptn3460_enable(struct drm_bridge *bridge)
{
}
static void ptn3460_disable(struct drm_bridge *bridge)
{
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
if (!ptn_bridge->enabled)
return;
ptn_bridge->enabled = false;
if (gpio_is_valid(ptn_bridge->gpio_rst_n))
gpio_set_value(ptn_bridge->gpio_rst_n, 1);
if (gpio_is_valid(ptn_bridge->gpio_pd_n))
gpio_set_value(ptn_bridge->gpio_pd_n, 0);
}
static void ptn3460_post_disable(struct drm_bridge *bridge)
{
}
void ptn3460_bridge_destroy(struct drm_bridge *bridge)
{
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
drm_bridge_cleanup(bridge);
if (gpio_is_valid(ptn_bridge->gpio_pd_n))
gpio_free(ptn_bridge->gpio_pd_n);
if (gpio_is_valid(ptn_bridge->gpio_rst_n))
gpio_free(ptn_bridge->gpio_rst_n);
/* Nothing else to free, we've got devm allocated memory */
}
struct drm_bridge_funcs ptn3460_bridge_funcs = {
.pre_enable = ptn3460_pre_enable,
.enable = ptn3460_enable,
.disable = ptn3460_disable,
.post_disable = ptn3460_post_disable,
.destroy = ptn3460_bridge_destroy,
};
int ptn3460_get_modes(struct drm_connector *connector)
{
struct ptn3460_bridge *ptn_bridge;
u8 *edid;
int ret, num_modes;
bool power_off;
ptn_bridge = container_of(connector, struct ptn3460_bridge, connector);
if (ptn_bridge->edid)
return drm_add_edid_modes(connector, ptn_bridge->edid);
power_off = !ptn_bridge->enabled;
ptn3460_pre_enable(ptn_bridge->bridge);
edid = kmalloc(EDID_LENGTH, GFP_KERNEL);
if (!edid) {
DRM_ERROR("Failed to allocate edid\n");
return 0;
}
ret = ptn3460_read_bytes(ptn_bridge, PTN3460_EDID_ADDR, edid,
EDID_LENGTH);
if (ret) {
kfree(edid);
num_modes = 0;
goto out;
}
ptn_bridge->edid = (struct edid *)edid;
drm_mode_connector_update_edid_property(connector, ptn_bridge->edid);
num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
out:
if (power_off)
ptn3460_disable(ptn_bridge->bridge);
return num_modes;
}
static int ptn3460_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
struct drm_encoder *ptn3460_best_encoder(struct drm_connector *connector)
{
struct ptn3460_bridge *ptn_bridge;
ptn_bridge = container_of(connector, struct ptn3460_bridge, connector);
return ptn_bridge->encoder;
}
struct drm_connector_helper_funcs ptn3460_connector_helper_funcs = {
.get_modes = ptn3460_get_modes,
.mode_valid = ptn3460_mode_valid,
.best_encoder = ptn3460_best_encoder,
};
enum drm_connector_status ptn3460_detect(struct drm_connector *connector,
bool force)
{
return connector_status_connected;
}
void ptn3460_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}
struct drm_connector_funcs ptn3460_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = ptn3460_detect,
.destroy = ptn3460_connector_destroy,
};
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node)
{
int ret;
struct drm_bridge *bridge;
struct ptn3460_bridge *ptn_bridge;
bridge = devm_kzalloc(dev->dev, sizeof(*bridge), GFP_KERNEL);
if (!bridge) {
DRM_ERROR("Failed to allocate drm bridge\n");
return -ENOMEM;
}
ptn_bridge = devm_kzalloc(dev->dev, sizeof(*ptn_bridge), GFP_KERNEL);
if (!ptn_bridge) {
DRM_ERROR("Failed to allocate ptn bridge\n");
return -ENOMEM;
}
ptn_bridge->client = client;
ptn_bridge->encoder = encoder;
ptn_bridge->bridge = bridge;
ptn_bridge->gpio_pd_n = of_get_named_gpio(node, "powerdown-gpio", 0);
if (gpio_is_valid(ptn_bridge->gpio_pd_n)) {
ret = gpio_request_one(ptn_bridge->gpio_pd_n,
GPIOF_OUT_INIT_HIGH, "PTN3460_PD_N");
if (ret) {
DRM_ERROR("Request powerdown-gpio failed (%d)\n", ret);
return ret;
}
}
ptn_bridge->gpio_rst_n = of_get_named_gpio(node, "reset-gpio", 0);
if (gpio_is_valid(ptn_bridge->gpio_rst_n)) {
/*
* Request the reset pin low to avoid the bridge being
* initialized prematurely
*/
ret = gpio_request_one(ptn_bridge->gpio_rst_n,
GPIOF_OUT_INIT_LOW, "PTN3460_RST_N");
if (ret) {
DRM_ERROR("Request reset-gpio failed (%d)\n", ret);
gpio_free(ptn_bridge->gpio_pd_n);
return ret;
}
}
ret = of_property_read_u32(node, "edid-emulation",
&ptn_bridge->edid_emulation);
if (ret) {
DRM_ERROR("Can't read edid emulation value\n");
goto err;
}
ret = drm_bridge_init(dev, bridge, &ptn3460_bridge_funcs);
if (ret) {
DRM_ERROR("Failed to initialize bridge with drm\n");
goto err;
}
bridge->driver_private = ptn_bridge;
encoder->bridge = bridge;
ret = drm_connector_init(dev, &ptn_bridge->connector,
&ptn3460_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
if (ret) {
DRM_ERROR("Failed to initialize connector with drm\n");
goto err;
}
drm_connector_helper_add(&ptn_bridge->connector,
&ptn3460_connector_helper_funcs);
drm_sysfs_connector_add(&ptn_bridge->connector);
drm_mode_connector_attach_encoder(&ptn_bridge->connector, encoder);
return 0;
err:
if (gpio_is_valid(ptn_bridge->gpio_pd_n))
gpio_free(ptn_bridge->gpio_pd_n);
if (gpio_is_valid(ptn_bridge->gpio_rst_n))
gpio_free(ptn_bridge->gpio_rst_n);
return ret;
}
...@@ -31,6 +31,21 @@ config DRM_EXYNOS_FIMD ...@@ -31,6 +31,21 @@ config DRM_EXYNOS_FIMD
help help
Choose this option if you want to use Exynos FIMD for DRM. Choose this option if you want to use Exynos FIMD for DRM.
config DRM_EXYNOS_DPI
bool "EXYNOS DRM parallel output support"
depends on DRM_EXYNOS
select DRM_PANEL
default n
help
This enables support for Exynos parallel output.
config DRM_EXYNOS_DP
bool "EXYNOS DRM DP driver support"
depends on DRM_EXYNOS && ARCH_EXYNOS
default DRM_EXYNOS
help
This enables support for DP device.
config DRM_EXYNOS_HDMI config DRM_EXYNOS_HDMI
bool "Exynos DRM HDMI" bool "Exynos DRM HDMI"
depends on DRM_EXYNOS && !VIDEO_SAMSUNG_S5P_TV depends on DRM_EXYNOS && !VIDEO_SAMSUNG_S5P_TV
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/exynos ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/exynos
exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \ exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o \
exynos_drm_crtc.o exynos_drm_fbdev.o exynos_drm_fb.o \ exynos_drm_crtc.o exynos_drm_fbdev.o exynos_drm_fb.o \
exynos_drm_buf.o exynos_drm_gem.o exynos_drm_core.o \ exynos_drm_buf.o exynos_drm_gem.o exynos_drm_core.o \
exynos_drm_plane.o exynos_drm_plane.o
...@@ -11,9 +11,9 @@ exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \ ...@@ -11,9 +11,9 @@ exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \
exynosdrm-$(CONFIG_DRM_EXYNOS_IOMMU) += exynos_drm_iommu.o exynosdrm-$(CONFIG_DRM_EXYNOS_IOMMU) += exynos_drm_iommu.o
exynosdrm-$(CONFIG_DRM_EXYNOS_DMABUF) += exynos_drm_dmabuf.o exynosdrm-$(CONFIG_DRM_EXYNOS_DMABUF) += exynos_drm_dmabuf.o
exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o
exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o \ exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o
exynos_ddc.o exynos_hdmiphy.o \ exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp_core.o exynos_dp_reg.o
exynos_drm_hdmi.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o
exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o
exynosdrm-$(CONFIG_DRM_EXYNOS_G2D) += exynos_drm_g2d.o exynosdrm-$(CONFIG_DRM_EXYNOS_G2D) += exynos_drm_g2d.o
exynosdrm-$(CONFIG_DRM_EXYNOS_IPP) += exynos_drm_ipp.o exynosdrm-$(CONFIG_DRM_EXYNOS_IPP) += exynos_drm_ipp.o
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/io.h> #include <linux/io.h>
...@@ -20,9 +19,25 @@ ...@@ -20,9 +19,25 @@
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/phy/phy.h> #include <linux/phy/phy.h>
#include <video/of_display_timing.h>
#include <video/of_videomode.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/bridge/ptn3460.h>
#include "exynos_drm_drv.h"
#include "exynos_dp_core.h" #include "exynos_dp_core.h"
#define ctx_from_connector(c) container_of(c, struct exynos_dp_device, \
connector)
struct bridge_init {
struct i2c_client *client;
struct device_node *node;
};
static int exynos_dp_init_dp(struct exynos_dp_device *dp) static int exynos_dp_init_dp(struct exynos_dp_device *dp)
{ {
exynos_dp_reset(dp); exynos_dp_reset(dp);
...@@ -893,6 +908,214 @@ static void exynos_dp_hotplug(struct work_struct *work) ...@@ -893,6 +908,214 @@ static void exynos_dp_hotplug(struct work_struct *work)
dev_err(dp->dev, "unable to config video\n"); dev_err(dp->dev, "unable to config video\n");
} }
static enum drm_connector_status exynos_dp_detect(
struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static void exynos_dp_connector_destroy(struct drm_connector *connector)
{
}
static struct drm_connector_funcs exynos_dp_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = exynos_dp_detect,
.destroy = exynos_dp_connector_destroy,
};
static int exynos_dp_get_modes(struct drm_connector *connector)
{
struct exynos_dp_device *dp = ctx_from_connector(connector);
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_videomode(&dp->panel.vm, mode);
mode->width_mm = dp->panel.width_mm;
mode->height_mm = dp->panel.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_set_name(mode);
drm_mode_probed_add(connector, mode);
return 1;
}
static int exynos_dp_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static struct drm_encoder *exynos_dp_best_encoder(
struct drm_connector *connector)
{
struct exynos_dp_device *dp = ctx_from_connector(connector);
return dp->encoder;
}
static struct drm_connector_helper_funcs exynos_dp_connector_helper_funcs = {
.get_modes = exynos_dp_get_modes,
.mode_valid = exynos_dp_mode_valid,
.best_encoder = exynos_dp_best_encoder,
};
static int exynos_dp_initialize(struct exynos_drm_display *display,
struct drm_device *drm_dev)
{
struct exynos_dp_device *dp = display->ctx;
dp->drm_dev = drm_dev;
return 0;
}
static bool find_bridge(const char *compat, struct bridge_init *bridge)
{
bridge->client = NULL;
bridge->node = of_find_compatible_node(NULL, NULL, compat);
if (!bridge->node)
return false;
bridge->client = of_find_i2c_device_by_node(bridge->node);
if (!bridge->client)
return false;
return true;
}
/* returns the number of bridges attached */
static int exynos_drm_attach_lcd_bridge(struct drm_device *dev,
struct drm_encoder *encoder)
{
struct bridge_init bridge;
int ret;
if (find_bridge("nxp,ptn3460", &bridge)) {
ret = ptn3460_init(dev, encoder, bridge.client, bridge.node);
if (!ret)
return 1;
}
return 0;
}
static int exynos_dp_create_connector(struct exynos_drm_display *display,
struct drm_encoder *encoder)
{
struct exynos_dp_device *dp = display->ctx;
struct drm_connector *connector = &dp->connector;
int ret;
dp->encoder = encoder;
/* Pre-empt DP connector creation if there's a bridge */
ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder);
if (ret)
return 0;
connector->polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(dp->drm_dev, connector,
&exynos_dp_connector_funcs, DRM_MODE_CONNECTOR_eDP);
if (ret) {
DRM_ERROR("Failed to initialize connector with drm\n");
return ret;
}
drm_connector_helper_add(connector, &exynos_dp_connector_helper_funcs);
drm_sysfs_connector_add(connector);
drm_mode_connector_attach_encoder(connector, encoder);
return 0;
}
static void exynos_dp_phy_init(struct exynos_dp_device *dp)
{
if (dp->phy) {
phy_power_on(dp->phy);
} else if (dp->phy_addr) {
u32 reg;
reg = __raw_readl(dp->phy_addr);
reg |= dp->enable_mask;
__raw_writel(reg, dp->phy_addr);
}
}
static void exynos_dp_phy_exit(struct exynos_dp_device *dp)
{
if (dp->phy) {
phy_power_off(dp->phy);
} else if (dp->phy_addr) {
u32 reg;
reg = __raw_readl(dp->phy_addr);
reg &= ~(dp->enable_mask);
__raw_writel(reg, dp->phy_addr);
}
}
static void exynos_dp_poweron(struct exynos_dp_device *dp)
{
if (dp->dpms_mode == DRM_MODE_DPMS_ON)
return;
clk_prepare_enable(dp->clock);
exynos_dp_phy_init(dp);
exynos_dp_init_dp(dp);
enable_irq(dp->irq);
}
static void exynos_dp_poweroff(struct exynos_dp_device *dp)
{
if (dp->dpms_mode != DRM_MODE_DPMS_ON)
return;
disable_irq(dp->irq);
flush_work(&dp->hotplug_work);
exynos_dp_phy_exit(dp);
clk_disable_unprepare(dp->clock);
}
static void exynos_dp_dpms(struct exynos_drm_display *display, int mode)
{
struct exynos_dp_device *dp = display->ctx;
switch (mode) {
case DRM_MODE_DPMS_ON:
exynos_dp_poweron(dp);
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
exynos_dp_poweroff(dp);
break;
default:
break;
};
dp->dpms_mode = mode;
}
static struct exynos_drm_display_ops exynos_dp_display_ops = {
.initialize = exynos_dp_initialize,
.create_connector = exynos_dp_create_connector,
.dpms = exynos_dp_dpms,
};
static struct exynos_drm_display exynos_dp_display = {
.type = EXYNOS_DISPLAY_TYPE_LCD,
.ops = &exynos_dp_display_ops,
};
static struct video_info *exynos_dp_dt_parse_pdata(struct device *dev) static struct video_info *exynos_dp_dt_parse_pdata(struct device *dev)
{ {
struct device_node *dp_node = dev->of_node; struct device_node *dp_node = dev->of_node;
...@@ -994,30 +1217,17 @@ static int exynos_dp_dt_parse_phydata(struct exynos_dp_device *dp) ...@@ -994,30 +1217,17 @@ static int exynos_dp_dt_parse_phydata(struct exynos_dp_device *dp)
return ret; return ret;
} }
static void exynos_dp_phy_init(struct exynos_dp_device *dp) static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp)
{
if (dp->phy) {
phy_power_on(dp->phy);
} else if (dp->phy_addr) {
u32 reg;
reg = __raw_readl(dp->phy_addr);
reg |= dp->enable_mask;
__raw_writel(reg, dp->phy_addr);
}
}
static void exynos_dp_phy_exit(struct exynos_dp_device *dp)
{ {
if (dp->phy) { int ret;
phy_power_off(dp->phy);
} else if (dp->phy_addr) {
u32 reg;
reg = __raw_readl(dp->phy_addr); ret = of_get_videomode(dp->dev->of_node, &dp->panel.vm,
reg &= ~(dp->enable_mask); OF_USE_NATIVE_MODE);
__raw_writel(reg, dp->phy_addr); if (ret) {
DRM_ERROR("failed: of_get_videomode() : %d\n", ret);
return ret;
} }
return 0;
} }
static int exynos_dp_probe(struct platform_device *pdev) static int exynos_dp_probe(struct platform_device *pdev)
...@@ -1035,6 +1245,7 @@ static int exynos_dp_probe(struct platform_device *pdev) ...@@ -1035,6 +1245,7 @@ static int exynos_dp_probe(struct platform_device *pdev)
} }
dp->dev = &pdev->dev; dp->dev = &pdev->dev;
dp->dpms_mode = DRM_MODE_DPMS_OFF;
dp->video_info = exynos_dp_dt_parse_pdata(&pdev->dev); dp->video_info = exynos_dp_dt_parse_pdata(&pdev->dev);
if (IS_ERR(dp->video_info)) if (IS_ERR(dp->video_info))
...@@ -1044,6 +1255,10 @@ static int exynos_dp_probe(struct platform_device *pdev) ...@@ -1044,6 +1255,10 @@ static int exynos_dp_probe(struct platform_device *pdev)
if (ret) if (ret)
return ret; return ret;
ret = exynos_dp_dt_parse_panel(dp);
if (ret)
return ret;
dp->clock = devm_clk_get(&pdev->dev, "dp"); dp->clock = devm_clk_get(&pdev->dev, "dp");
if (IS_ERR(dp->clock)) { if (IS_ERR(dp->clock)) {
dev_err(&pdev->dev, "failed to get clock\n"); dev_err(&pdev->dev, "failed to get clock\n");
...@@ -1076,22 +1291,22 @@ static int exynos_dp_probe(struct platform_device *pdev) ...@@ -1076,22 +1291,22 @@ static int exynos_dp_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to request irq\n"); dev_err(&pdev->dev, "failed to request irq\n");
return ret; return ret;
} }
disable_irq(dp->irq);
exynos_dp_display.ctx = dp;
platform_set_drvdata(pdev, dp); platform_set_drvdata(pdev, &exynos_dp_display);
exynos_drm_display_register(&exynos_dp_display);
return 0; return 0;
} }
static int exynos_dp_remove(struct platform_device *pdev) static int exynos_dp_remove(struct platform_device *pdev)
{ {
struct exynos_dp_device *dp = platform_get_drvdata(pdev); struct exynos_drm_display *display = platform_get_drvdata(pdev);
flush_work(&dp->hotplug_work);
exynos_dp_phy_exit(dp);
clk_disable_unprepare(dp->clock);
exynos_dp_dpms(display, DRM_MODE_DPMS_OFF);
exynos_drm_display_unregister(&exynos_dp_display);
return 0; return 0;
} }
...@@ -1099,31 +1314,19 @@ static int exynos_dp_remove(struct platform_device *pdev) ...@@ -1099,31 +1314,19 @@ static int exynos_dp_remove(struct platform_device *pdev)
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
static int exynos_dp_suspend(struct device *dev) static int exynos_dp_suspend(struct device *dev)
{ {
struct exynos_dp_device *dp = dev_get_drvdata(dev); struct platform_device *pdev = to_platform_device(dev);
struct exynos_drm_display *display = platform_get_drvdata(pdev);
disable_irq(dp->irq);
flush_work(&dp->hotplug_work);
exynos_dp_phy_exit(dp);
clk_disable_unprepare(dp->clock);
exynos_dp_dpms(display, DRM_MODE_DPMS_OFF);
return 0; return 0;
} }
static int exynos_dp_resume(struct device *dev) static int exynos_dp_resume(struct device *dev)
{ {
struct exynos_dp_device *dp = dev_get_drvdata(dev); struct platform_device *pdev = to_platform_device(dev);
struct exynos_drm_display *display = platform_get_drvdata(pdev);
exynos_dp_phy_init(dp);
clk_prepare_enable(dp->clock);
exynos_dp_init_dp(dp);
enable_irq(dp->irq);
exynos_dp_dpms(display, DRM_MODE_DPMS_ON);
return 0; return 0;
} }
#endif #endif
...@@ -1138,7 +1341,7 @@ static const struct of_device_id exynos_dp_match[] = { ...@@ -1138,7 +1341,7 @@ static const struct of_device_id exynos_dp_match[] = {
}; };
MODULE_DEVICE_TABLE(of, exynos_dp_match); MODULE_DEVICE_TABLE(of, exynos_dp_match);
static struct platform_driver exynos_dp_driver = { struct platform_driver dp_driver = {
.probe = exynos_dp_probe, .probe = exynos_dp_probe,
.remove = exynos_dp_remove, .remove = exynos_dp_remove,
.driver = { .driver = {
...@@ -1149,8 +1352,6 @@ static struct platform_driver exynos_dp_driver = { ...@@ -1149,8 +1352,6 @@ static struct platform_driver exynos_dp_driver = {
}, },
}; };
module_platform_driver(exynos_dp_driver);
MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
MODULE_DESCRIPTION("Samsung SoC DP Driver"); MODULE_DESCRIPTION("Samsung SoC DP Driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
#ifndef _EXYNOS_DP_CORE_H #ifndef _EXYNOS_DP_CORE_H
#define _EXYNOS_DP_CORE_H #define _EXYNOS_DP_CORE_H
#include <drm/drm_crtc.h>
#include <drm/exynos_drm.h>
#define DP_TIMEOUT_LOOP_COUNT 100 #define DP_TIMEOUT_LOOP_COUNT 100
#define MAX_CR_LOOP 5 #define MAX_CR_LOOP 5
#define MAX_EQ_LOOP 5 #define MAX_EQ_LOOP 5
...@@ -142,6 +145,9 @@ struct link_train { ...@@ -142,6 +145,9 @@ struct link_train {
struct exynos_dp_device { struct exynos_dp_device {
struct device *dev; struct device *dev;
struct drm_device *drm_dev;
struct drm_connector connector;
struct drm_encoder *encoder;
struct clk *clock; struct clk *clock;
unsigned int irq; unsigned int irq;
void __iomem *reg_base; void __iomem *reg_base;
...@@ -152,6 +158,9 @@ struct exynos_dp_device { ...@@ -152,6 +158,9 @@ struct exynos_dp_device {
struct link_train link_train; struct link_train link_train;
struct work_struct hotplug_work; struct work_struct hotplug_work;
struct phy *phy; struct phy *phy;
int dpms_mode;
struct exynos_drm_panel_info panel;
}; };
/* exynos_dp_reg.c */ /* exynos_dp_reg.c */
......
...@@ -25,25 +25,18 @@ ...@@ -25,25 +25,18 @@
struct exynos_drm_connector { struct exynos_drm_connector {
struct drm_connector drm_connector; struct drm_connector drm_connector;
uint32_t encoder_id; uint32_t encoder_id;
struct exynos_drm_manager *manager; struct exynos_drm_display *display;
uint32_t dpms;
}; };
static int exynos_drm_connector_get_modes(struct drm_connector *connector) static int exynos_drm_connector_get_modes(struct drm_connector *connector)
{ {
struct exynos_drm_connector *exynos_connector = struct exynos_drm_connector *exynos_connector =
to_exynos_connector(connector); to_exynos_connector(connector);
struct exynos_drm_manager *manager = exynos_connector->manager; struct exynos_drm_display *display = exynos_connector->display;
struct exynos_drm_display_ops *display_ops = manager->display_ops;
struct edid *edid = NULL; struct edid *edid = NULL;
unsigned int count = 0; unsigned int count = 0;
int ret; int ret;
if (!display_ops) {
DRM_DEBUG_KMS("display_ops is null.\n");
return 0;
}
/* /*
* if get_edid() exists then get_edid() callback of hdmi side * if get_edid() exists then get_edid() callback of hdmi side
* is called to get edid data through i2c interface else * is called to get edid data through i2c interface else
...@@ -52,8 +45,8 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector) ...@@ -52,8 +45,8 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector)
* P.S. in case of lcd panel, count is always 1 if success * P.S. in case of lcd panel, count is always 1 if success
* because lcd panel has only one mode. * because lcd panel has only one mode.
*/ */
if (display_ops->get_edid) { if (display->ops->get_edid) {
edid = display_ops->get_edid(manager->dev, connector); edid = display->ops->get_edid(display, connector);
if (IS_ERR_OR_NULL(edid)) { if (IS_ERR_OR_NULL(edid)) {
ret = PTR_ERR(edid); ret = PTR_ERR(edid);
edid = NULL; edid = NULL;
...@@ -76,8 +69,8 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector) ...@@ -76,8 +69,8 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector)
return 0; return 0;
} }
if (display_ops->get_panel) if (display->ops->get_panel)
panel = display_ops->get_panel(manager->dev); panel = display->ops->get_panel(display);
else { else {
drm_mode_destroy(connector->dev, mode); drm_mode_destroy(connector->dev, mode);
return 0; return 0;
...@@ -106,20 +99,20 @@ static int exynos_drm_connector_mode_valid(struct drm_connector *connector, ...@@ -106,20 +99,20 @@ static int exynos_drm_connector_mode_valid(struct drm_connector *connector,
{ {
struct exynos_drm_connector *exynos_connector = struct exynos_drm_connector *exynos_connector =
to_exynos_connector(connector); to_exynos_connector(connector);
struct exynos_drm_manager *manager = exynos_connector->manager; struct exynos_drm_display *display = exynos_connector->display;
struct exynos_drm_display_ops *display_ops = manager->display_ops;
int ret = MODE_BAD; int ret = MODE_BAD;
DRM_DEBUG_KMS("%s\n", __FILE__); DRM_DEBUG_KMS("%s\n", __FILE__);
if (display_ops && display_ops->check_mode) if (display->ops->check_mode)
if (!display_ops->check_mode(manager->dev, mode)) if (!display->ops->check_mode(display, mode))
ret = MODE_OK; ret = MODE_OK;
return ret; return ret;
} }
struct drm_encoder *exynos_drm_best_encoder(struct drm_connector *connector) static struct drm_encoder *exynos_drm_best_encoder(
struct drm_connector *connector)
{ {
struct drm_device *dev = connector->dev; struct drm_device *dev = connector->dev;
struct exynos_drm_connector *exynos_connector = struct exynos_drm_connector *exynos_connector =
...@@ -146,48 +139,12 @@ static struct drm_connector_helper_funcs exynos_connector_helper_funcs = { ...@@ -146,48 +139,12 @@ static struct drm_connector_helper_funcs exynos_connector_helper_funcs = {
.best_encoder = exynos_drm_best_encoder, .best_encoder = exynos_drm_best_encoder,
}; };
void exynos_drm_display_power(struct drm_connector *connector, int mode)
{
struct drm_encoder *encoder = exynos_drm_best_encoder(connector);
struct exynos_drm_connector *exynos_connector;
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
struct exynos_drm_display_ops *display_ops = manager->display_ops;
exynos_connector = to_exynos_connector(connector);
if (exynos_connector->dpms == mode) {
DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
return;
}
if (display_ops && display_ops->power_on)
display_ops->power_on(manager->dev, mode);
exynos_connector->dpms = mode;
}
static void exynos_drm_connector_dpms(struct drm_connector *connector,
int mode)
{
/*
* in case that drm_crtc_helper_set_mode() is called,
* encoder/crtc->funcs->dpms() will be just returned
* because they already were DRM_MODE_DPMS_ON so only
* exynos_drm_display_power() will be called.
*/
drm_helper_connector_dpms(connector, mode);
exynos_drm_display_power(connector, mode);
}
static int exynos_drm_connector_fill_modes(struct drm_connector *connector, static int exynos_drm_connector_fill_modes(struct drm_connector *connector,
unsigned int max_width, unsigned int max_height) unsigned int max_width, unsigned int max_height)
{ {
struct exynos_drm_connector *exynos_connector = struct exynos_drm_connector *exynos_connector =
to_exynos_connector(connector); to_exynos_connector(connector);
struct exynos_drm_manager *manager = exynos_connector->manager; struct exynos_drm_display *display = exynos_connector->display;
struct exynos_drm_manager_ops *ops = manager->ops;
unsigned int width, height; unsigned int width, height;
width = max_width; width = max_width;
...@@ -197,8 +154,8 @@ static int exynos_drm_connector_fill_modes(struct drm_connector *connector, ...@@ -197,8 +154,8 @@ static int exynos_drm_connector_fill_modes(struct drm_connector *connector,
* if specific driver want to find desired_mode using maxmum * if specific driver want to find desired_mode using maxmum
* resolution then get max width and height from that driver. * resolution then get max width and height from that driver.
*/ */
if (ops && ops->get_max_resol) if (display->ops->get_max_resol)
ops->get_max_resol(manager->dev, &width, &height); display->ops->get_max_resol(display, &width, &height);
return drm_helper_probe_single_connector_modes(connector, width, return drm_helper_probe_single_connector_modes(connector, width,
height); height);
...@@ -210,13 +167,11 @@ exynos_drm_connector_detect(struct drm_connector *connector, bool force) ...@@ -210,13 +167,11 @@ exynos_drm_connector_detect(struct drm_connector *connector, bool force)
{ {
struct exynos_drm_connector *exynos_connector = struct exynos_drm_connector *exynos_connector =
to_exynos_connector(connector); to_exynos_connector(connector);
struct exynos_drm_manager *manager = exynos_connector->manager; struct exynos_drm_display *display = exynos_connector->display;
struct exynos_drm_display_ops *display_ops =
manager->display_ops;
enum drm_connector_status status = connector_status_disconnected; enum drm_connector_status status = connector_status_disconnected;
if (display_ops && display_ops->is_connected) { if (display->ops->is_connected) {
if (display_ops->is_connected(manager->dev)) if (display->ops->is_connected(display))
status = connector_status_connected; status = connector_status_connected;
else else
status = connector_status_disconnected; status = connector_status_disconnected;
...@@ -236,7 +191,7 @@ static void exynos_drm_connector_destroy(struct drm_connector *connector) ...@@ -236,7 +191,7 @@ static void exynos_drm_connector_destroy(struct drm_connector *connector)
} }
static struct drm_connector_funcs exynos_connector_funcs = { static struct drm_connector_funcs exynos_connector_funcs = {
.dpms = exynos_drm_connector_dpms, .dpms = drm_helper_connector_dpms,
.fill_modes = exynos_drm_connector_fill_modes, .fill_modes = exynos_drm_connector_fill_modes,
.detect = exynos_drm_connector_detect, .detect = exynos_drm_connector_detect,
.destroy = exynos_drm_connector_destroy, .destroy = exynos_drm_connector_destroy,
...@@ -246,7 +201,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, ...@@ -246,7 +201,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev,
struct drm_encoder *encoder) struct drm_encoder *encoder)
{ {
struct exynos_drm_connector *exynos_connector; struct exynos_drm_connector *exynos_connector;
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder); struct exynos_drm_display *display = exynos_drm_get_display(encoder);
struct drm_connector *connector; struct drm_connector *connector;
int type; int type;
int err; int err;
...@@ -257,7 +212,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, ...@@ -257,7 +212,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev,
connector = &exynos_connector->drm_connector; connector = &exynos_connector->drm_connector;
switch (manager->display_ops->type) { switch (display->type) {
case EXYNOS_DISPLAY_TYPE_HDMI: case EXYNOS_DISPLAY_TYPE_HDMI:
type = DRM_MODE_CONNECTOR_HDMIA; type = DRM_MODE_CONNECTOR_HDMIA;
connector->interlace_allowed = true; connector->interlace_allowed = true;
...@@ -280,8 +235,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, ...@@ -280,8 +235,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev,
goto err_connector; goto err_connector;
exynos_connector->encoder_id = encoder->base.id; exynos_connector->encoder_id = encoder->base.id;
exynos_connector->manager = manager; exynos_connector->display = display;
exynos_connector->dpms = DRM_MODE_DPMS_OFF;
connector->dpms = DRM_MODE_DPMS_OFF; connector->dpms = DRM_MODE_DPMS_OFF;
connector->encoder = encoder; connector->encoder = encoder;
......
...@@ -17,8 +17,4 @@ ...@@ -17,8 +17,4 @@
struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, struct drm_connector *exynos_drm_connector_create(struct drm_device *dev,
struct drm_encoder *encoder); struct drm_encoder *encoder);
struct drm_encoder *exynos_drm_best_encoder(struct drm_connector *connector);
void exynos_drm_display_power(struct drm_connector *connector, int mode);
#endif #endif
...@@ -14,43 +14,42 @@ ...@@ -14,43 +14,42 @@
#include <drm/drmP.h> #include <drm/drmP.h>
#include "exynos_drm_drv.h" #include "exynos_drm_drv.h"
#include "exynos_drm_crtc.h"
#include "exynos_drm_encoder.h" #include "exynos_drm_encoder.h"
#include "exynos_drm_connector.h"
#include "exynos_drm_fbdev.h" #include "exynos_drm_fbdev.h"
static LIST_HEAD(exynos_drm_subdrv_list); static LIST_HEAD(exynos_drm_subdrv_list);
static LIST_HEAD(exynos_drm_manager_list);
static LIST_HEAD(exynos_drm_display_list);
static int exynos_drm_create_enc_conn(struct drm_device *dev, static int exynos_drm_create_enc_conn(struct drm_device *dev,
struct exynos_drm_subdrv *subdrv) struct exynos_drm_display *display)
{ {
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct drm_connector *connector; struct exynos_drm_manager *manager;
int ret; int ret;
unsigned long possible_crtcs = 0;
subdrv->manager->dev = subdrv->dev; /* Find possible crtcs for this display */
list_for_each_entry(manager, &exynos_drm_manager_list, list)
if (manager->type == display->type)
possible_crtcs |= 1 << manager->pipe;
/* create and initialize a encoder for this sub driver. */ /* create and initialize a encoder for this sub driver. */
encoder = exynos_drm_encoder_create(dev, subdrv->manager, encoder = exynos_drm_encoder_create(dev, display, possible_crtcs);
(1 << MAX_CRTC) - 1);
if (!encoder) { if (!encoder) {
DRM_ERROR("failed to create encoder\n"); DRM_ERROR("failed to create encoder\n");
return -EFAULT; return -EFAULT;
} }
/* display->encoder = encoder;
* create and initialize a connector for this sub driver and
* attach the encoder created above to the connector. ret = display->ops->create_connector(display, encoder);
*/ if (ret) {
connector = exynos_drm_connector_create(dev, encoder); DRM_ERROR("failed to create connector ret = %d\n", ret);
if (!connector) {
DRM_ERROR("failed to create connector\n");
ret = -EFAULT;
goto err_destroy_encoder; goto err_destroy_encoder;
} }
subdrv->encoder = encoder;
subdrv->connector = connector;
return 0; return 0;
err_destroy_encoder: err_destroy_encoder:
...@@ -58,21 +57,6 @@ static int exynos_drm_create_enc_conn(struct drm_device *dev, ...@@ -58,21 +57,6 @@ static int exynos_drm_create_enc_conn(struct drm_device *dev,
return ret; return ret;
} }
static void exynos_drm_destroy_enc_conn(struct exynos_drm_subdrv *subdrv)
{
if (subdrv->encoder) {
struct drm_encoder *encoder = subdrv->encoder;
encoder->funcs->destroy(encoder);
subdrv->encoder = NULL;
}
if (subdrv->connector) {
struct drm_connector *connector = subdrv->connector;
connector->funcs->destroy(connector);
subdrv->connector = NULL;
}
}
static int exynos_drm_subdrv_probe(struct drm_device *dev, static int exynos_drm_subdrv_probe(struct drm_device *dev,
struct exynos_drm_subdrv *subdrv) struct exynos_drm_subdrv *subdrv)
{ {
...@@ -104,10 +88,98 @@ static void exynos_drm_subdrv_remove(struct drm_device *dev, ...@@ -104,10 +88,98 @@ static void exynos_drm_subdrv_remove(struct drm_device *dev,
subdrv->remove(dev, subdrv->dev); subdrv->remove(dev, subdrv->dev);
} }
int exynos_drm_initialize_managers(struct drm_device *dev)
{
struct exynos_drm_manager *manager, *n;
int ret, pipe = 0;
list_for_each_entry(manager, &exynos_drm_manager_list, list) {
if (manager->ops->initialize) {
ret = manager->ops->initialize(manager, dev, pipe);
if (ret) {
DRM_ERROR("Mgr init [%d] failed with %d\n",
manager->type, ret);
goto err;
}
}
manager->drm_dev = dev;
manager->pipe = pipe++;
ret = exynos_drm_crtc_create(manager);
if (ret) {
DRM_ERROR("CRTC create [%d] failed with %d\n",
manager->type, ret);
goto err;
}
}
return 0;
err:
list_for_each_entry_safe(manager, n, &exynos_drm_manager_list, list) {
if (pipe-- > 0)
exynos_drm_manager_unregister(manager);
else
list_del(&manager->list);
}
return ret;
}
void exynos_drm_remove_managers(struct drm_device *dev)
{
struct exynos_drm_manager *manager, *n;
list_for_each_entry_safe(manager, n, &exynos_drm_manager_list, list)
exynos_drm_manager_unregister(manager);
}
int exynos_drm_initialize_displays(struct drm_device *dev)
{
struct exynos_drm_display *display, *n;
int ret, initialized = 0;
list_for_each_entry(display, &exynos_drm_display_list, list) {
if (display->ops->initialize) {
ret = display->ops->initialize(display, dev);
if (ret) {
DRM_ERROR("Display init [%d] failed with %d\n",
display->type, ret);
goto err;
}
}
initialized++;
ret = exynos_drm_create_enc_conn(dev, display);
if (ret) {
DRM_ERROR("Encoder create [%d] failed with %d\n",
display->type, ret);
goto err;
}
}
return 0;
err:
list_for_each_entry_safe(display, n, &exynos_drm_display_list, list) {
if (initialized-- > 0)
exynos_drm_display_unregister(display);
else
list_del(&display->list);
}
return ret;
}
void exynos_drm_remove_displays(struct drm_device *dev)
{
struct exynos_drm_display *display, *n;
list_for_each_entry_safe(display, n, &exynos_drm_display_list, list)
exynos_drm_display_unregister(display);
}
int exynos_drm_device_register(struct drm_device *dev) int exynos_drm_device_register(struct drm_device *dev)
{ {
struct exynos_drm_subdrv *subdrv, *n; struct exynos_drm_subdrv *subdrv, *n;
unsigned int fine_cnt = 0;
int err; int err;
if (!dev) if (!dev)
...@@ -120,30 +192,8 @@ int exynos_drm_device_register(struct drm_device *dev) ...@@ -120,30 +192,8 @@ int exynos_drm_device_register(struct drm_device *dev)
list_del(&subdrv->list); list_del(&subdrv->list);
continue; continue;
} }
/*
* if manager is null then it means that this sub driver
* doesn't need encoder and connector.
*/
if (!subdrv->manager) {
fine_cnt++;
continue;
}
err = exynos_drm_create_enc_conn(dev, subdrv);
if (err) {
DRM_DEBUG("failed to create encoder and connector.\n");
exynos_drm_subdrv_remove(dev, subdrv);
list_del(&subdrv->list);
continue;
}
fine_cnt++;
} }
if (!fine_cnt)
return -EINVAL;
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(exynos_drm_device_register); EXPORT_SYMBOL_GPL(exynos_drm_device_register);
...@@ -159,13 +209,44 @@ int exynos_drm_device_unregister(struct drm_device *dev) ...@@ -159,13 +209,44 @@ int exynos_drm_device_unregister(struct drm_device *dev)
list_for_each_entry(subdrv, &exynos_drm_subdrv_list, list) { list_for_each_entry(subdrv, &exynos_drm_subdrv_list, list) {
exynos_drm_subdrv_remove(dev, subdrv); exynos_drm_subdrv_remove(dev, subdrv);
exynos_drm_destroy_enc_conn(subdrv);
} }
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(exynos_drm_device_unregister); EXPORT_SYMBOL_GPL(exynos_drm_device_unregister);
int exynos_drm_manager_register(struct exynos_drm_manager *manager)
{
BUG_ON(!manager->ops);
list_add_tail(&manager->list, &exynos_drm_manager_list);
return 0;
}
int exynos_drm_manager_unregister(struct exynos_drm_manager *manager)
{
if (manager->ops->remove)
manager->ops->remove(manager);
list_del(&manager->list);
return 0;
}
int exynos_drm_display_register(struct exynos_drm_display *display)
{
BUG_ON(!display->ops);
list_add_tail(&display->list, &exynos_drm_display_list);
return 0;
}
int exynos_drm_display_unregister(struct exynos_drm_display *display)
{
if (display->ops->remove)
display->ops->remove(display);
list_del(&display->list);
return 0;
}
int exynos_drm_subdrv_register(struct exynos_drm_subdrv *subdrv) int exynos_drm_subdrv_register(struct exynos_drm_subdrv *subdrv)
{ {
if (!subdrv) if (!subdrv)
......
...@@ -33,6 +33,7 @@ enum exynos_crtc_mode { ...@@ -33,6 +33,7 @@ enum exynos_crtc_mode {
* *
* @drm_crtc: crtc object. * @drm_crtc: crtc object.
* @drm_plane: pointer of private plane object for this crtc * @drm_plane: pointer of private plane object for this crtc
* @manager: the manager associated with this crtc
* @pipe: a crtc index created at load() with a new crtc object creation * @pipe: a crtc index created at load() with a new crtc object creation
* and the crtc object would be set to private->crtc array * and the crtc object would be set to private->crtc array
* to get a crtc object corresponding to this pipe from private->crtc * to get a crtc object corresponding to this pipe from private->crtc
...@@ -46,6 +47,7 @@ enum exynos_crtc_mode { ...@@ -46,6 +47,7 @@ enum exynos_crtc_mode {
struct exynos_drm_crtc { struct exynos_drm_crtc {
struct drm_crtc drm_crtc; struct drm_crtc drm_crtc;
struct drm_plane *plane; struct drm_plane *plane;
struct exynos_drm_manager *manager;
unsigned int pipe; unsigned int pipe;
unsigned int dpms; unsigned int dpms;
enum exynos_crtc_mode mode; enum exynos_crtc_mode mode;
...@@ -56,6 +58,7 @@ struct exynos_drm_crtc { ...@@ -56,6 +58,7 @@ struct exynos_drm_crtc {
static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode) static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
{ {
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct exynos_drm_manager *manager = exynos_crtc->manager;
DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode); DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode);
...@@ -71,7 +74,9 @@ static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode) ...@@ -71,7 +74,9 @@ static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
drm_vblank_off(crtc->dev, exynos_crtc->pipe); drm_vblank_off(crtc->dev, exynos_crtc->pipe);
} }
exynos_drm_fn_encoder(crtc, &mode, exynos_drm_encoder_crtc_dpms); if (manager->ops->dpms)
manager->ops->dpms(manager, mode);
exynos_crtc->dpms = mode; exynos_crtc->dpms = mode;
} }
...@@ -83,9 +88,15 @@ static void exynos_drm_crtc_prepare(struct drm_crtc *crtc) ...@@ -83,9 +88,15 @@ static void exynos_drm_crtc_prepare(struct drm_crtc *crtc)
static void exynos_drm_crtc_commit(struct drm_crtc *crtc) static void exynos_drm_crtc_commit(struct drm_crtc *crtc)
{ {
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct exynos_drm_manager *manager = exynos_crtc->manager;
exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON); exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
exynos_plane_commit(exynos_crtc->plane); exynos_plane_commit(exynos_crtc->plane);
if (manager->ops->commit)
manager->ops->commit(manager);
exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_ON); exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_ON);
} }
...@@ -94,7 +105,12 @@ exynos_drm_crtc_mode_fixup(struct drm_crtc *crtc, ...@@ -94,7 +105,12 @@ exynos_drm_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode, const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode) struct drm_display_mode *adjusted_mode)
{ {
/* drm framework doesn't check NULL */ struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct exynos_drm_manager *manager = exynos_crtc->manager;
if (manager->ops->mode_fixup)
return manager->ops->mode_fixup(manager, mode, adjusted_mode);
return true; return true;
} }
...@@ -104,10 +120,10 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, ...@@ -104,10 +120,10 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
struct drm_framebuffer *old_fb) struct drm_framebuffer *old_fb)
{ {
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct exynos_drm_manager *manager = exynos_crtc->manager;
struct drm_plane *plane = exynos_crtc->plane; struct drm_plane *plane = exynos_crtc->plane;
unsigned int crtc_w; unsigned int crtc_w;
unsigned int crtc_h; unsigned int crtc_h;
int pipe = exynos_crtc->pipe;
int ret; int ret;
/* /*
...@@ -119,6 +135,9 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, ...@@ -119,6 +135,9 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
crtc_w = crtc->fb->width - x; crtc_w = crtc->fb->width - x;
crtc_h = crtc->fb->height - y; crtc_h = crtc->fb->height - y;
if (manager->ops->mode_set)
manager->ops->mode_set(manager, &crtc->mode);
ret = exynos_plane_mode_set(plane, crtc, crtc->fb, 0, 0, crtc_w, crtc_h, ret = exynos_plane_mode_set(plane, crtc, crtc->fb, 0, 0, crtc_w, crtc_h,
x, y, crtc_w, crtc_h); x, y, crtc_w, crtc_h);
if (ret) if (ret)
...@@ -127,8 +146,6 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, ...@@ -127,8 +146,6 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
plane->crtc = crtc; plane->crtc = crtc;
plane->fb = crtc->fb; plane->fb = crtc->fb;
exynos_drm_fn_encoder(crtc, &pipe, exynos_drm_encoder_crtc_pipe);
return 0; return 0;
} }
...@@ -168,10 +185,19 @@ static int exynos_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, ...@@ -168,10 +185,19 @@ static int exynos_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
static void exynos_drm_crtc_disable(struct drm_crtc *crtc) static void exynos_drm_crtc_disable(struct drm_crtc *crtc)
{ {
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); struct drm_plane *plane;
int ret;
exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_OFF);
exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
list_for_each_entry(plane, &crtc->dev->mode_config.plane_list, head) {
if (plane->crtc != crtc)
continue;
ret = plane->funcs->disable_plane(plane);
if (ret)
DRM_ERROR("Failed to disable plane %d\n", ret);
}
} }
static struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = { static struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = {
...@@ -318,21 +344,24 @@ static void exynos_drm_crtc_attach_mode_property(struct drm_crtc *crtc) ...@@ -318,21 +344,24 @@ static void exynos_drm_crtc_attach_mode_property(struct drm_crtc *crtc)
drm_object_attach_property(&crtc->base, prop, 0); drm_object_attach_property(&crtc->base, prop, 0);
} }
int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr) int exynos_drm_crtc_create(struct exynos_drm_manager *manager)
{ {
struct exynos_drm_crtc *exynos_crtc; struct exynos_drm_crtc *exynos_crtc;
struct exynos_drm_private *private = dev->dev_private; struct exynos_drm_private *private = manager->drm_dev->dev_private;
struct drm_crtc *crtc; struct drm_crtc *crtc;
exynos_crtc = kzalloc(sizeof(*exynos_crtc), GFP_KERNEL); exynos_crtc = kzalloc(sizeof(*exynos_crtc), GFP_KERNEL);
if (!exynos_crtc) if (!exynos_crtc)
return -ENOMEM; return -ENOMEM;
exynos_crtc->pipe = nr;
exynos_crtc->dpms = DRM_MODE_DPMS_OFF;
init_waitqueue_head(&exynos_crtc->pending_flip_queue); init_waitqueue_head(&exynos_crtc->pending_flip_queue);
atomic_set(&exynos_crtc->pending_flip, 0); atomic_set(&exynos_crtc->pending_flip, 0);
exynos_crtc->plane = exynos_plane_init(dev, 1 << nr, true);
exynos_crtc->dpms = DRM_MODE_DPMS_OFF;
exynos_crtc->manager = manager;
exynos_crtc->pipe = manager->pipe;
exynos_crtc->plane = exynos_plane_init(manager->drm_dev,
1 << manager->pipe, true);
if (!exynos_crtc->plane) { if (!exynos_crtc->plane) {
kfree(exynos_crtc); kfree(exynos_crtc);
return -ENOMEM; return -ENOMEM;
...@@ -340,9 +369,9 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr) ...@@ -340,9 +369,9 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr)
crtc = &exynos_crtc->drm_crtc; crtc = &exynos_crtc->drm_crtc;
private->crtc[nr] = crtc; private->crtc[manager->pipe] = crtc;
drm_crtc_init(dev, crtc, &exynos_crtc_funcs); drm_crtc_init(manager->drm_dev, crtc, &exynos_crtc_funcs);
drm_crtc_helper_add(crtc, &exynos_crtc_helper_funcs); drm_crtc_helper_add(crtc, &exynos_crtc_helper_funcs);
exynos_drm_crtc_attach_mode_property(crtc); exynos_drm_crtc_attach_mode_property(crtc);
...@@ -350,39 +379,41 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr) ...@@ -350,39 +379,41 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr)
return 0; return 0;
} }
int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc) int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int pipe)
{ {
struct exynos_drm_private *private = dev->dev_private; struct exynos_drm_private *private = dev->dev_private;
struct exynos_drm_crtc *exynos_crtc = struct exynos_drm_crtc *exynos_crtc =
to_exynos_crtc(private->crtc[crtc]); to_exynos_crtc(private->crtc[pipe]);
struct exynos_drm_manager *manager = exynos_crtc->manager;
if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) if (exynos_crtc->dpms != DRM_MODE_DPMS_ON)
return -EPERM; return -EPERM;
exynos_drm_fn_encoder(private->crtc[crtc], &crtc, if (manager->ops->enable_vblank)
exynos_drm_enable_vblank); manager->ops->enable_vblank(manager);
return 0; return 0;
} }
void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc) void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int pipe)
{ {
struct exynos_drm_private *private = dev->dev_private; struct exynos_drm_private *private = dev->dev_private;
struct exynos_drm_crtc *exynos_crtc = struct exynos_drm_crtc *exynos_crtc =
to_exynos_crtc(private->crtc[crtc]); to_exynos_crtc(private->crtc[pipe]);
struct exynos_drm_manager *manager = exynos_crtc->manager;
if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) if (exynos_crtc->dpms != DRM_MODE_DPMS_ON)
return; return;
exynos_drm_fn_encoder(private->crtc[crtc], &crtc, if (manager->ops->disable_vblank)
exynos_drm_disable_vblank); manager->ops->disable_vblank(manager);
} }
void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc) void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe)
{ {
struct exynos_drm_private *dev_priv = dev->dev_private; struct exynos_drm_private *dev_priv = dev->dev_private;
struct drm_pending_vblank_event *e, *t; struct drm_pending_vblank_event *e, *t;
struct drm_crtc *drm_crtc = dev_priv->crtc[crtc]; struct drm_crtc *drm_crtc = dev_priv->crtc[pipe];
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(drm_crtc); struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(drm_crtc);
unsigned long flags; unsigned long flags;
...@@ -391,15 +422,71 @@ void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc) ...@@ -391,15 +422,71 @@ void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc)
list_for_each_entry_safe(e, t, &dev_priv->pageflip_event_list, list_for_each_entry_safe(e, t, &dev_priv->pageflip_event_list,
base.link) { base.link) {
/* if event's pipe isn't same as crtc then ignore it. */ /* if event's pipe isn't same as crtc then ignore it. */
if (crtc != e->pipe) if (pipe != e->pipe)
continue; continue;
list_del(&e->base.link); list_del(&e->base.link);
drm_send_vblank_event(dev, -1, e); drm_send_vblank_event(dev, -1, e);
drm_vblank_put(dev, crtc); drm_vblank_put(dev, pipe);
atomic_set(&exynos_crtc->pending_flip, 0); atomic_set(&exynos_crtc->pending_flip, 0);
wake_up(&exynos_crtc->pending_flip_queue); wake_up(&exynos_crtc->pending_flip_queue);
} }
spin_unlock_irqrestore(&dev->event_lock, flags); spin_unlock_irqrestore(&dev->event_lock, flags);
} }
void exynos_drm_crtc_plane_mode_set(struct drm_crtc *crtc,
struct exynos_drm_overlay *overlay)
{
struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager;
if (manager->ops->win_mode_set)
manager->ops->win_mode_set(manager, overlay);
}
void exynos_drm_crtc_plane_commit(struct drm_crtc *crtc, int zpos)
{
struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager;
if (manager->ops->win_commit)
manager->ops->win_commit(manager, zpos);
}
void exynos_drm_crtc_plane_enable(struct drm_crtc *crtc, int zpos)
{
struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager;
if (manager->ops->win_enable)
manager->ops->win_enable(manager, zpos);
}
void exynos_drm_crtc_plane_disable(struct drm_crtc *crtc, int zpos)
{
struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager;
if (manager->ops->win_disable)
manager->ops->win_disable(manager, zpos);
}
void exynos_drm_crtc_complete_scanout(struct drm_framebuffer *fb)
{
struct exynos_drm_manager *manager;
struct drm_device *dev = fb->dev;
struct drm_crtc *crtc;
/*
* make sure that overlay data are updated to real hardware
* for all encoders.
*/
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
manager = to_exynos_crtc(crtc)->manager;
/*
* wait for vblank interrupt
* - this makes sure that overlay data are updated to
* real hardware.
*/
if (manager->ops->wait_for_vblank)
manager->ops->wait_for_vblank(manager);
}
}
...@@ -15,9 +15,21 @@ ...@@ -15,9 +15,21 @@
#ifndef _EXYNOS_DRM_CRTC_H_ #ifndef _EXYNOS_DRM_CRTC_H_
#define _EXYNOS_DRM_CRTC_H_ #define _EXYNOS_DRM_CRTC_H_
int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr); struct drm_device;
int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc); struct drm_crtc;
void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc); struct exynos_drm_manager;
void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc); struct exynos_drm_overlay;
int exynos_drm_crtc_create(struct exynos_drm_manager *manager);
int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int pipe);
void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int pipe);
void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe);
void exynos_drm_crtc_complete_scanout(struct drm_framebuffer *fb);
void exynos_drm_crtc_plane_mode_set(struct drm_crtc *crtc,
struct exynos_drm_overlay *overlay);
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);
#endif #endif
/*
* Exynos DRM Parallel output support.
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd
*
* Contacts: Andrzej Hajda <a.hajda@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_crtc_helper.h>
#include <drm/drm_panel.h>
#include <linux/regulator/consumer.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
#include "exynos_drm_drv.h"
struct exynos_dpi {
struct device *dev;
struct device_node *panel_node;
struct drm_panel *panel;
struct drm_connector connector;
struct drm_encoder *encoder;
struct videomode *vm;
int dpms_mode;
};
#define connector_to_dpi(c) container_of(c, struct exynos_dpi, connector)
static enum drm_connector_status
exynos_dpi_detect(struct drm_connector *connector, bool force)
{
struct exynos_dpi *ctx = connector_to_dpi(connector);
/* panels supported only by boot-loader are always connected */
if (!ctx->panel_node)
return connector_status_connected;
if (!ctx->panel) {
ctx->panel = of_drm_find_panel(ctx->panel_node);
if (ctx->panel)
drm_panel_attach(ctx->panel, &ctx->connector);
}
if (ctx->panel)
return connector_status_connected;
return connector_status_disconnected;
}
static void exynos_dpi_connector_destroy(struct drm_connector *connector)
{
drm_sysfs_connector_remove(connector);
drm_connector_cleanup(connector);
}
static struct drm_connector_funcs exynos_dpi_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = exynos_dpi_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = exynos_dpi_connector_destroy,
};
static int exynos_dpi_get_modes(struct drm_connector *connector)
{
struct exynos_dpi *ctx = connector_to_dpi(connector);
/* fimd timings gets precedence over panel modes */
if (ctx->vm) {
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_videomode(ctx->vm, mode);
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
return 1;
}
if (ctx->panel)
return ctx->panel->funcs->get_modes(ctx->panel);
return 0;
}
static int exynos_dpi_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static struct drm_encoder *
exynos_dpi_best_encoder(struct drm_connector *connector)
{
struct exynos_dpi *ctx = connector_to_dpi(connector);
return ctx->encoder;
}
static struct drm_connector_helper_funcs exynos_dpi_connector_helper_funcs = {
.get_modes = exynos_dpi_get_modes,
.mode_valid = exynos_dpi_mode_valid,
.best_encoder = exynos_dpi_best_encoder,
};
static int exynos_dpi_create_connector(struct exynos_drm_display *display,
struct drm_encoder *encoder)
{
struct exynos_dpi *ctx = display->ctx;
struct drm_connector *connector = &ctx->connector;
int ret;
ctx->encoder = encoder;
if (ctx->panel_node)
connector->polled = DRM_CONNECTOR_POLL_CONNECT;
else
connector->polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(encoder->dev, connector,
&exynos_dpi_connector_funcs,
DRM_MODE_CONNECTOR_VGA);
if (ret) {
DRM_ERROR("failed to initialize connector with drm\n");
return ret;
}
drm_connector_helper_add(connector, &exynos_dpi_connector_helper_funcs);
drm_sysfs_connector_add(connector);
drm_mode_connector_attach_encoder(connector, encoder);
return 0;
}
static void exynos_dpi_poweron(struct exynos_dpi *ctx)
{
if (ctx->panel)
drm_panel_enable(ctx->panel);
}
static void exynos_dpi_poweroff(struct exynos_dpi *ctx)
{
if (ctx->panel)
drm_panel_disable(ctx->panel);
}
static void exynos_dpi_dpms(struct exynos_drm_display *display, int mode)
{
struct exynos_dpi *ctx = display->ctx;
switch (mode) {
case DRM_MODE_DPMS_ON:
if (ctx->dpms_mode != DRM_MODE_DPMS_ON)
exynos_dpi_poweron(ctx);
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
if (ctx->dpms_mode == DRM_MODE_DPMS_ON)
exynos_dpi_poweroff(ctx);
break;
default:
break;
};
ctx->dpms_mode = mode;
}
static struct exynos_drm_display_ops exynos_dpi_display_ops = {
.create_connector = exynos_dpi_create_connector,
.dpms = exynos_dpi_dpms
};
static struct exynos_drm_display exynos_dpi_display = {
.type = EXYNOS_DISPLAY_TYPE_LCD,
.ops = &exynos_dpi_display_ops,
};
/* of_* functions will be removed after merge of of_graph patches */
static struct device_node *
of_get_child_by_name_reg(struct device_node *parent, const char *name, u32 reg)
{
struct device_node *np;
for_each_child_of_node(parent, np) {
u32 r;
if (!np->name || of_node_cmp(np->name, name))
continue;
if (of_property_read_u32(np, "reg", &r) < 0)
r = 0;
if (reg == r)
break;
}
return np;
}
static struct device_node *of_graph_get_port_by_reg(struct device_node *parent,
u32 reg)
{
struct device_node *ports, *port;
ports = of_get_child_by_name(parent, "ports");
if (ports)
parent = ports;
port = of_get_child_by_name_reg(parent, "port", reg);
of_node_put(ports);
return port;
}
static struct device_node *
of_graph_get_endpoint_by_reg(struct device_node *port, u32 reg)
{
return of_get_child_by_name_reg(port, "endpoint", reg);
}
static struct device_node *
of_graph_get_remote_port_parent(const struct device_node *node)
{
struct device_node *np;
unsigned int depth;
np = of_parse_phandle(node, "remote-endpoint", 0);
/* Walk 3 levels up only if there is 'ports' node. */
for (depth = 3; depth && np; depth--) {
np = of_get_next_parent(np);
if (depth == 2 && of_node_cmp(np->name, "ports"))
break;
}
return np;
}
enum {
FIMD_PORT_IN0,
FIMD_PORT_IN1,
FIMD_PORT_IN2,
FIMD_PORT_RGB,
FIMD_PORT_WRB,
};
static struct device_node *exynos_dpi_of_find_panel_node(struct device *dev)
{
struct device_node *np, *ep;
np = of_graph_get_port_by_reg(dev->of_node, FIMD_PORT_RGB);
if (!np)
return NULL;
ep = of_graph_get_endpoint_by_reg(np, 0);
of_node_put(np);
if (!ep)
return NULL;
np = of_graph_get_remote_port_parent(ep);
of_node_put(ep);
return np;
}
static int exynos_dpi_parse_dt(struct exynos_dpi *ctx)
{
struct device *dev = ctx->dev;
struct device_node *dn = dev->of_node;
struct device_node *np;
ctx->panel_node = exynos_dpi_of_find_panel_node(dev);
np = of_get_child_by_name(dn, "display-timings");
if (np) {
struct videomode *vm;
int ret;
of_node_put(np);
vm = devm_kzalloc(dev, sizeof(*ctx->vm), GFP_KERNEL);
if (!vm)
return -ENOMEM;
ret = of_get_videomode(dn, vm, 0);
if (ret < 0)
return ret;
ctx->vm = vm;
return 0;
}
if (!ctx->panel_node)
return -EINVAL;
return 0;
}
int exynos_dpi_probe(struct device *dev)
{
struct exynos_dpi *ctx;
int ret;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->dev = dev;
exynos_dpi_display.ctx = ctx;
ctx->dpms_mode = DRM_MODE_DPMS_OFF;
ret = exynos_dpi_parse_dt(ctx);
if (ret < 0)
return ret;
exynos_drm_display_register(&exynos_dpi_display);
return 0;
}
int exynos_dpi_remove(struct device *dev)
{
exynos_dpi_dpms(&exynos_dpi_display, DRM_MODE_DPMS_OFF);
exynos_drm_display_unregister(&exynos_dpi_display);
return 0;
}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
* option) any later version. * option) any later version.
*/ */
#include <linux/pm_runtime.h>
#include <drm/drmP.h> #include <drm/drmP.h>
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
...@@ -53,6 +54,7 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags) ...@@ -53,6 +54,7 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
return -ENOMEM; return -ENOMEM;
INIT_LIST_HEAD(&private->pageflip_event_list); INIT_LIST_HEAD(&private->pageflip_event_list);
dev_set_drvdata(dev->dev, dev);
dev->dev_private = (void *)private; dev->dev_private = (void *)private;
/* /*
...@@ -64,38 +66,36 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags) ...@@ -64,38 +66,36 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
ret = drm_create_iommu_mapping(dev); ret = drm_create_iommu_mapping(dev);
if (ret < 0) { if (ret < 0) {
DRM_ERROR("failed to create iommu mapping.\n"); DRM_ERROR("failed to create iommu mapping.\n");
goto err_crtc; goto err_free_private;
} }
drm_mode_config_init(dev); drm_mode_config_init(dev);
/* init kms poll for handling hpd */
drm_kms_helper_poll_init(dev);
exynos_drm_mode_config_init(dev); exynos_drm_mode_config_init(dev);
/* ret = exynos_drm_initialize_managers(dev);
* EXYNOS4 is enough to have two CRTCs and each crtc would be used
* without dependency of hardware.
*/
for (nr = 0; nr < MAX_CRTC; nr++) {
ret = exynos_drm_crtc_create(dev, nr);
if (ret) if (ret)
goto err_release_iommu_mapping; goto err_mode_config_cleanup;
}
for (nr = 0; nr < MAX_PLANE; nr++) { for (nr = 0; nr < MAX_PLANE; nr++) {
struct drm_plane *plane; struct drm_plane *plane;
unsigned int possible_crtcs = (1 << MAX_CRTC) - 1; unsigned long possible_crtcs = (1 << MAX_CRTC) - 1;
plane = exynos_plane_init(dev, possible_crtcs, false); plane = exynos_plane_init(dev, possible_crtcs, false);
if (!plane) if (!plane)
goto err_release_iommu_mapping; goto err_manager_cleanup;
} }
ret = exynos_drm_initialize_displays(dev);
if (ret)
goto err_manager_cleanup;
/* init kms poll for handling hpd */
drm_kms_helper_poll_init(dev);
ret = drm_vblank_init(dev, MAX_CRTC); ret = drm_vblank_init(dev, MAX_CRTC);
if (ret) if (ret)
goto err_release_iommu_mapping; goto err_display_cleanup;
/* /*
* probe sub drivers such as display controller and hdmi driver, * probe sub drivers such as display controller and hdmi driver,
...@@ -109,30 +109,25 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags) ...@@ -109,30 +109,25 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
/* setup possible_clones. */ /* setup possible_clones. */
exynos_drm_encoder_setup(dev); exynos_drm_encoder_setup(dev);
/*
* create and configure fb helper and also exynos specific
* fbdev object.
*/
ret = exynos_drm_fbdev_init(dev);
if (ret) {
DRM_ERROR("failed to initialize drm fbdev\n");
goto err_drm_device;
}
drm_vblank_offdelay = VBLANK_OFF_DELAY; drm_vblank_offdelay = VBLANK_OFF_DELAY;
platform_set_drvdata(dev->platformdev, dev); platform_set_drvdata(dev->platformdev, dev);
/* force connectors detection */
drm_helper_hpd_irq_event(dev);
return 0; return 0;
err_drm_device:
exynos_drm_device_unregister(dev);
err_vblank: err_vblank:
drm_vblank_cleanup(dev); drm_vblank_cleanup(dev);
err_release_iommu_mapping: err_display_cleanup:
drm_release_iommu_mapping(dev); exynos_drm_remove_displays(dev);
err_crtc: err_manager_cleanup:
exynos_drm_remove_managers(dev);
err_mode_config_cleanup:
drm_mode_config_cleanup(dev); drm_mode_config_cleanup(dev);
drm_release_iommu_mapping(dev);
err_free_private:
kfree(private); kfree(private);
return ret; return ret;
...@@ -144,6 +139,8 @@ static int exynos_drm_unload(struct drm_device *dev) ...@@ -144,6 +139,8 @@ static int exynos_drm_unload(struct drm_device *dev)
exynos_drm_device_unregister(dev); exynos_drm_device_unregister(dev);
drm_vblank_cleanup(dev); drm_vblank_cleanup(dev);
drm_kms_helper_poll_fini(dev); drm_kms_helper_poll_fini(dev);
exynos_drm_remove_displays(dev);
exynos_drm_remove_managers(dev);
drm_mode_config_cleanup(dev); drm_mode_config_cleanup(dev);
drm_release_iommu_mapping(dev); drm_release_iommu_mapping(dev);
...@@ -158,6 +155,41 @@ static const struct file_operations exynos_drm_gem_fops = { ...@@ -158,6 +155,41 @@ static const struct file_operations exynos_drm_gem_fops = {
.mmap = exynos_drm_gem_mmap_buffer, .mmap = exynos_drm_gem_mmap_buffer,
}; };
static int exynos_drm_suspend(struct drm_device *dev, pm_message_t state)
{
struct drm_connector *connector;
drm_modeset_lock_all(dev);
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
int old_dpms = connector->dpms;
if (connector->funcs->dpms)
connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF);
/* Set the old mode back to the connector for resume */
connector->dpms = old_dpms;
}
drm_modeset_unlock_all(dev);
return 0;
}
static int exynos_drm_resume(struct drm_device *dev)
{
struct drm_connector *connector;
drm_modeset_lock_all(dev);
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->funcs->dpms)
connector->funcs->dpms(connector, connector->dpms);
}
drm_helper_resume_force_mode(dev);
drm_modeset_unlock_all(dev);
return 0;
}
static int exynos_drm_open(struct drm_device *dev, struct drm_file *file) static int exynos_drm_open(struct drm_device *dev, struct drm_file *file)
{ {
struct drm_exynos_file_private *file_priv; struct drm_exynos_file_private *file_priv;
...@@ -172,20 +204,24 @@ static int exynos_drm_open(struct drm_device *dev, struct drm_file *file) ...@@ -172,20 +204,24 @@ static int exynos_drm_open(struct drm_device *dev, struct drm_file *file)
ret = exynos_drm_subdrv_open(dev, file); ret = exynos_drm_subdrv_open(dev, file);
if (ret) if (ret)
goto out; goto err_file_priv_free;
anon_filp = anon_inode_getfile("exynos_gem", &exynos_drm_gem_fops, anon_filp = anon_inode_getfile("exynos_gem", &exynos_drm_gem_fops,
NULL, 0); NULL, 0);
if (IS_ERR(anon_filp)) { if (IS_ERR(anon_filp)) {
ret = PTR_ERR(anon_filp); ret = PTR_ERR(anon_filp);
goto out; goto err_subdrv_close;
} }
anon_filp->f_mode = FMODE_READ | FMODE_WRITE; anon_filp->f_mode = FMODE_READ | FMODE_WRITE;
file_priv->anon_filp = anon_filp; file_priv->anon_filp = anon_filp;
return ret; return ret;
out:
err_subdrv_close:
exynos_drm_subdrv_close(dev, file);
err_file_priv_free:
kfree(file_priv); kfree(file_priv);
file->driver_priv = NULL; file->driver_priv = NULL;
return ret; return ret;
...@@ -291,6 +327,8 @@ static struct drm_driver exynos_drm_driver = { ...@@ -291,6 +327,8 @@ static struct drm_driver exynos_drm_driver = {
DRIVER_GEM | DRIVER_PRIME, DRIVER_GEM | DRIVER_PRIME,
.load = exynos_drm_load, .load = exynos_drm_load,
.unload = exynos_drm_unload, .unload = exynos_drm_unload,
.suspend = exynos_drm_suspend,
.resume = exynos_drm_resume,
.open = exynos_drm_open, .open = exynos_drm_open,
.preclose = exynos_drm_preclose, .preclose = exynos_drm_preclose,
.lastclose = exynos_drm_lastclose, .lastclose = exynos_drm_lastclose,
...@@ -325,6 +363,9 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) ...@@ -325,6 +363,9 @@ static int exynos_drm_platform_probe(struct platform_device *pdev)
if (ret) if (ret)
return ret; return ret;
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
return drm_platform_init(&exynos_drm_driver, pdev); return drm_platform_init(&exynos_drm_driver, pdev);
} }
...@@ -335,12 +376,67 @@ static int exynos_drm_platform_remove(struct platform_device *pdev) ...@@ -335,12 +376,67 @@ static int exynos_drm_platform_remove(struct platform_device *pdev)
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP
static int exynos_drm_sys_suspend(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
pm_message_t message;
if (pm_runtime_suspended(dev))
return 0;
message.event = PM_EVENT_SUSPEND;
return exynos_drm_suspend(drm_dev, message);
}
static int exynos_drm_sys_resume(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
if (pm_runtime_suspended(dev))
return 0;
return exynos_drm_resume(drm_dev);
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int exynos_drm_runtime_suspend(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
pm_message_t message;
if (pm_runtime_suspended(dev))
return 0;
message.event = PM_EVENT_SUSPEND;
return exynos_drm_suspend(drm_dev, message);
}
static int exynos_drm_runtime_resume(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
if (!pm_runtime_suspended(dev))
return 0;
return exynos_drm_resume(drm_dev);
}
#endif
static const struct dev_pm_ops exynos_drm_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(exynos_drm_sys_suspend, exynos_drm_sys_resume)
SET_RUNTIME_PM_OPS(exynos_drm_runtime_suspend,
exynos_drm_runtime_resume, NULL)
};
static struct platform_driver exynos_drm_platform_driver = { static struct platform_driver exynos_drm_platform_driver = {
.probe = exynos_drm_platform_probe, .probe = exynos_drm_platform_probe,
.remove = exynos_drm_platform_remove, .remove = exynos_drm_platform_remove,
.driver = { .driver = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.name = "exynos-drm", .name = "exynos-drm",
.pm = &exynos_drm_pm_ops,
}, },
}; };
...@@ -348,6 +444,12 @@ static int __init exynos_drm_init(void) ...@@ -348,6 +444,12 @@ static int __init exynos_drm_init(void)
{ {
int ret; int ret;
#ifdef CONFIG_DRM_EXYNOS_DP
ret = platform_driver_register(&dp_driver);
if (ret < 0)
goto out_dp;
#endif
#ifdef CONFIG_DRM_EXYNOS_FIMD #ifdef CONFIG_DRM_EXYNOS_FIMD
ret = platform_driver_register(&fimd_driver); ret = platform_driver_register(&fimd_driver);
if (ret < 0) if (ret < 0)
...@@ -361,13 +463,6 @@ static int __init exynos_drm_init(void) ...@@ -361,13 +463,6 @@ static int __init exynos_drm_init(void)
ret = platform_driver_register(&mixer_driver); ret = platform_driver_register(&mixer_driver);
if (ret < 0) if (ret < 0)
goto out_mixer; goto out_mixer;
ret = platform_driver_register(&exynos_drm_common_hdmi_driver);
if (ret < 0)
goto out_common_hdmi;
ret = exynos_platform_device_hdmi_register();
if (ret < 0)
goto out_common_hdmi_dev;
#endif #endif
#ifdef CONFIG_DRM_EXYNOS_VIDI #ifdef CONFIG_DRM_EXYNOS_VIDI
...@@ -460,10 +555,6 @@ static int __init exynos_drm_init(void) ...@@ -460,10 +555,6 @@ static int __init exynos_drm_init(void)
#endif #endif
#ifdef CONFIG_DRM_EXYNOS_HDMI #ifdef CONFIG_DRM_EXYNOS_HDMI
exynos_platform_device_hdmi_unregister();
out_common_hdmi_dev:
platform_driver_unregister(&exynos_drm_common_hdmi_driver);
out_common_hdmi:
platform_driver_unregister(&mixer_driver); platform_driver_unregister(&mixer_driver);
out_mixer: out_mixer:
platform_driver_unregister(&hdmi_driver); platform_driver_unregister(&hdmi_driver);
...@@ -473,6 +564,11 @@ static int __init exynos_drm_init(void) ...@@ -473,6 +564,11 @@ static int __init exynos_drm_init(void)
#ifdef CONFIG_DRM_EXYNOS_FIMD #ifdef CONFIG_DRM_EXYNOS_FIMD
platform_driver_unregister(&fimd_driver); platform_driver_unregister(&fimd_driver);
out_fimd: out_fimd:
#endif
#ifdef CONFIG_DRM_EXYNOS_DP
platform_driver_unregister(&dp_driver);
out_dp:
#endif #endif
return ret; return ret;
} }
...@@ -505,8 +601,6 @@ static void __exit exynos_drm_exit(void) ...@@ -505,8 +601,6 @@ static void __exit exynos_drm_exit(void)
#endif #endif
#ifdef CONFIG_DRM_EXYNOS_HDMI #ifdef CONFIG_DRM_EXYNOS_HDMI
exynos_platform_device_hdmi_unregister();
platform_driver_unregister(&exynos_drm_common_hdmi_driver);
platform_driver_unregister(&mixer_driver); platform_driver_unregister(&mixer_driver);
platform_driver_unregister(&hdmi_driver); platform_driver_unregister(&hdmi_driver);
#endif #endif
...@@ -518,6 +612,10 @@ static void __exit exynos_drm_exit(void) ...@@ -518,6 +612,10 @@ static void __exit exynos_drm_exit(void)
#ifdef CONFIG_DRM_EXYNOS_FIMD #ifdef CONFIG_DRM_EXYNOS_FIMD
platform_driver_unregister(&fimd_driver); platform_driver_unregister(&fimd_driver);
#endif #endif
#ifdef CONFIG_DRM_EXYNOS_DP
platform_driver_unregister(&dp_driver);
#endif
} }
module_init(exynos_drm_init); module_init(exynos_drm_init);
......
...@@ -53,22 +53,6 @@ enum exynos_drm_output_type { ...@@ -53,22 +53,6 @@ enum exynos_drm_output_type {
EXYNOS_DISPLAY_TYPE_VIDI, EXYNOS_DISPLAY_TYPE_VIDI,
}; };
/*
* Exynos drm overlay ops structure.
*
* @mode_set: copy drm overlay info to hw specific overlay info.
* @commit: apply hardware specific overlay data to registers.
* @enable: enable hardware specific overlay.
* @disable: disable hardware specific overlay.
*/
struct exynos_drm_overlay_ops {
void (*mode_set)(struct device *subdrv_dev,
struct exynos_drm_overlay *overlay);
void (*commit)(struct device *subdrv_dev, int zpos);
void (*enable)(struct device *subdrv_dev, int zpos);
void (*disable)(struct device *subdrv_dev, int zpos);
};
/* /*
* Exynos drm common overlay structure. * Exynos drm common overlay structure.
* *
...@@ -138,77 +122,110 @@ struct exynos_drm_overlay { ...@@ -138,77 +122,110 @@ struct exynos_drm_overlay {
* Exynos DRM Display Structure. * Exynos DRM Display Structure.
* - this structure is common to analog tv, digital tv and lcd panel. * - this structure is common to analog tv, digital tv and lcd panel.
* *
* @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI. * @initialize: initializes the display with drm_dev
* @is_connected: check for that display is connected or not. * @remove: cleans up the display for removal
* @get_edid: get edid modes from display driver. * @mode_fixup: fix mode data comparing to hw specific display mode.
* @get_panel: get panel object from display driver. * @mode_set: convert drm_display_mode to hw specific display mode and
* would be called by encoder->mode_set().
* @check_mode: check if mode is valid or not. * @check_mode: check if mode is valid or not.
* @power_on: display device on or off. * @dpms: display device on or off.
* @commit: apply changes to hw
*/ */
struct exynos_drm_display;
struct exynos_drm_display_ops { struct exynos_drm_display_ops {
int (*initialize)(struct exynos_drm_display *display,
struct drm_device *drm_dev);
int (*create_connector)(struct exynos_drm_display *display,
struct drm_encoder *encoder);
void (*remove)(struct exynos_drm_display *display);
void (*mode_fixup)(struct exynos_drm_display *display,
struct drm_connector *connector,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
void (*mode_set)(struct exynos_drm_display *display,
struct drm_display_mode *mode);
int (*check_mode)(struct exynos_drm_display *display,
struct drm_display_mode *mode);
void (*dpms)(struct exynos_drm_display *display, int mode);
void (*commit)(struct exynos_drm_display *display);
};
/*
* Exynos drm display structure, maps 1:1 with an encoder/connector
*
* @list: the list entry for this manager
* @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI.
* @encoder: encoder object this display maps to
* @connector: connector object this display maps to
* @ops: pointer to callbacks for exynos drm specific functionality
* @ctx: A pointer to the display's implementation specific context
*/
struct exynos_drm_display {
struct list_head list;
enum exynos_drm_output_type type; enum exynos_drm_output_type type;
bool (*is_connected)(struct device *dev); struct drm_encoder *encoder;
struct edid *(*get_edid)(struct device *dev, struct drm_connector *connector;
struct drm_connector *connector); struct exynos_drm_display_ops *ops;
void *(*get_panel)(struct device *dev); void *ctx;
int (*check_mode)(struct device *dev, struct drm_display_mode *mode);
int (*power_on)(struct device *dev, int mode);
}; };
/* /*
* Exynos drm manager ops * Exynos drm manager ops
* *
* @initialize: initializes the manager with drm_dev
* @remove: cleans up the manager for removal
* @dpms: control device power. * @dpms: control device power.
* @apply: set timing, vblank and overlay data to registers. * @mode_fixup: fix mode data before applying it
* @mode_fixup: fix mode data comparing to hw specific display mode. * @mode_set: set the given mode to the manager
* @mode_set: convert drm_display_mode to hw specific display mode and
* would be called by encoder->mode_set().
* @get_max_resol: get maximum resolution to specific hardware.
* @commit: set current hw specific display mode to hw. * @commit: set current hw specific display mode to hw.
* @enable_vblank: specific driver callback for enabling vblank interrupt. * @enable_vblank: specific driver callback for enabling vblank interrupt.
* @disable_vblank: specific driver callback for disabling vblank interrupt. * @disable_vblank: specific driver callback for disabling vblank interrupt.
* @wait_for_vblank: wait for vblank interrupt to make sure that * @wait_for_vblank: wait for vblank interrupt to make sure that
* hardware overlay is updated. * hardware overlay is updated.
* @win_mode_set: copy drm overlay info to hw specific overlay info.
* @win_commit: apply hardware specific overlay data to registers.
* @win_enable: enable hardware specific overlay.
* @win_disable: disable hardware specific overlay.
*/ */
struct exynos_drm_manager;
struct exynos_drm_manager_ops { struct exynos_drm_manager_ops {
void (*dpms)(struct device *subdrv_dev, int mode); int (*initialize)(struct exynos_drm_manager *mgr,
void (*apply)(struct device *subdrv_dev); struct drm_device *drm_dev, int pipe);
void (*mode_fixup)(struct device *subdrv_dev, void (*remove)(struct exynos_drm_manager *mgr);
struct drm_connector *connector, void (*dpms)(struct exynos_drm_manager *mgr, int mode);
bool (*mode_fixup)(struct exynos_drm_manager *mgr,
const struct drm_display_mode *mode, const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode); struct drm_display_mode *adjusted_mode);
void (*mode_set)(struct device *subdrv_dev, void *mode); void (*mode_set)(struct exynos_drm_manager *mgr,
void (*get_max_resol)(struct device *subdrv_dev, unsigned int *width, const struct drm_display_mode *mode);
unsigned int *height); void (*commit)(struct exynos_drm_manager *mgr);
void (*commit)(struct device *subdrv_dev); int (*enable_vblank)(struct exynos_drm_manager *mgr);
int (*enable_vblank)(struct device *subdrv_dev); void (*disable_vblank)(struct exynos_drm_manager *mgr);
void (*disable_vblank)(struct device *subdrv_dev); void (*wait_for_vblank)(struct exynos_drm_manager *mgr);
void (*wait_for_vblank)(struct device *subdrv_dev); void (*win_mode_set)(struct exynos_drm_manager *mgr,
struct exynos_drm_overlay *overlay);
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);
}; };
/* /*
* Exynos drm common manager structure. * Exynos drm common manager structure, maps 1:1 with a crtc
* *
* @dev: pointer to device object for subdrv device driver. * @list: the list entry for this manager
* sub drivers such as display controller or hdmi driver, * @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI.
* have their own device object. * @drm_dev: pointer to the drm device
* @ops: pointer to callbacks for exynos drm specific framebuffer. * @pipe: the pipe number for this crtc/manager
* these callbacks should be set by specific drivers such fimd * @ops: pointer to callbacks for exynos drm specific functionality
* or hdmi driver and are used to control hardware global registers. * @ctx: A pointer to the manager's implementation specific context
* @overlay_ops: pointer to callbacks for exynos drm specific framebuffer.
* these callbacks should be set by specific drivers such fimd
* or hdmi driver and are used to control hardware overlay reigsters.
* @display: pointer to callbacks for exynos drm specific framebuffer.
* these callbacks should be set by specific drivers such fimd
* or hdmi driver and are used to control display devices such as
* analog tv, digital tv and lcd panel and also get timing data for them.
*/ */
struct exynos_drm_manager { struct exynos_drm_manager {
struct device *dev; struct list_head list;
enum exynos_drm_output_type type;
struct drm_device *drm_dev;
int pipe; int pipe;
struct exynos_drm_manager_ops *ops; struct exynos_drm_manager_ops *ops;
struct exynos_drm_overlay_ops *overlay_ops; void *ctx;
struct exynos_drm_display_ops *display_ops;
}; };
struct exynos_drm_g2d_private { struct exynos_drm_g2d_private {
...@@ -273,14 +290,11 @@ struct exynos_drm_private { ...@@ -273,14 +290,11 @@ struct exynos_drm_private {
* by probe callback. * by probe callback.
* @open: this would be called with drm device file open. * @open: this would be called with drm device file open.
* @close: this would be called with drm device file close. * @close: this would be called with drm device file close.
* @encoder: encoder object owned by this sub driver.
* @connector: connector object owned by this sub driver.
*/ */
struct exynos_drm_subdrv { struct exynos_drm_subdrv {
struct list_head list; struct list_head list;
struct device *dev; struct device *dev;
struct drm_device *drm_dev; struct drm_device *drm_dev;
struct exynos_drm_manager *manager;
int (*probe)(struct drm_device *drm_dev, struct device *dev); int (*probe)(struct drm_device *drm_dev, struct device *dev);
void (*remove)(struct drm_device *drm_dev, struct device *dev); void (*remove)(struct drm_device *drm_dev, struct device *dev);
...@@ -288,9 +302,6 @@ struct exynos_drm_subdrv { ...@@ -288,9 +302,6 @@ struct exynos_drm_subdrv {
struct drm_file *file); struct drm_file *file);
void (*close)(struct drm_device *drm_dev, struct device *dev, void (*close)(struct drm_device *drm_dev, struct device *dev,
struct drm_file *file); struct drm_file *file);
struct drm_encoder *encoder;
struct drm_connector *connector;
}; };
/* /*
...@@ -305,6 +316,16 @@ int exynos_drm_device_register(struct drm_device *dev); ...@@ -305,6 +316,16 @@ int exynos_drm_device_register(struct drm_device *dev);
*/ */
int exynos_drm_device_unregister(struct drm_device *dev); int exynos_drm_device_unregister(struct drm_device *dev);
int exynos_drm_initialize_managers(struct drm_device *dev);
void exynos_drm_remove_managers(struct drm_device *dev);
int exynos_drm_initialize_displays(struct drm_device *dev);
void exynos_drm_remove_displays(struct drm_device *dev);
int exynos_drm_manager_register(struct exynos_drm_manager *manager);
int exynos_drm_manager_unregister(struct exynos_drm_manager *manager);
int exynos_drm_display_register(struct exynos_drm_display *display);
int exynos_drm_display_unregister(struct exynos_drm_display *display);
/* /*
* this function would be called by sub drivers such as display controller * this function would be called by sub drivers such as display controller
* or hdmi driver to register this sub driver object to exynos drm driver * or hdmi driver to register this sub driver object to exynos drm driver
...@@ -340,6 +361,15 @@ int exynos_platform_device_ipp_register(void); ...@@ -340,6 +361,15 @@ int exynos_platform_device_ipp_register(void);
*/ */
void exynos_platform_device_ipp_unregister(void); void exynos_platform_device_ipp_unregister(void);
#ifdef CONFIG_DRM_EXYNOS_DPI
int exynos_dpi_probe(struct device *dev);
int exynos_dpi_remove(struct device *dev);
#else
static inline int exynos_dpi_probe(struct device *dev) { return 0; }
static inline int exynos_dpi_remove(struct device *dev) { return 0; }
#endif
extern struct platform_driver dp_driver;
extern struct platform_driver fimd_driver; extern struct platform_driver fimd_driver;
extern struct platform_driver hdmi_driver; extern struct platform_driver hdmi_driver;
extern struct platform_driver mixer_driver; extern struct platform_driver mixer_driver;
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
#include "exynos_drm_drv.h" #include "exynos_drm_drv.h"
#include "exynos_drm_encoder.h" #include "exynos_drm_encoder.h"
#include "exynos_drm_connector.h"
#define to_exynos_encoder(x) container_of(x, struct exynos_drm_encoder,\ #define to_exynos_encoder(x) container_of(x, struct exynos_drm_encoder,\
drm_encoder) drm_encoder)
...@@ -26,72 +25,22 @@ ...@@ -26,72 +25,22 @@
* exynos specific encoder structure. * exynos specific encoder structure.
* *
* @drm_encoder: encoder object. * @drm_encoder: encoder object.
* @manager: specific encoder has its own manager to control a hardware * @display: the display structure that maps to this encoder
* appropriately and we can access a hardware drawing on this manager.
* @dpms: store the encoder dpms value.
* @updated: indicate whether overlay data updating is needed or not.
*/ */
struct exynos_drm_encoder { struct exynos_drm_encoder {
struct drm_crtc *old_crtc;
struct drm_encoder drm_encoder; struct drm_encoder drm_encoder;
struct exynos_drm_manager *manager; struct exynos_drm_display *display;
int dpms;
bool updated;
}; };
static void exynos_drm_connector_power(struct drm_encoder *encoder, int mode)
{
struct drm_device *dev = encoder->dev;
struct drm_connector *connector;
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (exynos_drm_best_encoder(connector) == encoder) {
DRM_DEBUG_KMS("connector[%d] dpms[%d]\n",
connector->base.id, mode);
exynos_drm_display_power(connector, mode);
}
}
}
static void exynos_drm_encoder_dpms(struct drm_encoder *encoder, int mode) static void exynos_drm_encoder_dpms(struct drm_encoder *encoder, int mode)
{ {
struct drm_device *dev = encoder->dev;
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
struct exynos_drm_manager_ops *manager_ops = manager->ops;
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
struct exynos_drm_display *display = exynos_encoder->display;
DRM_DEBUG_KMS("encoder dpms: %d\n", mode); DRM_DEBUG_KMS("encoder dpms: %d\n", mode);
if (exynos_encoder->dpms == mode) { if (display->ops->dpms)
DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n"); display->ops->dpms(display, mode);
return;
}
mutex_lock(&dev->struct_mutex);
switch (mode) {
case DRM_MODE_DPMS_ON:
if (manager_ops && manager_ops->apply)
if (!exynos_encoder->updated)
manager_ops->apply(manager->dev);
exynos_drm_connector_power(encoder, mode);
exynos_encoder->dpms = mode;
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
exynos_drm_connector_power(encoder, mode);
exynos_encoder->dpms = mode;
exynos_encoder->updated = false;
break;
default:
DRM_ERROR("unspecified mode %d\n", mode);
break;
}
mutex_unlock(&dev->struct_mutex);
} }
static bool static bool
...@@ -100,87 +49,31 @@ exynos_drm_encoder_mode_fixup(struct drm_encoder *encoder, ...@@ -100,87 +49,31 @@ exynos_drm_encoder_mode_fixup(struct drm_encoder *encoder,
struct drm_display_mode *adjusted_mode) struct drm_display_mode *adjusted_mode)
{ {
struct drm_device *dev = encoder->dev; struct drm_device *dev = encoder->dev;
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
struct exynos_drm_display *display = exynos_encoder->display;
struct drm_connector *connector; struct drm_connector *connector;
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
struct exynos_drm_manager_ops *manager_ops = manager->ops;
list_for_each_entry(connector, &dev->mode_config.connector_list, head) { list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->encoder == encoder) if (connector->encoder != encoder)
if (manager_ops && manager_ops->mode_fixup) continue;
manager_ops->mode_fixup(manager->dev, connector,
mode, adjusted_mode); if (display->ops->mode_fixup)
display->ops->mode_fixup(display, connector, mode,
adjusted_mode);
} }
return true; return true;
} }
static void disable_plane_to_crtc(struct drm_device *dev,
struct drm_crtc *old_crtc,
struct drm_crtc *new_crtc)
{
struct drm_plane *plane;
/*
* if old_crtc isn't same as encoder->crtc then it means that
* user changed crtc id to another one so the plane to old_crtc
* should be disabled and plane->crtc should be set to new_crtc
* (encoder->crtc)
*/
list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
if (plane->crtc == old_crtc) {
/*
* do not change below call order.
*
* plane->funcs->disable_plane call checks
* if encoder->crtc is same as plane->crtc and if same
* then overlay_ops->disable callback will be called
* to diasble current hw overlay so plane->crtc should
* have new_crtc because new_crtc was set to
* encoder->crtc in advance.
*/
plane->crtc = new_crtc;
plane->funcs->disable_plane(plane);
}
}
}
static void exynos_drm_encoder_mode_set(struct drm_encoder *encoder, static void exynos_drm_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode, struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode) struct drm_display_mode *adjusted_mode)
{ {
struct drm_device *dev = encoder->dev; struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
struct drm_connector *connector; struct exynos_drm_display *display = exynos_encoder->display;
struct exynos_drm_manager *manager;
struct exynos_drm_manager_ops *manager_ops;
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->encoder == encoder) {
struct exynos_drm_encoder *exynos_encoder;
exynos_encoder = to_exynos_encoder(encoder);
if (exynos_encoder->old_crtc != encoder->crtc &&
exynos_encoder->old_crtc) {
/*
* disable a plane to old crtc and change
* crtc of the plane to new one.
*/
disable_plane_to_crtc(dev,
exynos_encoder->old_crtc,
encoder->crtc);
}
manager = exynos_drm_get_manager(encoder);
manager_ops = manager->ops;
if (manager_ops && manager_ops->mode_set)
manager_ops->mode_set(manager->dev,
adjusted_mode);
exynos_encoder->old_crtc = encoder->crtc; if (display->ops->mode_set)
} display->ops->mode_set(display, adjusted_mode);
}
} }
static void exynos_drm_encoder_prepare(struct drm_encoder *encoder) static void exynos_drm_encoder_prepare(struct drm_encoder *encoder)
...@@ -191,53 +84,15 @@ static void exynos_drm_encoder_prepare(struct drm_encoder *encoder) ...@@ -191,53 +84,15 @@ static void exynos_drm_encoder_prepare(struct drm_encoder *encoder)
static void exynos_drm_encoder_commit(struct drm_encoder *encoder) static void exynos_drm_encoder_commit(struct drm_encoder *encoder)
{ {
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
struct exynos_drm_manager *manager = exynos_encoder->manager; struct exynos_drm_display *display = exynos_encoder->display;
struct exynos_drm_manager_ops *manager_ops = manager->ops;
if (manager_ops && manager_ops->commit) if (display->ops->dpms)
manager_ops->commit(manager->dev); display->ops->dpms(display, DRM_MODE_DPMS_ON);
/*
* this will avoid one issue that overlay data is updated to
* real hardware two times.
* And this variable will be used to check if the data was
* already updated or not by exynos_drm_encoder_dpms function.
*/
exynos_encoder->updated = true;
/* if (display->ops->commit)
* In case of setcrtc, there is no way to update encoder's dpms display->ops->commit(display);
* so update it here.
*/
exynos_encoder->dpms = DRM_MODE_DPMS_ON;
} }
void exynos_drm_encoder_complete_scanout(struct drm_framebuffer *fb)
{
struct exynos_drm_encoder *exynos_encoder;
struct exynos_drm_manager_ops *ops;
struct drm_device *dev = fb->dev;
struct drm_encoder *encoder;
/*
* make sure that overlay data are updated to real hardware
* for all encoders.
*/
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
exynos_encoder = to_exynos_encoder(encoder);
ops = exynos_encoder->manager->ops;
/*
* wait for vblank interrupt
* - this makes sure that overlay data are updated to
* real hardware.
*/
if (ops->wait_for_vblank)
ops->wait_for_vblank(exynos_encoder->manager->dev);
}
}
static void exynos_drm_encoder_disable(struct drm_encoder *encoder) static void exynos_drm_encoder_disable(struct drm_encoder *encoder)
{ {
struct drm_plane *plane; struct drm_plane *plane;
...@@ -263,10 +118,7 @@ static struct drm_encoder_helper_funcs exynos_encoder_helper_funcs = { ...@@ -263,10 +118,7 @@ static struct drm_encoder_helper_funcs exynos_encoder_helper_funcs = {
static void exynos_drm_encoder_destroy(struct drm_encoder *encoder) static void exynos_drm_encoder_destroy(struct drm_encoder *encoder)
{ {
struct exynos_drm_encoder *exynos_encoder = struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
to_exynos_encoder(encoder);
exynos_encoder->manager->pipe = -1;
drm_encoder_cleanup(encoder); drm_encoder_cleanup(encoder);
kfree(exynos_encoder); kfree(exynos_encoder);
...@@ -281,13 +133,12 @@ static unsigned int exynos_drm_encoder_clones(struct drm_encoder *encoder) ...@@ -281,13 +133,12 @@ static unsigned int exynos_drm_encoder_clones(struct drm_encoder *encoder)
struct drm_encoder *clone; struct drm_encoder *clone;
struct drm_device *dev = encoder->dev; struct drm_device *dev = encoder->dev;
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
struct exynos_drm_display_ops *display_ops = struct exynos_drm_display *display = exynos_encoder->display;
exynos_encoder->manager->display_ops;
unsigned int clone_mask = 0; unsigned int clone_mask = 0;
int cnt = 0; int cnt = 0;
list_for_each_entry(clone, &dev->mode_config.encoder_list, head) { list_for_each_entry(clone, &dev->mode_config.encoder_list, head) {
switch (display_ops->type) { switch (display->type) {
case EXYNOS_DISPLAY_TYPE_LCD: case EXYNOS_DISPLAY_TYPE_LCD:
case EXYNOS_DISPLAY_TYPE_HDMI: case EXYNOS_DISPLAY_TYPE_HDMI:
case EXYNOS_DISPLAY_TYPE_VIDI: case EXYNOS_DISPLAY_TYPE_VIDI:
...@@ -311,24 +162,20 @@ void exynos_drm_encoder_setup(struct drm_device *dev) ...@@ -311,24 +162,20 @@ void exynos_drm_encoder_setup(struct drm_device *dev)
struct drm_encoder * struct drm_encoder *
exynos_drm_encoder_create(struct drm_device *dev, exynos_drm_encoder_create(struct drm_device *dev,
struct exynos_drm_manager *manager, struct exynos_drm_display *display,
unsigned int possible_crtcs) unsigned long possible_crtcs)
{ {
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct exynos_drm_encoder *exynos_encoder; struct exynos_drm_encoder *exynos_encoder;
if (!manager || !possible_crtcs) if (!possible_crtcs)
return NULL;
if (!manager->dev)
return NULL; return NULL;
exynos_encoder = kzalloc(sizeof(*exynos_encoder), GFP_KERNEL); exynos_encoder = kzalloc(sizeof(*exynos_encoder), GFP_KERNEL);
if (!exynos_encoder) if (!exynos_encoder)
return NULL; return NULL;
exynos_encoder->dpms = DRM_MODE_DPMS_OFF; exynos_encoder->display = display;
exynos_encoder->manager = manager;
encoder = &exynos_encoder->drm_encoder; encoder = &exynos_encoder->drm_encoder;
encoder->possible_crtcs = possible_crtcs; encoder->possible_crtcs = possible_crtcs;
...@@ -344,149 +191,7 @@ exynos_drm_encoder_create(struct drm_device *dev, ...@@ -344,149 +191,7 @@ exynos_drm_encoder_create(struct drm_device *dev,
return encoder; return encoder;
} }
struct exynos_drm_manager *exynos_drm_get_manager(struct drm_encoder *encoder) struct exynos_drm_display *exynos_drm_get_display(struct drm_encoder *encoder)
{ {
return to_exynos_encoder(encoder)->manager; return to_exynos_encoder(encoder)->display;
}
void exynos_drm_fn_encoder(struct drm_crtc *crtc, void *data,
void (*fn)(struct drm_encoder *, void *))
{
struct drm_device *dev = crtc->dev;
struct drm_encoder *encoder;
struct exynos_drm_private *private = dev->dev_private;
struct exynos_drm_manager *manager;
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
/*
* if crtc is detached from encoder, check pipe,
* otherwise check crtc attached to encoder
*/
if (!encoder->crtc) {
manager = to_exynos_encoder(encoder)->manager;
if (manager->pipe < 0 ||
private->crtc[manager->pipe] != crtc)
continue;
} else {
if (encoder->crtc != crtc)
continue;
}
fn(encoder, data);
}
}
void exynos_drm_enable_vblank(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_manager *manager =
to_exynos_encoder(encoder)->manager;
struct exynos_drm_manager_ops *manager_ops = manager->ops;
int crtc = *(int *)data;
if (manager->pipe != crtc)
return;
if (manager_ops->enable_vblank)
manager_ops->enable_vblank(manager->dev);
}
void exynos_drm_disable_vblank(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_manager *manager =
to_exynos_encoder(encoder)->manager;
struct exynos_drm_manager_ops *manager_ops = manager->ops;
int crtc = *(int *)data;
if (manager->pipe != crtc)
return;
if (manager_ops->disable_vblank)
manager_ops->disable_vblank(manager->dev);
}
void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
struct exynos_drm_manager *manager = exynos_encoder->manager;
struct exynos_drm_manager_ops *manager_ops = manager->ops;
int mode = *(int *)data;
if (manager_ops && manager_ops->dpms)
manager_ops->dpms(manager->dev, mode);
/*
* if this condition is ok then it means that the crtc is already
* detached from encoder and last function for detaching is properly
* done, so clear pipe from manager to prevent repeated call.
*/
if (mode > DRM_MODE_DPMS_ON) {
if (!encoder->crtc)
manager->pipe = -1;
}
}
void exynos_drm_encoder_crtc_pipe(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_manager *manager =
to_exynos_encoder(encoder)->manager;
int pipe = *(int *)data;
/*
* when crtc is detached from encoder, this pipe is used
* to select manager operation
*/
manager->pipe = pipe;
}
void exynos_drm_encoder_plane_mode_set(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_manager *manager =
to_exynos_encoder(encoder)->manager;
struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops;
struct exynos_drm_overlay *overlay = data;
if (overlay_ops && overlay_ops->mode_set)
overlay_ops->mode_set(manager->dev, overlay);
}
void exynos_drm_encoder_plane_commit(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_manager *manager =
to_exynos_encoder(encoder)->manager;
struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops;
int zpos = DEFAULT_ZPOS;
if (data)
zpos = *(int *)data;
if (overlay_ops && overlay_ops->commit)
overlay_ops->commit(manager->dev, zpos);
}
void exynos_drm_encoder_plane_enable(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_manager *manager =
to_exynos_encoder(encoder)->manager;
struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops;
int zpos = DEFAULT_ZPOS;
if (data)
zpos = *(int *)data;
if (overlay_ops && overlay_ops->enable)
overlay_ops->enable(manager->dev, zpos);
}
void exynos_drm_encoder_plane_disable(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_manager *manager =
to_exynos_encoder(encoder)->manager;
struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops;
int zpos = DEFAULT_ZPOS;
if (data)
zpos = *(int *)data;
if (overlay_ops && overlay_ops->disable)
overlay_ops->disable(manager->dev, zpos);
} }
...@@ -18,20 +18,8 @@ struct exynos_drm_manager; ...@@ -18,20 +18,8 @@ struct exynos_drm_manager;
void exynos_drm_encoder_setup(struct drm_device *dev); void exynos_drm_encoder_setup(struct drm_device *dev);
struct drm_encoder *exynos_drm_encoder_create(struct drm_device *dev, struct drm_encoder *exynos_drm_encoder_create(struct drm_device *dev,
struct exynos_drm_manager *mgr, struct exynos_drm_display *mgr,
unsigned int possible_crtcs); unsigned long possible_crtcs);
struct exynos_drm_manager * struct exynos_drm_display *exynos_drm_get_display(struct drm_encoder *encoder);
exynos_drm_get_manager(struct drm_encoder *encoder);
void exynos_drm_fn_encoder(struct drm_crtc *crtc, void *data,
void (*fn)(struct drm_encoder *, void *));
void exynos_drm_enable_vblank(struct drm_encoder *encoder, void *data);
void exynos_drm_disable_vblank(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_crtc_pipe(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_plane_mode_set(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_plane_commit(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_plane_enable(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_plane_disable(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_complete_scanout(struct drm_framebuffer *fb);
#endif #endif
...@@ -20,9 +20,10 @@ ...@@ -20,9 +20,10 @@
#include "exynos_drm_drv.h" #include "exynos_drm_drv.h"
#include "exynos_drm_fb.h" #include "exynos_drm_fb.h"
#include "exynos_drm_fbdev.h"
#include "exynos_drm_gem.h" #include "exynos_drm_gem.h"
#include "exynos_drm_iommu.h" #include "exynos_drm_iommu.h"
#include "exynos_drm_encoder.h" #include "exynos_drm_crtc.h"
#define to_exynos_fb(x) container_of(x, struct exynos_drm_fb, fb) #define to_exynos_fb(x) container_of(x, struct exynos_drm_fb, fb)
...@@ -71,7 +72,7 @@ static void exynos_drm_fb_destroy(struct drm_framebuffer *fb) ...@@ -71,7 +72,7 @@ static void exynos_drm_fb_destroy(struct drm_framebuffer *fb)
unsigned int i; unsigned int i;
/* make sure that overlay data are updated before relesing fb. */ /* make sure that overlay data are updated before relesing fb. */
exynos_drm_encoder_complete_scanout(fb); exynos_drm_crtc_complete_scanout(fb);
drm_framebuffer_cleanup(fb); drm_framebuffer_cleanup(fb);
...@@ -300,6 +301,8 @@ static void exynos_drm_output_poll_changed(struct drm_device *dev) ...@@ -300,6 +301,8 @@ static void exynos_drm_output_poll_changed(struct drm_device *dev)
if (fb_helper) if (fb_helper)
drm_fb_helper_hotplug_event(fb_helper); drm_fb_helper_hotplug_event(fb_helper);
else
exynos_drm_fbdev_init(dev);
} }
static const struct drm_mode_config_funcs exynos_drm_mode_config_funcs = { static const struct drm_mode_config_funcs exynos_drm_mode_config_funcs = {
......
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
/* FIMD has totally five hardware windows. */ /* FIMD has totally five hardware windows. */
#define WINDOWS_NR 5 #define WINDOWS_NR 5
#define get_fimd_context(dev) platform_get_drvdata(to_platform_device(dev)) #define get_fimd_manager(mgr) platform_get_drvdata(to_platform_device(dev))
struct fimd_driver_data { struct fimd_driver_data {
unsigned int timing_base; unsigned int timing_base;
...@@ -105,20 +105,18 @@ struct fimd_win_data { ...@@ -105,20 +105,18 @@ struct fimd_win_data {
}; };
struct fimd_context { struct fimd_context {
struct exynos_drm_subdrv subdrv; struct device *dev;
int irq; struct drm_device *drm_dev;
struct drm_crtc *crtc;
struct clk *bus_clk; struct clk *bus_clk;
struct clk *lcd_clk; struct clk *lcd_clk;
void __iomem *regs; void __iomem *regs;
struct drm_display_mode mode;
struct fimd_win_data win_data[WINDOWS_NR]; struct fimd_win_data win_data[WINDOWS_NR];
unsigned int clkdiv;
unsigned int default_win; unsigned int default_win;
unsigned long irq_flags; unsigned long irq_flags;
u32 vidcon0;
u32 vidcon1; u32 vidcon1;
bool suspended; bool suspended;
struct mutex lock; int pipe;
wait_queue_head_t wait_vsync_queue; wait_queue_head_t wait_vsync_queue;
atomic_t wait_vsync_event; atomic_t wait_vsync_event;
...@@ -145,153 +143,147 @@ static inline struct fimd_driver_data *drm_fimd_get_driver_data( ...@@ -145,153 +143,147 @@ static inline struct fimd_driver_data *drm_fimd_get_driver_data(
return (struct fimd_driver_data *)of_id->data; return (struct fimd_driver_data *)of_id->data;
} }
static bool fimd_display_is_connected(struct device *dev) static int fimd_mgr_initialize(struct exynos_drm_manager *mgr,
struct drm_device *drm_dev, int pipe)
{ {
/* TODO. */ struct fimd_context *ctx = mgr->ctx;
return true; ctx->drm_dev = drm_dev;
} ctx->pipe = pipe;
static void *fimd_get_panel(struct device *dev) /*
{ * enable drm irq mode.
struct fimd_context *ctx = get_fimd_context(dev); * - with irq_enabled = true, we can use the vblank feature.
*
* P.S. note that we wouldn't use drm irq handler but
* just specific driver own one instead because
* drm framework supports only one irq handler.
*/
drm_dev->irq_enabled = true;
return &ctx->panel; /*
} * with vblank_disable_allowed = true, vblank interrupt will be disabled
* by drm timer once a current process gives up ownership of
* vblank event.(after drm_vblank_put function is called)
*/
drm_dev->vblank_disable_allowed = true;
static int fimd_check_mode(struct device *dev, struct drm_display_mode *mode) /* attach this sub driver to iommu mapping if supported. */
{ if (is_drm_iommu_supported(ctx->drm_dev))
/* TODO. */ drm_iommu_attach_device(ctx->drm_dev, ctx->dev);
return 0; return 0;
} }
static int fimd_display_power_on(struct device *dev, int mode) static void fimd_mgr_remove(struct exynos_drm_manager *mgr)
{ {
/* TODO */ struct fimd_context *ctx = mgr->ctx;
return 0; /* detach this sub driver from iommu mapping if supported. */
if (is_drm_iommu_supported(ctx->drm_dev))
drm_iommu_detach_device(ctx->drm_dev, ctx->dev);
} }
static struct exynos_drm_display_ops fimd_display_ops = { static u32 fimd_calc_clkdiv(struct fimd_context *ctx,
.type = EXYNOS_DISPLAY_TYPE_LCD, const struct drm_display_mode *mode)
.is_connected = fimd_display_is_connected,
.get_panel = fimd_get_panel,
.check_mode = fimd_check_mode,
.power_on = fimd_display_power_on,
};
static void fimd_dpms(struct device *subdrv_dev, int mode)
{ {
struct fimd_context *ctx = get_fimd_context(subdrv_dev); unsigned long ideal_clk = mode->htotal * mode->vtotal * mode->vrefresh;
u32 clkdiv;
DRM_DEBUG_KMS("%d\n", mode); /* Find the clock divider value that gets us closest to ideal_clk */
clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->lcd_clk), ideal_clk);
mutex_lock(&ctx->lock); return (clkdiv < 0x100) ? clkdiv : 0xff;
}
switch (mode) { static bool fimd_mode_fixup(struct exynos_drm_manager *mgr,
case DRM_MODE_DPMS_ON: const struct drm_display_mode *mode,
/* struct drm_display_mode *adjusted_mode)
* enable fimd hardware only if suspended status. {
* if (adjusted_mode->vrefresh == 0)
* P.S. fimd_dpms function would be called at booting time so adjusted_mode->vrefresh = FIMD_DEFAULT_FRAMERATE;
* clk_enable could be called double time.
*/
if (ctx->suspended)
pm_runtime_get_sync(subdrv_dev);
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
if (!ctx->suspended)
pm_runtime_put_sync(subdrv_dev);
break;
default:
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
break;
}
mutex_unlock(&ctx->lock); return true;
} }
static void fimd_apply(struct device *subdrv_dev) static void fimd_mode_set(struct exynos_drm_manager *mgr,
const struct drm_display_mode *in_mode)
{ {
struct fimd_context *ctx = get_fimd_context(subdrv_dev); struct fimd_context *ctx = mgr->ctx;
struct exynos_drm_manager *mgr = ctx->subdrv.manager;
struct exynos_drm_manager_ops *mgr_ops = mgr->ops;
struct exynos_drm_overlay_ops *ovl_ops = mgr->overlay_ops;
struct fimd_win_data *win_data;
int i;
for (i = 0; i < WINDOWS_NR; i++) { drm_mode_copy(&ctx->mode, in_mode);
win_data = &ctx->win_data[i];
if (win_data->enabled && (ovl_ops && ovl_ops->commit))
ovl_ops->commit(subdrv_dev, i);
}
if (mgr_ops && mgr_ops->commit)
mgr_ops->commit(subdrv_dev);
} }
static void fimd_commit(struct device *dev) static void fimd_commit(struct exynos_drm_manager *mgr)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
struct exynos_drm_panel_info *panel = &ctx->panel; struct drm_display_mode *mode = &ctx->mode;
struct videomode *vm = &panel->vm;
struct fimd_driver_data *driver_data; struct fimd_driver_data *driver_data;
u32 val; u32 val, clkdiv, vidcon1;
int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd;
driver_data = ctx->driver_data; driver_data = ctx->driver_data;
if (ctx->suspended) if (ctx->suspended)
return; return;
/* setup polarity values from machine code. */ /* nothing to do if we haven't set the mode yet */
writel(ctx->vidcon1, ctx->regs + driver_data->timing_base + VIDCON1); 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. */ /* setup vertical timing values. */
val = VIDTCON0_VBPD(vm->vback_porch - 1) | vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
VIDTCON0_VFPD(vm->vfront_porch - 1) | vbpd = mode->crtc_vtotal - mode->crtc_vsync_end;
VIDTCON0_VSPW(vm->vsync_len - 1); 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); writel(val, ctx->regs + driver_data->timing_base + VIDTCON0);
/* setup horizontal timing values. */ /* setup horizontal timing values. */
val = VIDTCON1_HBPD(vm->hback_porch - 1) | hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
VIDTCON1_HFPD(vm->hfront_porch - 1) | hbpd = mode->crtc_htotal - mode->crtc_hsync_end;
VIDTCON1_HSPW(vm->hsync_len - 1); 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); writel(val, ctx->regs + driver_data->timing_base + VIDTCON1);
/* setup horizontal and vertical display size. */ /* setup horizontal and vertical display size. */
val = VIDTCON2_LINEVAL(vm->vactive - 1) | val = VIDTCON2_LINEVAL(mode->vdisplay - 1) |
VIDTCON2_HOZVAL(vm->hactive - 1) | VIDTCON2_HOZVAL(mode->hdisplay - 1) |
VIDTCON2_LINEVAL_E(vm->vactive - 1) | VIDTCON2_LINEVAL_E(mode->vdisplay - 1) |
VIDTCON2_HOZVAL_E(vm->hactive - 1); VIDTCON2_HOZVAL_E(mode->hdisplay - 1);
writel(val, ctx->regs + driver_data->timing_base + VIDTCON2); writel(val, ctx->regs + driver_data->timing_base + VIDTCON2);
/* setup clock source, clock divider, enable dma. */
val = ctx->vidcon0;
val &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR);
if (ctx->driver_data->has_clksel) {
val &= ~VIDCON0_CLKSEL_MASK;
val |= VIDCON0_CLKSEL_LCD;
}
if (ctx->clkdiv > 1)
val |= VIDCON0_CLKVAL_F(ctx->clkdiv - 1) | VIDCON0_CLKDIR;
else
val &= ~VIDCON0_CLKDIR; /* 1:1 clock */
/* /*
* fields of register with prefix '_F' would be updated * fields of register with prefix '_F' would be updated
* at vsync(same as dma start) * at vsync(same as dma start)
*/ */
val |= VIDCON0_ENVID | VIDCON0_ENVID_F; val = VIDCON0_ENVID | VIDCON0_ENVID_F;
if (ctx->driver_data->has_clksel)
val |= VIDCON0_CLKSEL_LCD;
clkdiv = fimd_calc_clkdiv(ctx, mode);
if (clkdiv > 1)
val |= VIDCON0_CLKVAL_F(clkdiv - 1) | VIDCON0_CLKDIR;
writel(val, ctx->regs + VIDCON0); writel(val, ctx->regs + VIDCON0);
} }
static int fimd_enable_vblank(struct device *dev) static int fimd_enable_vblank(struct exynos_drm_manager *mgr)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
u32 val; u32 val;
if (ctx->suspended) if (ctx->suspended)
...@@ -314,9 +306,9 @@ static int fimd_enable_vblank(struct device *dev) ...@@ -314,9 +306,9 @@ static int fimd_enable_vblank(struct device *dev)
return 0; return 0;
} }
static void fimd_disable_vblank(struct device *dev) static void fimd_disable_vblank(struct exynos_drm_manager *mgr)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
u32 val; u32 val;
if (ctx->suspended) if (ctx->suspended)
...@@ -332,9 +324,9 @@ static void fimd_disable_vblank(struct device *dev) ...@@ -332,9 +324,9 @@ static void fimd_disable_vblank(struct device *dev)
} }
} }
static void fimd_wait_for_vblank(struct device *dev) static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
if (ctx->suspended) if (ctx->suspended)
return; return;
...@@ -351,25 +343,16 @@ static void fimd_wait_for_vblank(struct device *dev) ...@@ -351,25 +343,16 @@ static void fimd_wait_for_vblank(struct device *dev)
DRM_DEBUG_KMS("vblank wait timed out.\n"); DRM_DEBUG_KMS("vblank wait timed out.\n");
} }
static struct exynos_drm_manager_ops fimd_manager_ops = { static void fimd_win_mode_set(struct exynos_drm_manager *mgr,
.dpms = fimd_dpms,
.apply = fimd_apply,
.commit = fimd_commit,
.enable_vblank = fimd_enable_vblank,
.disable_vblank = fimd_disable_vblank,
.wait_for_vblank = fimd_wait_for_vblank,
};
static void fimd_win_mode_set(struct device *dev,
struct exynos_drm_overlay *overlay) struct exynos_drm_overlay *overlay)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
struct fimd_win_data *win_data; struct fimd_win_data *win_data;
int win; int win;
unsigned long offset; unsigned long offset;
if (!overlay) { if (!overlay) {
dev_err(dev, "overlay is NULL\n"); DRM_ERROR("overlay is NULL\n");
return; return;
} }
...@@ -409,9 +392,8 @@ static void fimd_win_mode_set(struct device *dev, ...@@ -409,9 +392,8 @@ static void fimd_win_mode_set(struct device *dev,
overlay->fb_width, overlay->crtc_width); overlay->fb_width, overlay->crtc_width);
} }
static void fimd_win_set_pixfmt(struct device *dev, unsigned int win) static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win)
{ {
struct fimd_context *ctx = get_fimd_context(dev);
struct fimd_win_data *win_data = &ctx->win_data[win]; struct fimd_win_data *win_data = &ctx->win_data[win];
unsigned long val; unsigned long val;
...@@ -467,9 +449,8 @@ static void fimd_win_set_pixfmt(struct device *dev, unsigned int win) ...@@ -467,9 +449,8 @@ static void fimd_win_set_pixfmt(struct device *dev, unsigned int win)
writel(val, ctx->regs + WINCON(win)); writel(val, ctx->regs + WINCON(win));
} }
static void fimd_win_set_colkey(struct device *dev, unsigned int win) static void fimd_win_set_colkey(struct fimd_context *ctx, unsigned int win)
{ {
struct fimd_context *ctx = get_fimd_context(dev);
unsigned int keycon0 = 0, keycon1 = 0; unsigned int keycon0 = 0, keycon1 = 0;
keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F | keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F |
...@@ -508,9 +489,9 @@ static void fimd_shadow_protect_win(struct fimd_context *ctx, ...@@ -508,9 +489,9 @@ static void fimd_shadow_protect_win(struct fimd_context *ctx,
writel(val, ctx->regs + reg); writel(val, ctx->regs + reg);
} }
static void fimd_win_commit(struct device *dev, int zpos) static void fimd_win_commit(struct exynos_drm_manager *mgr, int zpos)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
struct fimd_win_data *win_data; struct fimd_win_data *win_data;
int win = zpos; int win = zpos;
unsigned long val, alpha, size; unsigned long val, alpha, size;
...@@ -528,6 +509,12 @@ static void fimd_win_commit(struct device *dev, int zpos) ...@@ -528,6 +509,12 @@ static void fimd_win_commit(struct device *dev, int zpos)
win_data = &ctx->win_data[win]; win_data = &ctx->win_data[win];
/* If suspended, enable this on resume */
if (ctx->suspended) {
win_data->resume = true;
return;
}
/* /*
* SHADOWCON/PRTCON register is used for enabling timing. * SHADOWCON/PRTCON register is used for enabling timing.
* *
...@@ -605,11 +592,11 @@ static void fimd_win_commit(struct device *dev, int zpos) ...@@ -605,11 +592,11 @@ static void fimd_win_commit(struct device *dev, int zpos)
DRM_DEBUG_KMS("osd size = 0x%x\n", (unsigned int)val); DRM_DEBUG_KMS("osd size = 0x%x\n", (unsigned int)val);
} }
fimd_win_set_pixfmt(dev, win); fimd_win_set_pixfmt(ctx, win);
/* hardware window 0 doesn't support color key. */ /* hardware window 0 doesn't support color key. */
if (win != 0) if (win != 0)
fimd_win_set_colkey(dev, win); fimd_win_set_colkey(ctx, win);
/* wincon */ /* wincon */
val = readl(ctx->regs + WINCON(win)); val = readl(ctx->regs + WINCON(win));
...@@ -628,9 +615,9 @@ static void fimd_win_commit(struct device *dev, int zpos) ...@@ -628,9 +615,9 @@ static void fimd_win_commit(struct device *dev, int zpos)
win_data->enabled = true; win_data->enabled = true;
} }
static void fimd_win_disable(struct device *dev, int zpos) static void fimd_win_disable(struct exynos_drm_manager *mgr, int zpos)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
struct fimd_win_data *win_data; struct fimd_win_data *win_data;
int win = zpos; int win = zpos;
u32 val; u32 val;
...@@ -669,132 +656,6 @@ static void fimd_win_disable(struct device *dev, int zpos) ...@@ -669,132 +656,6 @@ static void fimd_win_disable(struct device *dev, int zpos)
win_data->enabled = false; win_data->enabled = false;
} }
static struct exynos_drm_overlay_ops fimd_overlay_ops = {
.mode_set = fimd_win_mode_set,
.commit = fimd_win_commit,
.disable = fimd_win_disable,
};
static struct exynos_drm_manager fimd_manager = {
.pipe = -1,
.ops = &fimd_manager_ops,
.overlay_ops = &fimd_overlay_ops,
.display_ops = &fimd_display_ops,
};
static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
{
struct fimd_context *ctx = (struct fimd_context *)dev_id;
struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
struct drm_device *drm_dev = subdrv->drm_dev;
struct exynos_drm_manager *manager = subdrv->manager;
u32 val;
val = readl(ctx->regs + VIDINTCON1);
if (val & VIDINTCON1_INT_FRAME)
/* VSYNC interrupt */
writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1);
/* check the crtc is detached already from encoder */
if (manager->pipe < 0)
goto out;
drm_handle_vblank(drm_dev, manager->pipe);
exynos_drm_crtc_finish_pageflip(drm_dev, manager->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;
}
static int fimd_subdrv_probe(struct drm_device *drm_dev, struct device *dev)
{
/*
* enable drm irq mode.
* - with irq_enabled = true, we can use the vblank feature.
*
* P.S. note that we wouldn't use drm irq handler but
* just specific driver own one instead because
* drm framework supports only one irq handler.
*/
drm_dev->irq_enabled = true;
/*
* with vblank_disable_allowed = true, vblank interrupt will be disabled
* by drm timer once a current process gives up ownership of
* vblank event.(after drm_vblank_put function is called)
*/
drm_dev->vblank_disable_allowed = true;
/* attach this sub driver to iommu mapping if supported. */
if (is_drm_iommu_supported(drm_dev))
drm_iommu_attach_device(drm_dev, dev);
return 0;
}
static void fimd_subdrv_remove(struct drm_device *drm_dev, struct device *dev)
{
/* detach this sub driver from iommu mapping if supported. */
if (is_drm_iommu_supported(drm_dev))
drm_iommu_detach_device(drm_dev, dev);
}
static int fimd_configure_clocks(struct fimd_context *ctx, struct device *dev)
{
struct videomode *vm = &ctx->panel.vm;
unsigned long clk;
ctx->bus_clk = devm_clk_get(dev, "fimd");
if (IS_ERR(ctx->bus_clk)) {
dev_err(dev, "failed to get bus clock\n");
return PTR_ERR(ctx->bus_clk);
}
ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd");
if (IS_ERR(ctx->lcd_clk)) {
dev_err(dev, "failed to get lcd clock\n");
return PTR_ERR(ctx->lcd_clk);
}
clk = clk_get_rate(ctx->lcd_clk);
if (clk == 0) {
dev_err(dev, "error getting sclk_fimd clock rate\n");
return -EINVAL;
}
if (vm->pixelclock == 0) {
unsigned long c;
c = vm->hactive + vm->hback_porch + vm->hfront_porch +
vm->hsync_len;
c *= vm->vactive + vm->vback_porch + vm->vfront_porch +
vm->vsync_len;
vm->pixelclock = c * FIMD_DEFAULT_FRAMERATE;
if (vm->pixelclock == 0) {
dev_err(dev, "incorrect display timings\n");
return -EINVAL;
}
dev_warn(dev, "pixel clock recalculated to %luHz (%dHz frame rate)\n",
vm->pixelclock, FIMD_DEFAULT_FRAMERATE);
}
ctx->clkdiv = DIV_ROUND_UP(clk, vm->pixelclock);
if (ctx->clkdiv > 256) {
dev_warn(dev, "calculated pixel clock divider too high (%u), lowered to 256\n",
ctx->clkdiv);
ctx->clkdiv = 256;
}
vm->pixelclock = clk / ctx->clkdiv;
DRM_DEBUG_KMS("pixel clock = %lu, clkdiv = %d\n", vm->pixelclock,
ctx->clkdiv);
return 0;
}
static void fimd_clear_win(struct fimd_context *ctx, int win) static void fimd_clear_win(struct fimd_context *ctx, int win)
{ {
writel(0, ctx->regs + WINCON(win)); writel(0, ctx->regs + WINCON(win));
...@@ -808,111 +669,190 @@ static void fimd_clear_win(struct fimd_context *ctx, int win) ...@@ -808,111 +669,190 @@ static void fimd_clear_win(struct fimd_context *ctx, int win)
fimd_shadow_protect_win(ctx, win, false); fimd_shadow_protect_win(ctx, win, false);
} }
static int fimd_clock(struct fimd_context *ctx, bool enable) static void fimd_window_suspend(struct exynos_drm_manager *mgr)
{ {
if (enable) { struct fimd_context *ctx = mgr->ctx;
int ret; struct fimd_win_data *win_data;
int i;
ret = clk_prepare_enable(ctx->bus_clk);
if (ret < 0)
return ret;
ret = clk_prepare_enable(ctx->lcd_clk); for (i = 0; i < WINDOWS_NR; i++) {
if (ret < 0) { win_data = &ctx->win_data[i];
clk_disable_unprepare(ctx->bus_clk); win_data->resume = win_data->enabled;
return ret; if (win_data->enabled)
} fimd_win_disable(mgr, i);
} else {
clk_disable_unprepare(ctx->lcd_clk);
clk_disable_unprepare(ctx->bus_clk);
} }
fimd_wait_for_vblank(mgr);
return 0;
} }
static void fimd_window_suspend(struct device *dev) static void fimd_window_resume(struct exynos_drm_manager *mgr)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
struct fimd_win_data *win_data; struct fimd_win_data *win_data;
int i; int i;
for (i = 0; i < WINDOWS_NR; i++) { for (i = 0; i < WINDOWS_NR; i++) {
win_data = &ctx->win_data[i]; win_data = &ctx->win_data[i];
win_data->resume = win_data->enabled; win_data->enabled = win_data->resume;
fimd_win_disable(dev, i); win_data->resume = false;
} }
fimd_wait_for_vblank(dev);
} }
static void fimd_window_resume(struct device *dev) static void fimd_apply(struct exynos_drm_manager *mgr)
{ {
struct fimd_context *ctx = get_fimd_context(dev); struct fimd_context *ctx = mgr->ctx;
struct fimd_win_data *win_data; struct fimd_win_data *win_data;
int i; int i;
for (i = 0; i < WINDOWS_NR; i++) { for (i = 0; i < WINDOWS_NR; i++) {
win_data = &ctx->win_data[i]; win_data = &ctx->win_data[i];
win_data->enabled = win_data->resume; if (win_data->enabled)
win_data->resume = false; fimd_win_commit(mgr, i);
} }
fimd_commit(mgr);
} }
static int fimd_activate(struct fimd_context *ctx, bool enable) static int fimd_poweron(struct exynos_drm_manager *mgr)
{ {
struct device *dev = ctx->subdrv.dev; struct fimd_context *ctx = mgr->ctx;
if (enable) {
int ret; int ret;
ret = fimd_clock(ctx, true); if (!ctx->suspended)
if (ret < 0) return 0;
return ret;
ctx->suspended = false; ctx->suspended = false;
pm_runtime_get_sync(ctx->dev);
ret = clk_prepare_enable(ctx->bus_clk);
if (ret < 0) {
DRM_ERROR("Failed to prepare_enable the bus clk [%d]\n", ret);
goto bus_clk_err;
}
ret = clk_prepare_enable(ctx->lcd_clk);
if (ret < 0) {
DRM_ERROR("Failed to prepare_enable the lcd clk [%d]\n", ret);
goto lcd_clk_err;
}
/* if vblank was enabled status, enable it again. */ /* if vblank was enabled status, enable it again. */
if (test_and_clear_bit(0, &ctx->irq_flags)) if (test_and_clear_bit(0, &ctx->irq_flags)) {
fimd_enable_vblank(dev); ret = fimd_enable_vblank(mgr);
if (ret) {
DRM_ERROR("Failed to re-enable vblank [%d]\n", ret);
goto enable_vblank_err;
}
}
fimd_window_resume(dev); fimd_window_resume(mgr);
} else {
fimd_window_suspend(dev); fimd_apply(mgr);
fimd_clock(ctx, false); return 0;
enable_vblank_err:
clk_disable_unprepare(ctx->lcd_clk);
lcd_clk_err:
clk_disable_unprepare(ctx->bus_clk);
bus_clk_err:
ctx->suspended = true; ctx->suspended = true;
} return ret;
}
static int fimd_poweroff(struct exynos_drm_manager *mgr)
{
struct fimd_context *ctx = mgr->ctx;
if (ctx->suspended)
return 0;
/*
* We need to make sure that all windows are disabled before we
* suspend that connector. Otherwise we might try to scan from
* a destroyed buffer later.
*/
fimd_window_suspend(mgr);
clk_disable_unprepare(ctx->lcd_clk);
clk_disable_unprepare(ctx->bus_clk);
pm_runtime_put_sync(ctx->dev);
ctx->suspended = true;
return 0; return 0;
} }
static int fimd_get_platform_data(struct fimd_context *ctx, struct device *dev) static void fimd_dpms(struct exynos_drm_manager *mgr, int mode)
{ {
struct videomode *vm; DRM_DEBUG_KMS("%s, %d\n", __FILE__, mode);
int ret;
vm = &ctx->panel.vm; switch (mode) {
ret = of_get_videomode(dev->of_node, vm, OF_USE_NATIVE_MODE); case DRM_MODE_DPMS_ON:
if (ret) { fimd_poweron(mgr);
DRM_ERROR("failed: of_get_videomode() : %d\n", ret); break;
return ret; case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
fimd_poweroff(mgr);
break;
default:
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
break;
} }
}
if (vm->flags & DISPLAY_FLAGS_VSYNC_LOW) static struct exynos_drm_manager_ops fimd_manager_ops = {
ctx->vidcon1 |= VIDCON1_INV_VSYNC; .initialize = fimd_mgr_initialize,
if (vm->flags & DISPLAY_FLAGS_HSYNC_LOW) .remove = fimd_mgr_remove,
ctx->vidcon1 |= VIDCON1_INV_HSYNC; .dpms = fimd_dpms,
if (vm->flags & DISPLAY_FLAGS_DE_LOW) .mode_fixup = fimd_mode_fixup,
ctx->vidcon1 |= VIDCON1_INV_VDEN; .mode_set = fimd_mode_set,
if (vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) .commit = fimd_commit,
ctx->vidcon1 |= VIDCON1_INV_VCLK; .enable_vblank = fimd_enable_vblank,
.disable_vblank = fimd_disable_vblank,
.wait_for_vblank = fimd_wait_for_vblank,
.win_mode_set = fimd_win_mode_set,
.win_commit = fimd_win_commit,
.win_disable = fimd_win_disable,
};
return 0; static struct exynos_drm_manager fimd_manager = {
.type = EXYNOS_DISPLAY_TYPE_LCD,
.ops = &fimd_manager_ops,
};
static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
{
struct fimd_context *ctx = (struct fimd_context *)dev_id;
u32 val;
val = readl(ctx->regs + VIDINTCON1);
if (val & VIDINTCON1_INT_FRAME)
/* VSYNC interrupt */
writel(VIDINTCON1_INT_FRAME, 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);
/* 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;
} }
static int fimd_probe(struct platform_device *pdev) static int fimd_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct fimd_context *ctx; struct fimd_context *ctx;
struct exynos_drm_subdrv *subdrv;
struct resource *res; struct resource *res;
int win; int win;
int ret = -EINVAL; int ret = -EINVAL;
...@@ -924,13 +864,25 @@ static int fimd_probe(struct platform_device *pdev) ...@@ -924,13 +864,25 @@ static int fimd_probe(struct platform_device *pdev)
if (!ctx) if (!ctx)
return -ENOMEM; return -ENOMEM;
ret = fimd_get_platform_data(ctx, dev); ctx->dev = dev;
if (ret) ctx->suspended = true;
return ret;
ret = fimd_configure_clocks(ctx, dev); if (of_property_read_bool(dev->of_node, "samsung,invert-vden"))
if (ret) ctx->vidcon1 |= VIDCON1_INV_VDEN;
return ret; if (of_property_read_bool(dev->of_node, "samsung,invert-vclk"))
ctx->vidcon1 |= VIDCON1_INV_VCLK;
ctx->bus_clk = devm_clk_get(dev, "fimd");
if (IS_ERR(ctx->bus_clk)) {
dev_err(dev, "failed to get bus clock\n");
return PTR_ERR(ctx->bus_clk);
}
ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd");
if (IS_ERR(ctx->lcd_clk)) {
dev_err(dev, "failed to get lcd clock\n");
return PTR_ERR(ctx->lcd_clk);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
...@@ -944,9 +896,7 @@ static int fimd_probe(struct platform_device *pdev) ...@@ -944,9 +896,7 @@ static int fimd_probe(struct platform_device *pdev)
return -ENXIO; return -ENXIO;
} }
ctx->irq = res->start; ret = devm_request_irq(dev, res->start, fimd_irq_handler,
ret = devm_request_irq(dev, ctx->irq, fimd_irq_handler,
0, "drm_fimd", ctx); 0, "drm_fimd", ctx);
if (ret) { if (ret) {
dev_err(dev, "irq request failed.\n"); dev_err(dev, "irq request failed.\n");
...@@ -957,120 +907,42 @@ static int fimd_probe(struct platform_device *pdev) ...@@ -957,120 +907,42 @@ static int fimd_probe(struct platform_device *pdev)
init_waitqueue_head(&ctx->wait_vsync_queue); init_waitqueue_head(&ctx->wait_vsync_queue);
atomic_set(&ctx->wait_vsync_event, 0); atomic_set(&ctx->wait_vsync_event, 0);
subdrv = &ctx->subdrv; platform_set_drvdata(pdev, &fimd_manager);
subdrv->dev = dev;
subdrv->manager = &fimd_manager;
subdrv->probe = fimd_subdrv_probe;
subdrv->remove = fimd_subdrv_remove;
mutex_init(&ctx->lock); fimd_manager.ctx = ctx;
exynos_drm_manager_register(&fimd_manager);
platform_set_drvdata(pdev, ctx); exynos_dpi_probe(ctx->dev);
pm_runtime_enable(dev); pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
for (win = 0; win < WINDOWS_NR; win++) for (win = 0; win < WINDOWS_NR; win++)
fimd_clear_win(ctx, win); fimd_clear_win(ctx, win);
exynos_drm_subdrv_register(subdrv);
return 0; return 0;
} }
static int fimd_remove(struct platform_device *pdev) static int fimd_remove(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct exynos_drm_manager *mgr = platform_get_drvdata(pdev);
struct fimd_context *ctx = platform_get_drvdata(pdev);
exynos_drm_subdrv_unregister(&ctx->subdrv); exynos_dpi_remove(&pdev->dev);
if (ctx->suspended) exynos_drm_manager_unregister(&fimd_manager);
goto out;
pm_runtime_set_suspended(dev); fimd_dpms(mgr, DRM_MODE_DPMS_OFF);
pm_runtime_put_sync(dev);
out: pm_runtime_disable(&pdev->dev);
pm_runtime_disable(dev);
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP
static int fimd_suspend(struct device *dev)
{
struct fimd_context *ctx = get_fimd_context(dev);
/*
* do not use pm_runtime_suspend(). if pm_runtime_suspend() is
* called here, an error would be returned by that interface
* because the usage_count of pm runtime is more than 1.
*/
if (!pm_runtime_suspended(dev))
return fimd_activate(ctx, false);
return 0;
}
static int fimd_resume(struct device *dev)
{
struct fimd_context *ctx = get_fimd_context(dev);
/*
* if entered to sleep when lcd panel was on, the usage_count
* of pm runtime would still be 1 so in this case, fimd driver
* should be on directly not drawing on pm runtime interface.
*/
if (!pm_runtime_suspended(dev)) {
int ret;
ret = fimd_activate(ctx, true);
if (ret < 0)
return ret;
/*
* in case of dpms on(standby), fimd_apply function will
* be called by encoder's dpms callback to update fimd's
* registers but in case of sleep wakeup, it's not.
* so fimd_apply function should be called at here.
*/
fimd_apply(dev);
}
return 0;
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int fimd_runtime_suspend(struct device *dev)
{
struct fimd_context *ctx = get_fimd_context(dev);
return fimd_activate(ctx, false);
}
static int fimd_runtime_resume(struct device *dev)
{
struct fimd_context *ctx = get_fimd_context(dev);
return fimd_activate(ctx, true);
}
#endif
static const struct dev_pm_ops fimd_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(fimd_suspend, fimd_resume)
SET_RUNTIME_PM_OPS(fimd_runtime_suspend, fimd_runtime_resume, NULL)
};
struct platform_driver fimd_driver = { struct platform_driver fimd_driver = {
.probe = fimd_probe, .probe = fimd_probe,
.remove = fimd_remove, .remove = fimd_remove,
.driver = { .driver = {
.name = "exynos4-fb", .name = "exynos4-fb",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.pm = &fimd_pm_ops,
.of_match_table = fimd_driver_dt_match, .of_match_table = fimd_driver_dt_match,
}, },
}; };
/*
* Copyright (C) 2011 Samsung Electronics Co.Ltd
* Authors:
* Inki Dae <inki.dae@samsung.com>
* Seung-Woo Kim <sw0312.kim@samsung.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#include <drm/drmP.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/exynos_drm.h>
#include "exynos_drm_drv.h"
#include "exynos_drm_hdmi.h"
#define to_context(dev) platform_get_drvdata(to_platform_device(dev))
#define to_subdrv(dev) to_context(dev)
#define get_ctx_from_subdrv(subdrv) container_of(subdrv,\
struct drm_hdmi_context, subdrv);
/* platform device pointer for common drm hdmi device. */
static struct platform_device *exynos_drm_hdmi_pdev;
/* Common hdmi subdrv needs to access the hdmi and mixer though context.
* These should be initialied by the repective drivers */
static struct exynos_drm_hdmi_context *hdmi_ctx;
static struct exynos_drm_hdmi_context *mixer_ctx;
/* these callback points shoud be set by specific drivers. */
static struct exynos_hdmi_ops *hdmi_ops;
static struct exynos_mixer_ops *mixer_ops;
struct drm_hdmi_context {
struct exynos_drm_subdrv subdrv;
struct exynos_drm_hdmi_context *hdmi_ctx;
struct exynos_drm_hdmi_context *mixer_ctx;
bool enabled[MIXER_WIN_NR];
};
int exynos_platform_device_hdmi_register(void)
{
struct platform_device *pdev;
if (exynos_drm_hdmi_pdev)
return -EEXIST;
pdev = platform_device_register_simple(
"exynos-drm-hdmi", -1, NULL, 0);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
exynos_drm_hdmi_pdev = pdev;
return 0;
}
void exynos_platform_device_hdmi_unregister(void)
{
if (exynos_drm_hdmi_pdev) {
platform_device_unregister(exynos_drm_hdmi_pdev);
exynos_drm_hdmi_pdev = NULL;
}
}
void exynos_hdmi_drv_attach(struct exynos_drm_hdmi_context *ctx)
{
if (ctx)
hdmi_ctx = ctx;
}
void exynos_mixer_drv_attach(struct exynos_drm_hdmi_context *ctx)
{
if (ctx)
mixer_ctx = ctx;
}
void exynos_hdmi_ops_register(struct exynos_hdmi_ops *ops)
{
if (ops)
hdmi_ops = ops;
}
void exynos_mixer_ops_register(struct exynos_mixer_ops *ops)
{
if (ops)
mixer_ops = ops;
}
static bool drm_hdmi_is_connected(struct device *dev)
{
struct drm_hdmi_context *ctx = to_context(dev);
if (hdmi_ops && hdmi_ops->is_connected)
return hdmi_ops->is_connected(ctx->hdmi_ctx->ctx);
return false;
}
static struct edid *drm_hdmi_get_edid(struct device *dev,
struct drm_connector *connector)
{
struct drm_hdmi_context *ctx = to_context(dev);
if (hdmi_ops && hdmi_ops->get_edid)
return hdmi_ops->get_edid(ctx->hdmi_ctx->ctx, connector);
return NULL;
}
static int drm_hdmi_check_mode(struct device *dev,
struct drm_display_mode *mode)
{
struct drm_hdmi_context *ctx = to_context(dev);
int ret = 0;
/*
* Both, mixer and hdmi should be able to handle the requested mode.
* If any of the two fails, return mode as BAD.
*/
if (mixer_ops && mixer_ops->check_mode)
ret = mixer_ops->check_mode(ctx->mixer_ctx->ctx, mode);
if (ret)
return ret;
if (hdmi_ops && hdmi_ops->check_mode)
return hdmi_ops->check_mode(ctx->hdmi_ctx->ctx, mode);
return 0;
}
static int drm_hdmi_power_on(struct device *dev, int mode)
{
struct drm_hdmi_context *ctx = to_context(dev);
if (hdmi_ops && hdmi_ops->power_on)
return hdmi_ops->power_on(ctx->hdmi_ctx->ctx, mode);
return 0;
}
static struct exynos_drm_display_ops drm_hdmi_display_ops = {
.type = EXYNOS_DISPLAY_TYPE_HDMI,
.is_connected = drm_hdmi_is_connected,
.get_edid = drm_hdmi_get_edid,
.check_mode = drm_hdmi_check_mode,
.power_on = drm_hdmi_power_on,
};
static int drm_hdmi_enable_vblank(struct device *subdrv_dev)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
struct exynos_drm_manager *manager = subdrv->manager;
if (mixer_ops && mixer_ops->enable_vblank)
return mixer_ops->enable_vblank(ctx->mixer_ctx->ctx,
manager->pipe);
return 0;
}
static void drm_hdmi_disable_vblank(struct device *subdrv_dev)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
if (mixer_ops && mixer_ops->disable_vblank)
return mixer_ops->disable_vblank(ctx->mixer_ctx->ctx);
}
static void drm_hdmi_wait_for_vblank(struct device *subdrv_dev)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
if (mixer_ops && mixer_ops->wait_for_vblank)
mixer_ops->wait_for_vblank(ctx->mixer_ctx->ctx);
}
static void drm_hdmi_mode_fixup(struct device *subdrv_dev,
struct drm_connector *connector,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct drm_display_mode *m;
int mode_ok;
drm_mode_set_crtcinfo(adjusted_mode, 0);
mode_ok = drm_hdmi_check_mode(subdrv_dev, adjusted_mode);
/* just return if user desired mode exists. */
if (mode_ok == 0)
return;
/*
* otherwise, find the most suitable mode among modes and change it
* to adjusted_mode.
*/
list_for_each_entry(m, &connector->modes, head) {
mode_ok = drm_hdmi_check_mode(subdrv_dev, m);
if (mode_ok == 0) {
struct drm_mode_object base;
struct list_head head;
DRM_INFO("desired mode doesn't exist so\n");
DRM_INFO("use the most suitable mode among modes.\n");
DRM_DEBUG_KMS("Adjusted Mode: [%d]x[%d] [%d]Hz\n",
m->hdisplay, m->vdisplay, m->vrefresh);
/* preserve display mode header while copying. */
head = adjusted_mode->head;
base = adjusted_mode->base;
memcpy(adjusted_mode, m, sizeof(*m));
adjusted_mode->head = head;
adjusted_mode->base = base;
break;
}
}
}
static void drm_hdmi_mode_set(struct device *subdrv_dev, void *mode)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
if (hdmi_ops && hdmi_ops->mode_set)
hdmi_ops->mode_set(ctx->hdmi_ctx->ctx, mode);
}
static void drm_hdmi_get_max_resol(struct device *subdrv_dev,
unsigned int *width, unsigned int *height)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
if (hdmi_ops && hdmi_ops->get_max_resol)
hdmi_ops->get_max_resol(ctx->hdmi_ctx->ctx, width, height);
}
static void drm_hdmi_commit(struct device *subdrv_dev)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
if (hdmi_ops && hdmi_ops->commit)
hdmi_ops->commit(ctx->hdmi_ctx->ctx);
}
static void drm_hdmi_dpms(struct device *subdrv_dev, int mode)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
if (mixer_ops && mixer_ops->dpms)
mixer_ops->dpms(ctx->mixer_ctx->ctx, mode);
if (hdmi_ops && hdmi_ops->dpms)
hdmi_ops->dpms(ctx->hdmi_ctx->ctx, mode);
}
static void drm_hdmi_apply(struct device *subdrv_dev)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
int i;
for (i = 0; i < MIXER_WIN_NR; i++) {
if (!ctx->enabled[i])
continue;
if (mixer_ops && mixer_ops->win_commit)
mixer_ops->win_commit(ctx->mixer_ctx->ctx, i);
}
if (hdmi_ops && hdmi_ops->commit)
hdmi_ops->commit(ctx->hdmi_ctx->ctx);
}
static struct exynos_drm_manager_ops drm_hdmi_manager_ops = {
.dpms = drm_hdmi_dpms,
.apply = drm_hdmi_apply,
.enable_vblank = drm_hdmi_enable_vblank,
.disable_vblank = drm_hdmi_disable_vblank,
.wait_for_vblank = drm_hdmi_wait_for_vblank,
.mode_fixup = drm_hdmi_mode_fixup,
.mode_set = drm_hdmi_mode_set,
.get_max_resol = drm_hdmi_get_max_resol,
.commit = drm_hdmi_commit,
};
static void drm_mixer_mode_set(struct device *subdrv_dev,
struct exynos_drm_overlay *overlay)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
if (mixer_ops && mixer_ops->win_mode_set)
mixer_ops->win_mode_set(ctx->mixer_ctx->ctx, overlay);
}
static void drm_mixer_commit(struct device *subdrv_dev, int zpos)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos;
if (win < 0 || win >= MIXER_WIN_NR) {
DRM_ERROR("mixer window[%d] is wrong\n", win);
return;
}
if (mixer_ops && mixer_ops->win_commit)
mixer_ops->win_commit(ctx->mixer_ctx->ctx, win);
ctx->enabled[win] = true;
}
static void drm_mixer_disable(struct device *subdrv_dev, int zpos)
{
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos;
if (win < 0 || win >= MIXER_WIN_NR) {
DRM_ERROR("mixer window[%d] is wrong\n", win);
return;
}
if (mixer_ops && mixer_ops->win_disable)
mixer_ops->win_disable(ctx->mixer_ctx->ctx, win);
ctx->enabled[win] = false;
}
static struct exynos_drm_overlay_ops drm_hdmi_overlay_ops = {
.mode_set = drm_mixer_mode_set,
.commit = drm_mixer_commit,
.disable = drm_mixer_disable,
};
static struct exynos_drm_manager hdmi_manager = {
.pipe = -1,
.ops = &drm_hdmi_manager_ops,
.overlay_ops = &drm_hdmi_overlay_ops,
.display_ops = &drm_hdmi_display_ops,
};
static int hdmi_subdrv_probe(struct drm_device *drm_dev,
struct device *dev)
{
struct exynos_drm_subdrv *subdrv = to_subdrv(dev);
struct drm_hdmi_context *ctx;
if (!hdmi_ctx) {
DRM_ERROR("hdmi context not initialized.\n");
return -EFAULT;
}
if (!mixer_ctx) {
DRM_ERROR("mixer context not initialized.\n");
return -EFAULT;
}
ctx = get_ctx_from_subdrv(subdrv);
if (!ctx) {
DRM_ERROR("no drm hdmi context.\n");
return -EFAULT;
}
ctx->hdmi_ctx = hdmi_ctx;
ctx->mixer_ctx = mixer_ctx;
ctx->hdmi_ctx->drm_dev = drm_dev;
ctx->mixer_ctx->drm_dev = drm_dev;
if (mixer_ops->iommu_on)
mixer_ops->iommu_on(ctx->mixer_ctx->ctx, true);
return 0;
}
static void hdmi_subdrv_remove(struct drm_device *drm_dev, struct device *dev)
{
struct drm_hdmi_context *ctx;
struct exynos_drm_subdrv *subdrv = to_subdrv(dev);
ctx = get_ctx_from_subdrv(subdrv);
if (mixer_ops->iommu_on)
mixer_ops->iommu_on(ctx->mixer_ctx->ctx, false);
}
static int exynos_drm_hdmi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct exynos_drm_subdrv *subdrv;
struct drm_hdmi_context *ctx;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
subdrv = &ctx->subdrv;
subdrv->dev = dev;
subdrv->manager = &hdmi_manager;
subdrv->probe = hdmi_subdrv_probe;
subdrv->remove = hdmi_subdrv_remove;
platform_set_drvdata(pdev, subdrv);
exynos_drm_subdrv_register(subdrv);
return 0;
}
static int exynos_drm_hdmi_remove(struct platform_device *pdev)
{
struct drm_hdmi_context *ctx = platform_get_drvdata(pdev);
exynos_drm_subdrv_unregister(&ctx->subdrv);
return 0;
}
struct platform_driver exynos_drm_common_hdmi_driver = {
.probe = exynos_drm_hdmi_probe,
.remove = exynos_drm_hdmi_remove,
.driver = {
.name = "exynos-drm-hdmi",
.owner = THIS_MODULE,
},
};
/* exynos_drm_hdmi.h
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Authoer: Inki Dae <inki.dae@samsung.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef _EXYNOS_DRM_HDMI_H_
#define _EXYNOS_DRM_HDMI_H_
#define MIXER_WIN_NR 3
#define MIXER_DEFAULT_WIN 0
/*
* exynos hdmi common context structure.
*
* @drm_dev: pointer to drm_device.
* @ctx: pointer to the context of specific device driver.
* this context should be hdmi_context or mixer_context.
*/
struct exynos_drm_hdmi_context {
struct drm_device *drm_dev;
void *ctx;
};
struct exynos_hdmi_ops {
/* display */
bool (*is_connected)(void *ctx);
struct edid *(*get_edid)(void *ctx,
struct drm_connector *connector);
int (*check_mode)(void *ctx, struct drm_display_mode *mode);
int (*power_on)(void *ctx, int mode);
/* manager */
void (*mode_set)(void *ctx, struct drm_display_mode *mode);
void (*get_max_resol)(void *ctx, unsigned int *width,
unsigned int *height);
void (*commit)(void *ctx);
void (*dpms)(void *ctx, int mode);
};
struct exynos_mixer_ops {
/* manager */
int (*iommu_on)(void *ctx, bool enable);
int (*enable_vblank)(void *ctx, int pipe);
void (*disable_vblank)(void *ctx);
void (*wait_for_vblank)(void *ctx);
void (*dpms)(void *ctx, int mode);
/* overlay */
void (*win_mode_set)(void *ctx, struct exynos_drm_overlay *overlay);
void (*win_commit)(void *ctx, int zpos);
void (*win_disable)(void *ctx, int zpos);
/* display */
int (*check_mode)(void *ctx, struct drm_display_mode *mode);
};
void exynos_hdmi_drv_attach(struct exynos_drm_hdmi_context *ctx);
void exynos_mixer_drv_attach(struct exynos_drm_hdmi_context *ctx);
void exynos_hdmi_ops_register(struct exynos_hdmi_ops *ops);
void exynos_mixer_ops_register(struct exynos_mixer_ops *ops);
#endif
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
#include <drm/exynos_drm.h> #include <drm/exynos_drm.h>
#include "exynos_drm_drv.h" #include "exynos_drm_drv.h"
#include "exynos_drm_encoder.h" #include "exynos_drm_crtc.h"
#include "exynos_drm_fb.h" #include "exynos_drm_fb.h"
#include "exynos_drm_gem.h" #include "exynos_drm_gem.h"
#include "exynos_drm_plane.h" #include "exynos_drm_plane.h"
...@@ -139,7 +139,7 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc, ...@@ -139,7 +139,7 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc,
overlay->crtc_x, overlay->crtc_y, overlay->crtc_x, overlay->crtc_y,
overlay->crtc_width, overlay->crtc_height); overlay->crtc_width, overlay->crtc_height);
exynos_drm_fn_encoder(crtc, overlay, exynos_drm_encoder_plane_mode_set); exynos_drm_crtc_plane_mode_set(crtc, overlay);
return 0; return 0;
} }
...@@ -149,8 +149,7 @@ void exynos_plane_commit(struct drm_plane *plane) ...@@ -149,8 +149,7 @@ void exynos_plane_commit(struct drm_plane *plane)
struct exynos_plane *exynos_plane = to_exynos_plane(plane); struct exynos_plane *exynos_plane = to_exynos_plane(plane);
struct exynos_drm_overlay *overlay = &exynos_plane->overlay; struct exynos_drm_overlay *overlay = &exynos_plane->overlay;
exynos_drm_fn_encoder(plane->crtc, &overlay->zpos, exynos_drm_crtc_plane_commit(plane->crtc, overlay->zpos);
exynos_drm_encoder_plane_commit);
} }
void exynos_plane_dpms(struct drm_plane *plane, int mode) void exynos_plane_dpms(struct drm_plane *plane, int mode)
...@@ -162,17 +161,13 @@ void exynos_plane_dpms(struct drm_plane *plane, int mode) ...@@ -162,17 +161,13 @@ void exynos_plane_dpms(struct drm_plane *plane, int mode)
if (exynos_plane->enabled) if (exynos_plane->enabled)
return; return;
exynos_drm_fn_encoder(plane->crtc, &overlay->zpos, exynos_drm_crtc_plane_enable(plane->crtc, overlay->zpos);
exynos_drm_encoder_plane_enable);
exynos_plane->enabled = true; exynos_plane->enabled = true;
} else { } else {
if (!exynos_plane->enabled) if (!exynos_plane->enabled)
return; return;
exynos_drm_fn_encoder(plane->crtc, &overlay->zpos, exynos_drm_crtc_plane_disable(plane->crtc, overlay->zpos);
exynos_drm_encoder_plane_disable);
exynos_plane->enabled = false; exynos_plane->enabled = false;
} }
} }
...@@ -259,7 +254,7 @@ static void exynos_plane_attach_zpos_property(struct drm_plane *plane) ...@@ -259,7 +254,7 @@ static void exynos_plane_attach_zpos_property(struct drm_plane *plane)
} }
struct drm_plane *exynos_plane_init(struct drm_device *dev, struct drm_plane *exynos_plane_init(struct drm_device *dev,
unsigned int possible_crtcs, bool priv) unsigned long possible_crtcs, bool priv)
{ {
struct exynos_plane *exynos_plane; struct exynos_plane *exynos_plane;
int err; int err;
......
...@@ -17,4 +17,4 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc, ...@@ -17,4 +17,4 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc,
void exynos_plane_commit(struct drm_plane *plane); void exynos_plane_commit(struct drm_plane *plane);
void exynos_plane_dpms(struct drm_plane *plane, int mode); void exynos_plane_dpms(struct drm_plane *plane, int mode);
struct drm_plane *exynos_plane_init(struct drm_device *dev, struct drm_plane *exynos_plane_init(struct drm_device *dev,
unsigned int possible_crtcs, bool priv); unsigned long possible_crtcs, bool priv);
...@@ -28,7 +28,9 @@ ...@@ -28,7 +28,9 @@
/* vidi has totally three virtual windows. */ /* vidi has totally three virtual windows. */
#define WINDOWS_NR 3 #define WINDOWS_NR 3
#define get_vidi_context(dev) platform_get_drvdata(to_platform_device(dev)) #define get_vidi_mgr(dev) platform_get_drvdata(to_platform_device(dev))
#define ctx_from_connector(c) container_of(c, struct vidi_context, \
connector)
struct vidi_win_data { struct vidi_win_data {
unsigned int offset_x; unsigned int offset_x;
...@@ -45,8 +47,10 @@ struct vidi_win_data { ...@@ -45,8 +47,10 @@ struct vidi_win_data {
}; };
struct vidi_context { struct vidi_context {
struct exynos_drm_subdrv subdrv; struct drm_device *drm_dev;
struct drm_crtc *crtc; struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector connector;
struct vidi_win_data win_data[WINDOWS_NR]; struct vidi_win_data win_data[WINDOWS_NR];
struct edid *raw_edid; struct edid *raw_edid;
unsigned int clkdiv; unsigned int clkdiv;
...@@ -58,6 +62,7 @@ struct vidi_context { ...@@ -58,6 +62,7 @@ struct vidi_context {
bool direct_vblank; bool direct_vblank;
struct work_struct work; struct work_struct work;
struct mutex lock; struct mutex lock;
int pipe;
}; };
static const char fake_edid_info[] = { static const char fake_edid_info[] = {
...@@ -85,126 +90,34 @@ static const char fake_edid_info[] = { ...@@ -85,126 +90,34 @@ static const char fake_edid_info[] = {
0x00, 0x00, 0x00, 0x06 0x00, 0x00, 0x00, 0x06
}; };
static bool vidi_display_is_connected(struct device *dev) static void vidi_apply(struct exynos_drm_manager *mgr)
{ {
struct vidi_context *ctx = get_vidi_context(dev); struct vidi_context *ctx = mgr->ctx;
/*
* connection request would come from user side
* to do hotplug through specific ioctl.
*/
return ctx->connected ? true : false;
}
static struct edid *vidi_get_edid(struct device *dev,
struct drm_connector *connector)
{
struct vidi_context *ctx = get_vidi_context(dev);
struct edid *edid;
/*
* the edid data comes from user side and it would be set
* to ctx->raw_edid through specific ioctl.
*/
if (!ctx->raw_edid) {
DRM_DEBUG_KMS("raw_edid is null.\n");
return ERR_PTR(-EFAULT);
}
edid = drm_edid_duplicate(ctx->raw_edid);
if (!edid) {
DRM_DEBUG_KMS("failed to allocate edid\n");
return ERR_PTR(-ENOMEM);
}
return edid;
}
static void *vidi_get_panel(struct device *dev)
{
/* TODO. */
return NULL;
}
static int vidi_check_mode(struct device *dev, struct drm_display_mode *mode)
{
/* TODO. */
return 0;
}
static int vidi_display_power_on(struct device *dev, int mode)
{
/* TODO */
return 0;
}
static struct exynos_drm_display_ops vidi_display_ops = {
.type = EXYNOS_DISPLAY_TYPE_VIDI,
.is_connected = vidi_display_is_connected,
.get_edid = vidi_get_edid,
.get_panel = vidi_get_panel,
.check_mode = vidi_check_mode,
.power_on = vidi_display_power_on,
};
static void vidi_dpms(struct device *subdrv_dev, int mode)
{
struct vidi_context *ctx = get_vidi_context(subdrv_dev);
DRM_DEBUG_KMS("%d\n", mode);
mutex_lock(&ctx->lock);
switch (mode) {
case DRM_MODE_DPMS_ON:
/* TODO. */
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
/* TODO. */
break;
default:
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
break;
}
mutex_unlock(&ctx->lock);
}
static void vidi_apply(struct device *subdrv_dev)
{
struct vidi_context *ctx = get_vidi_context(subdrv_dev);
struct exynos_drm_manager *mgr = ctx->subdrv.manager;
struct exynos_drm_manager_ops *mgr_ops = mgr->ops; struct exynos_drm_manager_ops *mgr_ops = mgr->ops;
struct exynos_drm_overlay_ops *ovl_ops = mgr->overlay_ops;
struct vidi_win_data *win_data; struct vidi_win_data *win_data;
int i; int i;
for (i = 0; i < WINDOWS_NR; i++) { for (i = 0; i < WINDOWS_NR; i++) {
win_data = &ctx->win_data[i]; win_data = &ctx->win_data[i];
if (win_data->enabled && (ovl_ops && ovl_ops->commit)) if (win_data->enabled && (mgr_ops && mgr_ops->win_commit))
ovl_ops->commit(subdrv_dev, i); mgr_ops->win_commit(mgr, i);
} }
if (mgr_ops && mgr_ops->commit) if (mgr_ops && mgr_ops->commit)
mgr_ops->commit(subdrv_dev); mgr_ops->commit(mgr);
} }
static void vidi_commit(struct device *dev) static void vidi_commit(struct exynos_drm_manager *mgr)
{ {
struct vidi_context *ctx = get_vidi_context(dev); struct vidi_context *ctx = mgr->ctx;
if (ctx->suspended) if (ctx->suspended)
return; return;
} }
static int vidi_enable_vblank(struct device *dev) static int vidi_enable_vblank(struct exynos_drm_manager *mgr)
{ {
struct vidi_context *ctx = get_vidi_context(dev); struct vidi_context *ctx = mgr->ctx;
if (ctx->suspended) if (ctx->suspended)
return -EPERM; return -EPERM;
...@@ -217,16 +130,16 @@ static int vidi_enable_vblank(struct device *dev) ...@@ -217,16 +130,16 @@ static int vidi_enable_vblank(struct device *dev)
/* /*
* in case of page flip request, vidi_finish_pageflip function * in case of page flip request, vidi_finish_pageflip function
* will not be called because direct_vblank is true and then * will not be called because direct_vblank is true and then
* that function will be called by overlay_ops->commit callback * that function will be called by manager_ops->win_commit callback
*/ */
schedule_work(&ctx->work); schedule_work(&ctx->work);
return 0; return 0;
} }
static void vidi_disable_vblank(struct device *dev) static void vidi_disable_vblank(struct exynos_drm_manager *mgr)
{ {
struct vidi_context *ctx = get_vidi_context(dev); struct vidi_context *ctx = mgr->ctx;
if (ctx->suspended) if (ctx->suspended)
return; return;
...@@ -235,24 +148,16 @@ static void vidi_disable_vblank(struct device *dev) ...@@ -235,24 +148,16 @@ static void vidi_disable_vblank(struct device *dev)
ctx->vblank_on = false; ctx->vblank_on = false;
} }
static struct exynos_drm_manager_ops vidi_manager_ops = { static void vidi_win_mode_set(struct exynos_drm_manager *mgr,
.dpms = vidi_dpms,
.apply = vidi_apply,
.commit = vidi_commit,
.enable_vblank = vidi_enable_vblank,
.disable_vblank = vidi_disable_vblank,
};
static void vidi_win_mode_set(struct device *dev,
struct exynos_drm_overlay *overlay) struct exynos_drm_overlay *overlay)
{ {
struct vidi_context *ctx = get_vidi_context(dev); struct vidi_context *ctx = mgr->ctx;
struct vidi_win_data *win_data; struct vidi_win_data *win_data;
int win; int win;
unsigned long offset; unsigned long offset;
if (!overlay) { if (!overlay) {
dev_err(dev, "overlay is NULL\n"); DRM_ERROR("overlay is NULL\n");
return; return;
} }
...@@ -296,9 +201,9 @@ static void vidi_win_mode_set(struct device *dev, ...@@ -296,9 +201,9 @@ static void vidi_win_mode_set(struct device *dev,
overlay->fb_width, overlay->crtc_width); overlay->fb_width, overlay->crtc_width);
} }
static void vidi_win_commit(struct device *dev, int zpos) static void vidi_win_commit(struct exynos_drm_manager *mgr, int zpos)
{ {
struct vidi_context *ctx = get_vidi_context(dev); struct vidi_context *ctx = mgr->ctx;
struct vidi_win_data *win_data; struct vidi_win_data *win_data;
int win = zpos; int win = zpos;
...@@ -321,9 +226,9 @@ static void vidi_win_commit(struct device *dev, int zpos) ...@@ -321,9 +226,9 @@ static void vidi_win_commit(struct device *dev, int zpos)
schedule_work(&ctx->work); schedule_work(&ctx->work);
} }
static void vidi_win_disable(struct device *dev, int zpos) static void vidi_win_disable(struct exynos_drm_manager *mgr, int zpos)
{ {
struct vidi_context *ctx = get_vidi_context(dev); struct vidi_context *ctx = mgr->ctx;
struct vidi_win_data *win_data; struct vidi_win_data *win_data;
int win = zpos; int win = zpos;
...@@ -339,98 +244,132 @@ static void vidi_win_disable(struct device *dev, int zpos) ...@@ -339,98 +244,132 @@ static void vidi_win_disable(struct device *dev, int zpos)
/* TODO. */ /* TODO. */
} }
static struct exynos_drm_overlay_ops vidi_overlay_ops = { static int vidi_power_on(struct exynos_drm_manager *mgr, bool enable)
.mode_set = vidi_win_mode_set, {
.commit = vidi_win_commit, struct vidi_context *ctx = mgr->ctx;
.disable = vidi_win_disable,
};
static struct exynos_drm_manager vidi_manager = { DRM_DEBUG_KMS("%s\n", __FILE__);
.pipe = -1,
.ops = &vidi_manager_ops,
.overlay_ops = &vidi_overlay_ops,
.display_ops = &vidi_display_ops,
};
static void vidi_fake_vblank_handler(struct work_struct *work) if (enable != false && enable != true)
{ return -EINVAL;
struct vidi_context *ctx = container_of(work, struct vidi_context,
work);
struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
struct exynos_drm_manager *manager = subdrv->manager;
if (manager->pipe < 0) if (enable) {
return; ctx->suspended = false;
/* refresh rate is about 50Hz. */ /* if vblank was enabled status, enable it again. */
usleep_range(16000, 20000); if (test_and_clear_bit(0, &ctx->irq_flags))
vidi_enable_vblank(mgr);
vidi_apply(mgr);
} else {
ctx->suspended = true;
}
return 0;
}
static void vidi_dpms(struct exynos_drm_manager *mgr, int mode)
{
struct vidi_context *ctx = mgr->ctx;
DRM_DEBUG_KMS("%d\n", mode);
mutex_lock(&ctx->lock); mutex_lock(&ctx->lock);
if (ctx->direct_vblank) { switch (mode) {
drm_handle_vblank(subdrv->drm_dev, manager->pipe); case DRM_MODE_DPMS_ON:
ctx->direct_vblank = false; vidi_power_on(mgr, true);
mutex_unlock(&ctx->lock); break;
return; case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
vidi_power_on(mgr, false);
break;
default:
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
break;
} }
mutex_unlock(&ctx->lock); mutex_unlock(&ctx->lock);
exynos_drm_crtc_finish_pageflip(subdrv->drm_dev, manager->pipe);
} }
static int vidi_subdrv_probe(struct drm_device *drm_dev, struct device *dev) static int vidi_mgr_initialize(struct exynos_drm_manager *mgr,
struct drm_device *drm_dev, int pipe)
{ {
struct vidi_context *ctx = mgr->ctx;
DRM_ERROR("vidi initialize ct=%p dev=%p pipe=%d\n", ctx, drm_dev, pipe);
ctx->drm_dev = drm_dev;
ctx->pipe = pipe;
/* /*
* enable drm irq mode. * enable drm irq mode.
* - with irq_enabled = true, we can use the vblank feature. * - with irq_enabled = 1, we can use the vblank feature.
* *
* P.S. note that we wouldn't use drm irq handler but * P.S. note that we wouldn't use drm irq handler but
* just specific driver own one instead because * just specific driver own one instead because
* drm framework supports only one irq handler. * drm framework supports only one irq handler.
*/ */
drm_dev->irq_enabled = true; drm_dev->irq_enabled = 1;
/* /*
* with vblank_disable_allowed = true, vblank interrupt will be disabled * with vblank_disable_allowed = 1, vblank interrupt will be disabled
* by drm timer once a current process gives up ownership of * by drm timer once a current process gives up ownership of
* vblank event.(after drm_vblank_put function is called) * vblank event.(after drm_vblank_put function is called)
*/ */
drm_dev->vblank_disable_allowed = true; drm_dev->vblank_disable_allowed = 1;
return 0; return 0;
} }
static void vidi_subdrv_remove(struct drm_device *drm_dev, struct device *dev) static struct exynos_drm_manager_ops vidi_manager_ops = {
{ .initialize = vidi_mgr_initialize,
/* TODO. */ .dpms = vidi_dpms,
} .commit = vidi_commit,
.enable_vblank = vidi_enable_vblank,
.disable_vblank = vidi_disable_vblank,
.win_mode_set = vidi_win_mode_set,
.win_commit = vidi_win_commit,
.win_disable = vidi_win_disable,
};
static struct exynos_drm_manager vidi_manager = {
.type = EXYNOS_DISPLAY_TYPE_VIDI,
.ops = &vidi_manager_ops,
};
static int vidi_power_on(struct vidi_context *ctx, bool enable) static void vidi_fake_vblank_handler(struct work_struct *work)
{ {
struct exynos_drm_subdrv *subdrv = &ctx->subdrv; struct vidi_context *ctx = container_of(work, struct vidi_context,
struct device *dev = subdrv->dev; work);
if (enable) { if (ctx->pipe < 0)
ctx->suspended = false; return;
/* if vblank was enabled status, enable it again. */ /* refresh rate is about 50Hz. */
if (test_and_clear_bit(0, &ctx->irq_flags)) usleep_range(16000, 20000);
vidi_enable_vblank(dev);
vidi_apply(dev); mutex_lock(&ctx->lock);
} else {
ctx->suspended = true; if (ctx->direct_vblank) {
drm_handle_vblank(ctx->drm_dev, ctx->pipe);
ctx->direct_vblank = false;
mutex_unlock(&ctx->lock);
return;
} }
return 0; mutex_unlock(&ctx->lock);
exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);
} }
static int vidi_show_connection(struct device *dev, static int vidi_show_connection(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
int rc; int rc;
struct vidi_context *ctx = get_vidi_context(dev); struct exynos_drm_manager *mgr = get_vidi_mgr(dev);
struct vidi_context *ctx = mgr->ctx;
mutex_lock(&ctx->lock); mutex_lock(&ctx->lock);
...@@ -445,7 +384,8 @@ static int vidi_store_connection(struct device *dev, ...@@ -445,7 +384,8 @@ static int vidi_store_connection(struct device *dev,
struct device_attribute *attr, struct device_attribute *attr,
const char *buf, size_t len) const char *buf, size_t len)
{ {
struct vidi_context *ctx = get_vidi_context(dev); struct exynos_drm_manager *mgr = get_vidi_mgr(dev);
struct vidi_context *ctx = mgr->ctx;
int ret; int ret;
ret = kstrtoint(buf, 0, &ctx->connected); ret = kstrtoint(buf, 0, &ctx->connected);
...@@ -467,7 +407,7 @@ static int vidi_store_connection(struct device *dev, ...@@ -467,7 +407,7 @@ static int vidi_store_connection(struct device *dev,
DRM_DEBUG_KMS("requested connection.\n"); DRM_DEBUG_KMS("requested connection.\n");
drm_helper_hpd_irq_event(ctx->subdrv.drm_dev); drm_helper_hpd_irq_event(ctx->drm_dev);
return len; return len;
} }
...@@ -480,8 +420,7 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, ...@@ -480,8 +420,7 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data,
{ {
struct vidi_context *ctx = NULL; struct vidi_context *ctx = NULL;
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct exynos_drm_manager *manager; struct exynos_drm_display *display;
struct exynos_drm_display_ops *display_ops;
struct drm_exynos_vidi_connection *vidi = data; struct drm_exynos_vidi_connection *vidi = data;
if (!vidi) { if (!vidi) {
...@@ -496,11 +435,10 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, ...@@ -496,11 +435,10 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data,
list_for_each_entry(encoder, &drm_dev->mode_config.encoder_list, list_for_each_entry(encoder, &drm_dev->mode_config.encoder_list,
head) { head) {
manager = exynos_drm_get_manager(encoder); display = exynos_drm_get_display(encoder);
display_ops = manager->display_ops;
if (display_ops->type == EXYNOS_DISPLAY_TYPE_VIDI) { if (display->type == EXYNOS_DISPLAY_TYPE_VIDI) {
ctx = get_vidi_context(manager->dev); ctx = display->ctx;
break; break;
} }
} }
...@@ -539,16 +477,119 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, ...@@ -539,16 +477,119 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data,
} }
ctx->connected = vidi->connection; ctx->connected = vidi->connection;
drm_helper_hpd_irq_event(ctx->subdrv.drm_dev); drm_helper_hpd_irq_event(ctx->drm_dev);
return 0; return 0;
} }
static enum drm_connector_status vidi_detect(struct drm_connector *connector,
bool force)
{
struct vidi_context *ctx = ctx_from_connector(connector);
/*
* connection request would come from user side
* to do hotplug through specific ioctl.
*/
return ctx->connected ? connector_status_connected :
connector_status_disconnected;
}
static void vidi_connector_destroy(struct drm_connector *connector)
{
}
static struct drm_connector_funcs vidi_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = vidi_detect,
.destroy = vidi_connector_destroy,
};
static int vidi_get_modes(struct drm_connector *connector)
{
struct vidi_context *ctx = ctx_from_connector(connector);
struct edid *edid;
int edid_len;
/*
* the edid data comes from user side and it would be set
* to ctx->raw_edid through specific ioctl.
*/
if (!ctx->raw_edid) {
DRM_DEBUG_KMS("raw_edid is null.\n");
return -EFAULT;
}
edid_len = (1 + ctx->raw_edid->extensions) * EDID_LENGTH;
edid = kmemdup(ctx->raw_edid, edid_len, GFP_KERNEL);
if (!edid) {
DRM_DEBUG_KMS("failed to allocate edid\n");
return -ENOMEM;
}
drm_mode_connector_update_edid_property(connector, edid);
return drm_add_edid_modes(connector, edid);
}
static int vidi_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static struct drm_encoder *vidi_best_encoder(struct drm_connector *connector)
{
struct vidi_context *ctx = ctx_from_connector(connector);
return ctx->encoder;
}
static struct drm_connector_helper_funcs vidi_connector_helper_funcs = {
.get_modes = vidi_get_modes,
.mode_valid = vidi_mode_valid,
.best_encoder = vidi_best_encoder,
};
static int vidi_create_connector(struct exynos_drm_display *display,
struct drm_encoder *encoder)
{
struct vidi_context *ctx = display->ctx;
struct drm_connector *connector = &ctx->connector;
int ret;
ctx->encoder = encoder;
connector->polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(ctx->drm_dev, connector,
&vidi_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
if (ret) {
DRM_ERROR("Failed to initialize connector with drm\n");
return ret;
}
drm_connector_helper_add(connector, &vidi_connector_helper_funcs);
drm_sysfs_connector_add(connector);
drm_mode_connector_attach_encoder(connector, encoder);
return 0;
}
static struct exynos_drm_display_ops vidi_display_ops = {
.create_connector = vidi_create_connector,
};
static struct exynos_drm_display vidi_display = {
.type = EXYNOS_DISPLAY_TYPE_VIDI,
.ops = &vidi_display_ops,
};
static int vidi_probe(struct platform_device *pdev) static int vidi_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct vidi_context *ctx; struct vidi_context *ctx;
struct exynos_drm_subdrv *subdrv;
int ret; int ret;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
...@@ -559,21 +600,19 @@ static int vidi_probe(struct platform_device *pdev) ...@@ -559,21 +600,19 @@ static int vidi_probe(struct platform_device *pdev)
INIT_WORK(&ctx->work, vidi_fake_vblank_handler); INIT_WORK(&ctx->work, vidi_fake_vblank_handler);
subdrv = &ctx->subdrv; vidi_manager.ctx = ctx;
subdrv->dev = dev; vidi_display.ctx = ctx;
subdrv->manager = &vidi_manager;
subdrv->probe = vidi_subdrv_probe;
subdrv->remove = vidi_subdrv_remove;
mutex_init(&ctx->lock); mutex_init(&ctx->lock);
platform_set_drvdata(pdev, ctx); platform_set_drvdata(pdev, &vidi_manager);
ret = device_create_file(dev, &dev_attr_connection); ret = device_create_file(dev, &dev_attr_connection);
if (ret < 0) if (ret < 0)
DRM_INFO("failed to create connection sysfs.\n"); DRM_INFO("failed to create connection sysfs.\n");
exynos_drm_subdrv_register(subdrv); exynos_drm_manager_register(&vidi_manager);
exynos_drm_display_register(&vidi_display);
return 0; return 0;
} }
...@@ -582,7 +621,8 @@ static int vidi_remove(struct platform_device *pdev) ...@@ -582,7 +621,8 @@ static int vidi_remove(struct platform_device *pdev)
{ {
struct vidi_context *ctx = platform_get_drvdata(pdev); struct vidi_context *ctx = platform_get_drvdata(pdev);
exynos_drm_subdrv_unregister(&ctx->subdrv); exynos_drm_display_unregister(&vidi_display);
exynos_drm_manager_unregister(&vidi_manager);
if (ctx->raw_edid != (struct edid *)fake_edid_info) { if (ctx->raw_edid != (struct edid *)fake_edid_info) {
kfree(ctx->raw_edid); kfree(ctx->raw_edid);
...@@ -592,32 +632,11 @@ static int vidi_remove(struct platform_device *pdev) ...@@ -592,32 +632,11 @@ static int vidi_remove(struct platform_device *pdev)
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP
static int vidi_suspend(struct device *dev)
{
struct vidi_context *ctx = get_vidi_context(dev);
return vidi_power_on(ctx, false);
}
static int vidi_resume(struct device *dev)
{
struct vidi_context *ctx = get_vidi_context(dev);
return vidi_power_on(ctx, true);
}
#endif
static const struct dev_pm_ops vidi_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(vidi_suspend, vidi_resume)
};
struct platform_driver vidi_driver = { struct platform_driver vidi_driver = {
.probe = vidi_probe, .probe = vidi_probe,
.remove = vidi_remove, .remove = vidi_remove,
.driver = { .driver = {
.name = "exynos-drm-vidi", .name = "exynos-drm-vidi",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.pm = &vidi_pm_ops,
}, },
}; };
...@@ -33,38 +33,42 @@ ...@@ -33,38 +33,42 @@
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/i2c.h>
#include <linux/of_gpio.h> #include <linux/of_gpio.h>
#include <linux/hdmi.h> #include <linux/hdmi.h>
#include <drm/exynos_drm.h> #include <drm/exynos_drm.h>
#include "exynos_drm_drv.h" #include "exynos_drm_drv.h"
#include "exynos_drm_hdmi.h" #include "exynos_mixer.h"
#include "exynos_hdmi.h"
#include <linux/gpio.h> #include <linux/gpio.h>
#include <media/s5p_hdmi.h> #include <media/s5p_hdmi.h>
#define MAX_WIDTH 1920 #define get_hdmi_display(dev) platform_get_drvdata(to_platform_device(dev))
#define MAX_HEIGHT 1080 #define ctx_from_connector(c) container_of(c, struct hdmi_context, connector)
#define get_hdmi_context(dev) platform_get_drvdata(to_platform_device(dev))
/* AVI header and aspect ratio */ /* AVI header and aspect ratio */
#define HDMI_AVI_VERSION 0x02 #define HDMI_AVI_VERSION 0x02
#define HDMI_AVI_LENGTH 0x0D #define HDMI_AVI_LENGTH 0x0D
#define AVI_PIC_ASPECT_RATIO_16_9 (2 << 4)
#define AVI_SAME_AS_PIC_ASPECT_RATIO 8
/* AUI header info */ /* AUI header info */
#define HDMI_AUI_VERSION 0x01 #define HDMI_AUI_VERSION 0x01
#define HDMI_AUI_LENGTH 0x0A #define HDMI_AUI_LENGTH 0x0A
#define AVI_SAME_AS_PIC_ASPECT_RATIO 0x8
#define AVI_4_3_CENTER_RATIO 0x9
#define AVI_16_9_CENTER_RATIO 0xa
enum hdmi_type { enum hdmi_type {
HDMI_TYPE13, HDMI_TYPE13,
HDMI_TYPE14, HDMI_TYPE14,
}; };
struct hdmi_driver_data {
unsigned int type;
unsigned int is_apb_phy:1;
};
struct hdmi_resources { struct hdmi_resources {
struct clk *hdmi; struct clk *hdmi;
struct clk *sclk_hdmi; struct clk *sclk_hdmi;
...@@ -162,6 +166,7 @@ struct hdmi_v14_conf { ...@@ -162,6 +166,7 @@ struct hdmi_v14_conf {
struct hdmi_conf_regs { struct hdmi_conf_regs {
int pixel_clock; int pixel_clock;
int cea_video_id; int cea_video_id;
enum hdmi_picture_aspect aspect_ratio;
union { union {
struct hdmi_v13_conf v13_conf; struct hdmi_v13_conf v13_conf;
struct hdmi_v14_conf v14_conf; struct hdmi_v14_conf v14_conf;
...@@ -171,16 +176,17 @@ struct hdmi_conf_regs { ...@@ -171,16 +176,17 @@ struct hdmi_conf_regs {
struct hdmi_context { struct hdmi_context {
struct device *dev; struct device *dev;
struct drm_device *drm_dev; struct drm_device *drm_dev;
struct drm_connector connector;
struct drm_encoder *encoder;
bool hpd; bool hpd;
bool powered; bool powered;
bool dvi_mode; bool dvi_mode;
struct mutex hdmi_mutex; struct mutex hdmi_mutex;
void __iomem *regs; void __iomem *regs;
void *parent_ctx;
int irq; int irq;
struct i2c_client *ddc_port; struct i2c_adapter *ddc_adpt;
struct i2c_client *hdmiphy_port; struct i2c_client *hdmiphy_port;
/* current hdmiphy conf regs */ /* current hdmiphy conf regs */
...@@ -198,6 +204,14 @@ struct hdmiphy_config { ...@@ -198,6 +204,14 @@ struct hdmiphy_config {
u8 conf[32]; u8 conf[32];
}; };
struct hdmi_driver_data exynos4212_hdmi_driver_data = {
.type = HDMI_TYPE14,
};
struct hdmi_driver_data exynos5_hdmi_driver_data = {
.type = HDMI_TYPE14,
};
/* list of phy config settings */ /* list of phy config settings */
static const struct hdmiphy_config hdmiphy_v13_configs[] = { static const struct hdmiphy_config hdmiphy_v13_configs[] = {
{ {
...@@ -302,6 +316,24 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = { ...@@ -302,6 +316,24 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = {
0x54, 0xbd, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, 0x54, 0xbd, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
}, },
}, },
{
.pixel_clock = 71000000,
.conf = {
0x01, 0x91, 0x1e, 0x15, 0x40, 0x3c, 0xce, 0x08,
0x04, 0x20, 0xb2, 0xd8, 0x45, 0xa0, 0xac, 0x80,
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
0x54, 0xad, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
},
},
{
.pixel_clock = 73250000,
.conf = {
0x01, 0xd1, 0x1f, 0x15, 0x40, 0x18, 0xe9, 0x08,
0x02, 0xa0, 0xb7, 0xd8, 0x45, 0xa0, 0xac, 0x80,
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
0x54, 0xa8, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
},
},
{ {
.pixel_clock = 74176000, .pixel_clock = 74176000,
.conf = { .conf = {
...@@ -329,6 +361,15 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = { ...@@ -329,6 +361,15 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = {
0x54, 0x93, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, 0x54, 0x93, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
}, },
}, },
{
.pixel_clock = 88750000,
.conf = {
0x01, 0x91, 0x25, 0x17, 0x40, 0x30, 0xfe, 0x08,
0x06, 0x20, 0xde, 0xd8, 0x45, 0xa0, 0xac, 0x80,
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
0x54, 0x8a, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
},
},
{ {
.pixel_clock = 106500000, .pixel_clock = 106500000,
.conf = { .conf = {
...@@ -347,6 +388,24 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = { ...@@ -347,6 +388,24 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = {
0x54, 0xc7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, 0x54, 0xc7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
}, },
}, },
{
.pixel_clock = 115500000,
.conf = {
0x01, 0xd1, 0x30, 0x1a, 0x40, 0x40, 0x10, 0x04,
0x04, 0xa0, 0x21, 0xd9, 0x45, 0xa0, 0xac, 0x80,
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
0x54, 0xaa, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
},
},
{
.pixel_clock = 119000000,
.conf = {
0x01, 0x91, 0x32, 0x14, 0x40, 0x60, 0xd8, 0x08,
0x06, 0x20, 0x2a, 0xd9, 0x45, 0xa0, 0xac, 0x80,
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
0x54, 0x9d, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
},
},
{ {
.pixel_clock = 146250000, .pixel_clock = 146250000,
.conf = { .conf = {
...@@ -668,7 +727,6 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata, ...@@ -668,7 +727,6 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata,
{ {
u32 hdr_sum; u32 hdr_sum;
u8 chksum; u8 chksum;
u32 aspect_ratio;
u32 mod; u32 mod;
u32 vic; u32 vic;
...@@ -697,10 +755,28 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata, ...@@ -697,10 +755,28 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata,
AVI_ACTIVE_FORMAT_VALID | AVI_ACTIVE_FORMAT_VALID |
AVI_UNDERSCANNED_DISPLAY_VALID); AVI_UNDERSCANNED_DISPLAY_VALID);
aspect_ratio = AVI_PIC_ASPECT_RATIO_16_9; /*
* Set the aspect ratio as per the mode, mentioned in
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2), aspect_ratio | * Table 9 AVI InfoFrame Data Byte 2 of CEA-861-D Standard
*/
switch (hdata->mode_conf.aspect_ratio) {
case HDMI_PICTURE_ASPECT_4_3:
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2),
hdata->mode_conf.aspect_ratio |
AVI_4_3_CENTER_RATIO);
break;
case HDMI_PICTURE_ASPECT_16_9:
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2),
hdata->mode_conf.aspect_ratio |
AVI_16_9_CENTER_RATIO);
break;
case HDMI_PICTURE_ASPECT_NONE:
default:
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2),
hdata->mode_conf.aspect_ratio |
AVI_SAME_AS_PIC_ASPECT_RATIO); AVI_SAME_AS_PIC_ASPECT_RATIO);
break;
}
vic = hdata->mode_conf.cea_video_id; vic = hdata->mode_conf.cea_video_id;
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(4), vic); hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(4), vic);
...@@ -728,31 +804,46 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata, ...@@ -728,31 +804,46 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata,
} }
} }
static bool hdmi_is_connected(void *ctx) static enum drm_connector_status hdmi_detect(struct drm_connector *connector,
bool force)
{ {
struct hdmi_context *hdata = ctx; struct hdmi_context *hdata = ctx_from_connector(connector);
return hdata->hpd ? connector_status_connected :
connector_status_disconnected;
}
return hdata->hpd; static void hdmi_connector_destroy(struct drm_connector *connector)
{
} }
static struct edid *hdmi_get_edid(void *ctx, struct drm_connector *connector) static struct drm_connector_funcs hdmi_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = hdmi_detect,
.destroy = hdmi_connector_destroy,
};
static int hdmi_get_modes(struct drm_connector *connector)
{ {
struct edid *raw_edid; struct hdmi_context *hdata = ctx_from_connector(connector);
struct hdmi_context *hdata = ctx; struct edid *edid;
if (!hdata->ddc_port) if (!hdata->ddc_adpt)
return ERR_PTR(-ENODEV); return -ENODEV;
raw_edid = drm_get_edid(connector, hdata->ddc_port->adapter); edid = drm_get_edid(connector, hdata->ddc_adpt);
if (!raw_edid) if (!edid)
return ERR_PTR(-ENODEV); return -ENODEV;
hdata->dvi_mode = !drm_detect_hdmi_monitor(raw_edid); hdata->dvi_mode = !drm_detect_hdmi_monitor(edid);
DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n", DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n",
(hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"), (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
raw_edid->width_cm, raw_edid->height_cm); edid->width_cm, edid->height_cm);
return raw_edid; drm_mode_connector_update_edid_property(connector, edid);
return drm_add_edid_modes(connector, edid);
} }
static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock) static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock)
...@@ -777,9 +868,10 @@ static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock) ...@@ -777,9 +868,10 @@ static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock)
return -EINVAL; return -EINVAL;
} }
static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode) static int hdmi_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{ {
struct hdmi_context *hdata = ctx; struct hdmi_context *hdata = ctx_from_connector(connector);
int ret; int ret;
DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n", DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n",
...@@ -787,12 +879,103 @@ static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode) ...@@ -787,12 +879,103 @@ static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode)
(mode->flags & DRM_MODE_FLAG_INTERLACE) ? true : (mode->flags & DRM_MODE_FLAG_INTERLACE) ? true :
false, mode->clock * 1000); false, mode->clock * 1000);
ret = mixer_check_mode(mode);
if (ret)
return MODE_BAD;
ret = hdmi_find_phy_conf(hdata, mode->clock * 1000); ret = hdmi_find_phy_conf(hdata, mode->clock * 1000);
if (ret < 0) if (ret < 0)
return MODE_BAD;
return MODE_OK;
}
static struct drm_encoder *hdmi_best_encoder(struct drm_connector *connector)
{
struct hdmi_context *hdata = ctx_from_connector(connector);
return hdata->encoder;
}
static struct drm_connector_helper_funcs hdmi_connector_helper_funcs = {
.get_modes = hdmi_get_modes,
.mode_valid = hdmi_mode_valid,
.best_encoder = hdmi_best_encoder,
};
static int hdmi_create_connector(struct exynos_drm_display *display,
struct drm_encoder *encoder)
{
struct hdmi_context *hdata = display->ctx;
struct drm_connector *connector = &hdata->connector;
int ret;
hdata->encoder = encoder;
connector->interlace_allowed = true;
connector->polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(hdata->drm_dev, connector,
&hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA);
if (ret) {
DRM_ERROR("Failed to initialize connector with drm\n");
return ret; return ret;
}
drm_connector_helper_add(connector, &hdmi_connector_helper_funcs);
drm_sysfs_connector_add(connector);
drm_mode_connector_attach_encoder(connector, encoder);
return 0; return 0;
} }
static int hdmi_initialize(struct exynos_drm_display *display,
struct drm_device *drm_dev)
{
struct hdmi_context *hdata = display->ctx;
hdata->drm_dev = drm_dev;
return 0;
}
static void hdmi_mode_fixup(struct exynos_drm_display *display,
struct drm_connector *connector,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct drm_display_mode *m;
int mode_ok;
DRM_DEBUG_KMS("%s\n", __FILE__);
drm_mode_set_crtcinfo(adjusted_mode, 0);
mode_ok = hdmi_mode_valid(connector, adjusted_mode);
/* just return if user desired mode exists. */
if (mode_ok == MODE_OK)
return;
/*
* otherwise, find the most suitable mode among modes and change it
* to adjusted_mode.
*/
list_for_each_entry(m, &connector->modes, head) {
mode_ok = hdmi_mode_valid(connector, m);
if (mode_ok == MODE_OK) {
DRM_INFO("desired mode doesn't exist so\n");
DRM_INFO("use the most suitable mode among modes.\n");
DRM_DEBUG_KMS("Adjusted Mode: [%d]x[%d] [%d]Hz\n",
m->hdisplay, m->vdisplay, m->vrefresh);
drm_mode_copy(adjusted_mode, m);
break;
}
}
}
static void hdmi_set_acr(u32 freq, u8 *acr) static void hdmi_set_acr(u32 freq, u8 *acr)
{ {
u32 n, cts; u32 n, cts;
...@@ -1421,6 +1604,7 @@ static void hdmi_v13_mode_set(struct hdmi_context *hdata, ...@@ -1421,6 +1604,7 @@ static void hdmi_v13_mode_set(struct hdmi_context *hdata,
hdata->mode_conf.cea_video_id = hdata->mode_conf.cea_video_id =
drm_match_cea_mode((struct drm_display_mode *)m); drm_match_cea_mode((struct drm_display_mode *)m);
hdata->mode_conf.pixel_clock = m->clock * 1000; hdata->mode_conf.pixel_clock = m->clock * 1000;
hdata->mode_conf.aspect_ratio = m->picture_aspect_ratio;
hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay); hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay);
hdmi_set_reg(core->h_v_line, 3, (m->htotal << 12) | m->vtotal); hdmi_set_reg(core->h_v_line, 3, (m->htotal << 12) | m->vtotal);
...@@ -1517,6 +1701,7 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata, ...@@ -1517,6 +1701,7 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata,
hdata->mode_conf.cea_video_id = hdata->mode_conf.cea_video_id =
drm_match_cea_mode((struct drm_display_mode *)m); drm_match_cea_mode((struct drm_display_mode *)m);
hdata->mode_conf.pixel_clock = m->clock * 1000; hdata->mode_conf.pixel_clock = m->clock * 1000;
hdata->mode_conf.aspect_ratio = m->picture_aspect_ratio;
hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay); hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay);
hdmi_set_reg(core->v_line, 2, m->vtotal); hdmi_set_reg(core->v_line, 2, m->vtotal);
...@@ -1618,9 +1803,10 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata, ...@@ -1618,9 +1803,10 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata,
hdmi_set_reg(tg->tg_3d, 1, 0x0); hdmi_set_reg(tg->tg_3d, 1, 0x0);
} }
static void hdmi_mode_set(void *ctx, struct drm_display_mode *mode) static void hdmi_mode_set(struct exynos_drm_display *display,
struct drm_display_mode *mode)
{ {
struct hdmi_context *hdata = ctx; struct hdmi_context *hdata = display->ctx;
struct drm_display_mode *m = mode; struct drm_display_mode *m = mode;
DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%s\n", DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%s\n",
...@@ -1634,16 +1820,9 @@ static void hdmi_mode_set(void *ctx, struct drm_display_mode *mode) ...@@ -1634,16 +1820,9 @@ static void hdmi_mode_set(void *ctx, struct drm_display_mode *mode)
hdmi_v14_mode_set(hdata, mode); hdmi_v14_mode_set(hdata, mode);
} }
static void hdmi_get_max_resol(void *ctx, unsigned int *width, static void hdmi_commit(struct exynos_drm_display *display)
unsigned int *height)
{ {
*width = MAX_WIDTH; struct hdmi_context *hdata = display->ctx;
*height = MAX_HEIGHT;
}
static void hdmi_commit(void *ctx)
{
struct hdmi_context *hdata = ctx;
mutex_lock(&hdata->hdmi_mutex); mutex_lock(&hdata->hdmi_mutex);
if (!hdata->powered) { if (!hdata->powered) {
...@@ -1655,8 +1834,9 @@ static void hdmi_commit(void *ctx) ...@@ -1655,8 +1834,9 @@ static void hdmi_commit(void *ctx)
hdmi_conf_apply(hdata); hdmi_conf_apply(hdata);
} }
static void hdmi_poweron(struct hdmi_context *hdata) static void hdmi_poweron(struct exynos_drm_display *display)
{ {
struct hdmi_context *hdata = display->ctx;
struct hdmi_resources *res = &hdata->res; struct hdmi_resources *res = &hdata->res;
mutex_lock(&hdata->hdmi_mutex); mutex_lock(&hdata->hdmi_mutex);
...@@ -1669,6 +1849,8 @@ static void hdmi_poweron(struct hdmi_context *hdata) ...@@ -1669,6 +1849,8 @@ static void hdmi_poweron(struct hdmi_context *hdata)
mutex_unlock(&hdata->hdmi_mutex); mutex_unlock(&hdata->hdmi_mutex);
pm_runtime_get_sync(hdata->dev);
if (regulator_bulk_enable(res->regul_count, res->regul_bulk)) if (regulator_bulk_enable(res->regul_count, res->regul_bulk))
DRM_DEBUG_KMS("failed to enable regulator bulk\n"); DRM_DEBUG_KMS("failed to enable regulator bulk\n");
...@@ -1677,10 +1859,12 @@ static void hdmi_poweron(struct hdmi_context *hdata) ...@@ -1677,10 +1859,12 @@ static void hdmi_poweron(struct hdmi_context *hdata)
clk_prepare_enable(res->sclk_hdmi); clk_prepare_enable(res->sclk_hdmi);
hdmiphy_poweron(hdata); hdmiphy_poweron(hdata);
hdmi_commit(display);
} }
static void hdmi_poweroff(struct hdmi_context *hdata) static void hdmi_poweroff(struct exynos_drm_display *display)
{ {
struct hdmi_context *hdata = display->ctx;
struct hdmi_resources *res = &hdata->res; struct hdmi_resources *res = &hdata->res;
mutex_lock(&hdata->hdmi_mutex); mutex_lock(&hdata->hdmi_mutex);
...@@ -1700,30 +1884,27 @@ static void hdmi_poweroff(struct hdmi_context *hdata) ...@@ -1700,30 +1884,27 @@ static void hdmi_poweroff(struct hdmi_context *hdata)
clk_disable_unprepare(res->hdmiphy); clk_disable_unprepare(res->hdmiphy);
regulator_bulk_disable(res->regul_count, res->regul_bulk); regulator_bulk_disable(res->regul_count, res->regul_bulk);
mutex_lock(&hdata->hdmi_mutex); pm_runtime_put_sync(hdata->dev);
mutex_lock(&hdata->hdmi_mutex);
hdata->powered = false; hdata->powered = false;
out: out:
mutex_unlock(&hdata->hdmi_mutex); mutex_unlock(&hdata->hdmi_mutex);
} }
static void hdmi_dpms(void *ctx, int mode) static void hdmi_dpms(struct exynos_drm_display *display, int mode)
{ {
struct hdmi_context *hdata = ctx;
DRM_DEBUG_KMS("mode %d\n", mode); DRM_DEBUG_KMS("mode %d\n", mode);
switch (mode) { switch (mode) {
case DRM_MODE_DPMS_ON: case DRM_MODE_DPMS_ON:
if (pm_runtime_suspended(hdata->dev)) hdmi_poweron(display);
pm_runtime_get_sync(hdata->dev);
break; break;
case DRM_MODE_DPMS_STANDBY: case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND: case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF: case DRM_MODE_DPMS_OFF:
if (!pm_runtime_suspended(hdata->dev)) hdmi_poweroff(display);
pm_runtime_put_sync(hdata->dev);
break; break;
default: default:
DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode); DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode);
...@@ -1731,30 +1912,30 @@ static void hdmi_dpms(void *ctx, int mode) ...@@ -1731,30 +1912,30 @@ static void hdmi_dpms(void *ctx, int mode)
} }
} }
static struct exynos_hdmi_ops hdmi_ops = { static struct exynos_drm_display_ops hdmi_display_ops = {
/* display */ .initialize = hdmi_initialize,
.is_connected = hdmi_is_connected, .create_connector = hdmi_create_connector,
.get_edid = hdmi_get_edid, .mode_fixup = hdmi_mode_fixup,
.check_mode = hdmi_check_mode,
/* manager */
.mode_set = hdmi_mode_set, .mode_set = hdmi_mode_set,
.get_max_resol = hdmi_get_max_resol,
.commit = hdmi_commit,
.dpms = hdmi_dpms, .dpms = hdmi_dpms,
.commit = hdmi_commit,
};
static struct exynos_drm_display hdmi_display = {
.type = EXYNOS_DISPLAY_TYPE_HDMI,
.ops = &hdmi_display_ops,
}; };
static irqreturn_t hdmi_irq_thread(int irq, void *arg) static irqreturn_t hdmi_irq_thread(int irq, void *arg)
{ {
struct exynos_drm_hdmi_context *ctx = arg; struct hdmi_context *hdata = arg;
struct hdmi_context *hdata = ctx->ctx;
mutex_lock(&hdata->hdmi_mutex); mutex_lock(&hdata->hdmi_mutex);
hdata->hpd = gpio_get_value(hdata->hpd_gpio); hdata->hpd = gpio_get_value(hdata->hpd_gpio);
mutex_unlock(&hdata->hdmi_mutex); mutex_unlock(&hdata->hdmi_mutex);
if (ctx->drm_dev) if (hdata->drm_dev)
drm_helper_hpd_irq_event(ctx->drm_dev); drm_helper_hpd_irq_event(hdata->drm_dev);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -1830,20 +2011,6 @@ static int hdmi_resources_init(struct hdmi_context *hdata) ...@@ -1830,20 +2011,6 @@ static int hdmi_resources_init(struct hdmi_context *hdata)
return -ENODEV; return -ENODEV;
} }
static struct i2c_client *hdmi_ddc, *hdmi_hdmiphy;
void hdmi_attach_ddc_client(struct i2c_client *ddc)
{
if (ddc)
hdmi_ddc = ddc;
}
void hdmi_attach_hdmiphy_client(struct i2c_client *hdmiphy)
{
if (hdmiphy)
hdmi_hdmiphy = hdmiphy;
}
static struct s5p_hdmi_platform_data *drm_hdmi_dt_parse_pdata static struct s5p_hdmi_platform_data *drm_hdmi_dt_parse_pdata
(struct device *dev) (struct device *dev)
{ {
...@@ -1871,10 +2038,10 @@ static struct s5p_hdmi_platform_data *drm_hdmi_dt_parse_pdata ...@@ -1871,10 +2038,10 @@ static struct s5p_hdmi_platform_data *drm_hdmi_dt_parse_pdata
static struct of_device_id hdmi_match_types[] = { static struct of_device_id hdmi_match_types[] = {
{ {
.compatible = "samsung,exynos5-hdmi", .compatible = "samsung,exynos5-hdmi",
.data = (void *)HDMI_TYPE14, .data = &exynos5_hdmi_driver_data,
}, { }, {
.compatible = "samsung,exynos4212-hdmi", .compatible = "samsung,exynos4212-hdmi",
.data = (void *)HDMI_TYPE14, .data = &exynos4212_hdmi_driver_data,
}, { }, {
/* end node */ /* end node */
} }
...@@ -1883,11 +2050,12 @@ static struct of_device_id hdmi_match_types[] = { ...@@ -1883,11 +2050,12 @@ static struct of_device_id hdmi_match_types[] = {
static int hdmi_probe(struct platform_device *pdev) static int hdmi_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct exynos_drm_hdmi_context *drm_hdmi_ctx;
struct hdmi_context *hdata; struct hdmi_context *hdata;
struct s5p_hdmi_platform_data *pdata; struct s5p_hdmi_platform_data *pdata;
struct resource *res; struct resource *res;
const struct of_device_id *match; const struct of_device_id *match;
struct device_node *ddc_node, *phy_node;
struct hdmi_driver_data *drv_data;
int ret; int ret;
if (!dev->of_node) if (!dev->of_node)
...@@ -1897,25 +2065,20 @@ static int hdmi_probe(struct platform_device *pdev) ...@@ -1897,25 +2065,20 @@ static int hdmi_probe(struct platform_device *pdev)
if (!pdata) if (!pdata)
return -EINVAL; return -EINVAL;
drm_hdmi_ctx = devm_kzalloc(dev, sizeof(*drm_hdmi_ctx), GFP_KERNEL);
if (!drm_hdmi_ctx)
return -ENOMEM;
hdata = devm_kzalloc(dev, sizeof(struct hdmi_context), GFP_KERNEL); hdata = devm_kzalloc(dev, sizeof(struct hdmi_context), GFP_KERNEL);
if (!hdata) if (!hdata)
return -ENOMEM; return -ENOMEM;
mutex_init(&hdata->hdmi_mutex); mutex_init(&hdata->hdmi_mutex);
drm_hdmi_ctx->ctx = (void *)hdata; platform_set_drvdata(pdev, &hdmi_display);
hdata->parent_ctx = (void *)drm_hdmi_ctx;
platform_set_drvdata(pdev, drm_hdmi_ctx);
match = of_match_node(hdmi_match_types, dev->of_node); match = of_match_node(hdmi_match_types, dev->of_node);
if (!match) if (!match)
return -ENODEV; return -ENODEV;
hdata->type = (enum hdmi_type)match->data;
drv_data = (struct hdmi_driver_data *)match->data;
hdata->type = drv_data->type;
hdata->hpd_gpio = pdata->hpd_gpio; hdata->hpd_gpio = pdata->hpd_gpio;
hdata->dev = dev; hdata->dev = dev;
...@@ -1938,21 +2101,34 @@ static int hdmi_probe(struct platform_device *pdev) ...@@ -1938,21 +2101,34 @@ static int hdmi_probe(struct platform_device *pdev)
} }
/* DDC i2c driver */ /* DDC i2c driver */
if (i2c_add_driver(&ddc_driver)) { ddc_node = of_parse_phandle(dev->of_node, "ddc", 0);
DRM_ERROR("failed to register ddc i2c driver\n"); if (!ddc_node) {
return -ENOENT; DRM_ERROR("Failed to find ddc node in device tree\n");
return -ENODEV;
}
hdata->ddc_adpt = of_find_i2c_adapter_by_node(ddc_node);
if (!hdata->ddc_adpt) {
DRM_ERROR("Failed to get ddc i2c adapter by node\n");
return -ENODEV;
} }
hdata->ddc_port = hdmi_ddc; /* Not support APB PHY yet. */
if (drv_data->is_apb_phy)
return -EPERM;
/* hdmiphy i2c driver */ /* hdmiphy i2c driver */
if (i2c_add_driver(&hdmiphy_driver)) { phy_node = of_parse_phandle(dev->of_node, "phy", 0);
DRM_ERROR("failed to register hdmiphy i2c driver\n"); if (!phy_node) {
ret = -ENOENT; DRM_ERROR("Failed to find hdmiphy node in device tree\n");
ret = -ENODEV;
goto err_ddc;
}
hdata->hdmiphy_port = of_find_i2c_device_by_node(phy_node);
if (!hdata->hdmiphy_port) {
DRM_ERROR("Failed to get hdmi phy i2c client from node\n");
ret = -ENODEV;
goto err_ddc; goto err_ddc;
} }
hdata->hdmiphy_port = hdmi_hdmiphy;
hdata->irq = gpio_to_irq(hdata->hpd_gpio); hdata->irq = gpio_to_irq(hdata->hpd_gpio);
if (hdata->irq < 0) { if (hdata->irq < 0) {
...@@ -1966,119 +2142,45 @@ static int hdmi_probe(struct platform_device *pdev) ...@@ -1966,119 +2142,45 @@ static int hdmi_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(dev, hdata->irq, NULL, ret = devm_request_threaded_irq(dev, hdata->irq, NULL,
hdmi_irq_thread, IRQF_TRIGGER_RISING | hdmi_irq_thread, IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"hdmi", drm_hdmi_ctx); "hdmi", hdata);
if (ret) { if (ret) {
DRM_ERROR("failed to register hdmi interrupt\n"); DRM_ERROR("failed to register hdmi interrupt\n");
goto err_hdmiphy; goto err_hdmiphy;
} }
/* Attach HDMI Driver to common hdmi. */
exynos_hdmi_drv_attach(drm_hdmi_ctx);
/* register specific callbacks to common hdmi. */
exynos_hdmi_ops_register(&hdmi_ops);
pm_runtime_enable(dev); pm_runtime_enable(dev);
hdmi_display.ctx = hdata;
exynos_drm_display_register(&hdmi_display);
return 0; return 0;
err_hdmiphy: err_hdmiphy:
i2c_del_driver(&hdmiphy_driver); put_device(&hdata->hdmiphy_port->dev);
err_ddc: err_ddc:
i2c_del_driver(&ddc_driver); put_device(&hdata->ddc_adpt->dev);
return ret; return ret;
} }
static int hdmi_remove(struct platform_device *pdev) static int hdmi_remove(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct exynos_drm_display *display = get_hdmi_display(dev);
struct hdmi_context *hdata = display->ctx;
pm_runtime_disable(dev); put_device(&hdata->hdmiphy_port->dev);
put_device(&hdata->ddc_adpt->dev);
/* hdmiphy i2c driver */ pm_runtime_disable(&pdev->dev);
i2c_del_driver(&hdmiphy_driver);
/* DDC i2c driver */
i2c_del_driver(&ddc_driver);
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP
static int hdmi_suspend(struct device *dev)
{
struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev);
struct hdmi_context *hdata = ctx->ctx;
disable_irq(hdata->irq);
hdata->hpd = false;
if (ctx->drm_dev)
drm_helper_hpd_irq_event(ctx->drm_dev);
if (pm_runtime_suspended(dev)) {
DRM_DEBUG_KMS("Already suspended\n");
return 0;
}
hdmi_poweroff(hdata);
return 0;
}
static int hdmi_resume(struct device *dev)
{
struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev);
struct hdmi_context *hdata = ctx->ctx;
hdata->hpd = gpio_get_value(hdata->hpd_gpio);
enable_irq(hdata->irq);
if (!pm_runtime_suspended(dev)) {
DRM_DEBUG_KMS("Already resumed\n");
return 0;
}
hdmi_poweron(hdata);
return 0;
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int hdmi_runtime_suspend(struct device *dev)
{
struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev);
struct hdmi_context *hdata = ctx->ctx;
hdmi_poweroff(hdata);
return 0;
}
static int hdmi_runtime_resume(struct device *dev)
{
struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev);
struct hdmi_context *hdata = ctx->ctx;
hdmi_poweron(hdata);
return 0;
}
#endif
static const struct dev_pm_ops hdmi_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(hdmi_suspend, hdmi_resume)
SET_RUNTIME_PM_OPS(hdmi_runtime_suspend, hdmi_runtime_resume, NULL)
};
struct platform_driver hdmi_driver = { struct platform_driver hdmi_driver = {
.probe = hdmi_probe, .probe = hdmi_probe,
.remove = hdmi_remove, .remove = hdmi_remove,
.driver = { .driver = {
.name = "exynos-hdmi", .name = "exynos-hdmi",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.pm = &hdmi_pm_ops,
.of_match_table = hdmi_match_types, .of_match_table = hdmi_match_types,
}, },
}; };
...@@ -36,10 +36,13 @@ ...@@ -36,10 +36,13 @@
#include "exynos_drm_drv.h" #include "exynos_drm_drv.h"
#include "exynos_drm_crtc.h" #include "exynos_drm_crtc.h"
#include "exynos_drm_hdmi.h"
#include "exynos_drm_iommu.h" #include "exynos_drm_iommu.h"
#include "exynos_mixer.h"
#define get_mixer_context(dev) platform_get_drvdata(to_platform_device(dev)) #define get_mixer_manager(dev) platform_get_drvdata(to_platform_device(dev))
#define MIXER_WIN_NR 3
#define MIXER_DEFAULT_WIN 0
struct hdmi_win_data { struct hdmi_win_data {
dma_addr_t dma_addr; dma_addr_t dma_addr;
...@@ -82,6 +85,7 @@ enum mixer_version_id { ...@@ -82,6 +85,7 @@ enum mixer_version_id {
}; };
struct mixer_context { struct mixer_context {
struct platform_device *pdev;
struct device *dev; struct device *dev;
struct drm_device *drm_dev; struct drm_device *drm_dev;
int pipe; int pipe;
...@@ -94,7 +98,6 @@ struct mixer_context { ...@@ -94,7 +98,6 @@ struct mixer_context {
struct mixer_resources mixer_res; struct mixer_resources mixer_res;
struct hdmi_win_data win_data[MIXER_WIN_NR]; struct hdmi_win_data win_data[MIXER_WIN_NR];
enum mixer_version_id mxr_ver; enum mixer_version_id mxr_ver;
void *parent_ctx;
wait_queue_head_t wait_vsync_queue; wait_queue_head_t wait_vsync_queue;
atomic_t wait_vsync_event; atomic_t wait_vsync_event;
}; };
...@@ -685,31 +688,196 @@ static void mixer_win_reset(struct mixer_context *ctx) ...@@ -685,31 +688,196 @@ static void mixer_win_reset(struct mixer_context *ctx)
spin_unlock_irqrestore(&res->reg_slock, flags); spin_unlock_irqrestore(&res->reg_slock, flags);
} }
static int mixer_iommu_on(void *ctx, bool enable) static irqreturn_t mixer_irq_handler(int irq, void *arg)
{ {
struct exynos_drm_hdmi_context *drm_hdmi_ctx; struct mixer_context *ctx = arg;
struct mixer_context *mdata = ctx; struct mixer_resources *res = &ctx->mixer_res;
struct drm_device *drm_dev; u32 val, base, shadow;
spin_lock(&res->reg_slock);
/* read interrupt status for handling and clearing flags for VSYNC */
val = mixer_reg_read(res, MXR_INT_STATUS);
/* handling VSYNC */
if (val & MXR_INT_STATUS_VSYNC) {
/* interlace scan need to check shadow register */
if (ctx->interlace) {
base = mixer_reg_read(res, MXR_GRAPHIC_BASE(0));
shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0));
if (base != shadow)
goto out;
base = mixer_reg_read(res, MXR_GRAPHIC_BASE(1));
shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1));
if (base != shadow)
goto out;
}
drm_hdmi_ctx = mdata->parent_ctx; drm_handle_vblank(ctx->drm_dev, ctx->pipe);
drm_dev = drm_hdmi_ctx->drm_dev; exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);
if (is_drm_iommu_supported(drm_dev)) { /* set wait vsync event to zero and wake up queue. */
if (enable) if (atomic_read(&ctx->wait_vsync_event)) {
return drm_iommu_attach_device(drm_dev, mdata->dev); atomic_set(&ctx->wait_vsync_event, 0);
wake_up(&ctx->wait_vsync_queue);
}
}
drm_iommu_detach_device(drm_dev, mdata->dev); out:
/* clear interrupts */
if (~val & MXR_INT_EN_VSYNC) {
/* vsync interrupt use different bit for read and clear */
val &= ~MXR_INT_EN_VSYNC;
val |= MXR_INT_CLEAR_VSYNC;
} }
mixer_reg_write(res, MXR_INT_STATUS, val);
spin_unlock(&res->reg_slock);
return IRQ_HANDLED;
}
static int mixer_resources_init(struct mixer_context *mixer_ctx)
{
struct device *dev = &mixer_ctx->pdev->dev;
struct mixer_resources *mixer_res = &mixer_ctx->mixer_res;
struct resource *res;
int ret;
spin_lock_init(&mixer_res->reg_slock);
mixer_res->mixer = devm_clk_get(dev, "mixer");
if (IS_ERR(mixer_res->mixer)) {
dev_err(dev, "failed to get clock 'mixer'\n");
return -ENODEV;
}
mixer_res->sclk_hdmi = devm_clk_get(dev, "sclk_hdmi");
if (IS_ERR(mixer_res->sclk_hdmi)) {
dev_err(dev, "failed to get clock 'sclk_hdmi'\n");
return -ENODEV;
}
res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(dev, "get memory resource failed.\n");
return -ENXIO;
}
mixer_res->mixer_regs = devm_ioremap(dev, res->start,
resource_size(res));
if (mixer_res->mixer_regs == NULL) {
dev_err(dev, "register mapping failed.\n");
return -ENXIO;
}
res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_IRQ, 0);
if (res == NULL) {
dev_err(dev, "get interrupt resource failed.\n");
return -ENXIO;
}
ret = devm_request_irq(dev, res->start, mixer_irq_handler,
0, "drm_mixer", mixer_ctx);
if (ret) {
dev_err(dev, "request interrupt failed.\n");
return ret;
}
mixer_res->irq = res->start;
return 0; return 0;
} }
static int mixer_enable_vblank(void *ctx, int pipe) static int vp_resources_init(struct mixer_context *mixer_ctx)
{ {
struct mixer_context *mixer_ctx = ctx; struct device *dev = &mixer_ctx->pdev->dev;
struct mixer_resources *res = &mixer_ctx->mixer_res; struct mixer_resources *mixer_res = &mixer_ctx->mixer_res;
struct resource *res;
mixer_res->vp = devm_clk_get(dev, "vp");
if (IS_ERR(mixer_res->vp)) {
dev_err(dev, "failed to get clock 'vp'\n");
return -ENODEV;
}
mixer_res->sclk_mixer = devm_clk_get(dev, "sclk_mixer");
if (IS_ERR(mixer_res->sclk_mixer)) {
dev_err(dev, "failed to get clock 'sclk_mixer'\n");
return -ENODEV;
}
mixer_res->sclk_dac = devm_clk_get(dev, "sclk_dac");
if (IS_ERR(mixer_res->sclk_dac)) {
dev_err(dev, "failed to get clock 'sclk_dac'\n");
return -ENODEV;
}
if (mixer_res->sclk_hdmi)
clk_set_parent(mixer_res->sclk_mixer, mixer_res->sclk_hdmi);
res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_MEM, 1);
if (res == NULL) {
dev_err(dev, "get memory resource failed.\n");
return -ENXIO;
}
mixer_res->vp_regs = devm_ioremap(dev, res->start,
resource_size(res));
if (mixer_res->vp_regs == NULL) {
dev_err(dev, "register mapping failed.\n");
return -ENXIO;
}
return 0;
}
static int mixer_initialize(struct exynos_drm_manager *mgr,
struct drm_device *drm_dev, int pipe)
{
int ret;
struct mixer_context *mixer_ctx = mgr->ctx;
mixer_ctx->drm_dev = drm_dev;
mixer_ctx->pipe = pipe; mixer_ctx->pipe = pipe;
/* acquire resources: regs, irqs, clocks */
ret = mixer_resources_init(mixer_ctx);
if (ret) {
DRM_ERROR("mixer_resources_init failed ret=%d\n", ret);
return ret;
}
if (mixer_ctx->vp_enabled) {
/* acquire vp resources: regs, irqs, clocks */
ret = vp_resources_init(mixer_ctx);
if (ret) {
DRM_ERROR("vp_resources_init failed ret=%d\n", ret);
return ret;
}
}
if (!is_drm_iommu_supported(mixer_ctx->drm_dev))
return 0;
return drm_iommu_attach_device(mixer_ctx->drm_dev, mixer_ctx->dev);
}
static void mixer_mgr_remove(struct exynos_drm_manager *mgr)
{
struct mixer_context *mixer_ctx = mgr->ctx;
if (is_drm_iommu_supported(mixer_ctx->drm_dev))
drm_iommu_detach_device(mixer_ctx->drm_dev, mixer_ctx->dev);
}
static int mixer_enable_vblank(struct exynos_drm_manager *mgr)
{
struct mixer_context *mixer_ctx = mgr->ctx;
struct mixer_resources *res = &mixer_ctx->mixer_res;
if (!mixer_ctx->powered) {
mixer_ctx->int_en |= MXR_INT_EN_VSYNC;
return 0;
}
/* enable vsync interrupt */ /* enable vsync interrupt */
mixer_reg_writemask(res, MXR_INT_EN, MXR_INT_EN_VSYNC, mixer_reg_writemask(res, MXR_INT_EN, MXR_INT_EN_VSYNC,
MXR_INT_EN_VSYNC); MXR_INT_EN_VSYNC);
...@@ -717,19 +885,19 @@ static int mixer_enable_vblank(void *ctx, int pipe) ...@@ -717,19 +885,19 @@ static int mixer_enable_vblank(void *ctx, int pipe)
return 0; return 0;
} }
static void mixer_disable_vblank(void *ctx) static void mixer_disable_vblank(struct exynos_drm_manager *mgr)
{ {
struct mixer_context *mixer_ctx = ctx; struct mixer_context *mixer_ctx = mgr->ctx;
struct mixer_resources *res = &mixer_ctx->mixer_res; struct mixer_resources *res = &mixer_ctx->mixer_res;
/* disable vsync interrupt */ /* disable vsync interrupt */
mixer_reg_writemask(res, MXR_INT_EN, 0, MXR_INT_EN_VSYNC); mixer_reg_writemask(res, MXR_INT_EN, 0, MXR_INT_EN_VSYNC);
} }
static void mixer_win_mode_set(void *ctx, static void mixer_win_mode_set(struct exynos_drm_manager *mgr,
struct exynos_drm_overlay *overlay) struct exynos_drm_overlay *overlay)
{ {
struct mixer_context *mixer_ctx = ctx; struct mixer_context *mixer_ctx = mgr->ctx;
struct hdmi_win_data *win_data; struct hdmi_win_data *win_data;
int win; int win;
...@@ -778,9 +946,10 @@ static void mixer_win_mode_set(void *ctx, ...@@ -778,9 +946,10 @@ static void mixer_win_mode_set(void *ctx,
win_data->scan_flags = overlay->scan_flag; win_data->scan_flags = overlay->scan_flag;
} }
static void mixer_win_commit(void *ctx, int win) static void mixer_win_commit(struct exynos_drm_manager *mgr, int zpos)
{ {
struct mixer_context *mixer_ctx = ctx; struct mixer_context *mixer_ctx = mgr->ctx;
int win = zpos == DEFAULT_ZPOS ? MIXER_DEFAULT_WIN : zpos;
DRM_DEBUG_KMS("win: %d\n", win); DRM_DEBUG_KMS("win: %d\n", win);
...@@ -799,10 +968,11 @@ static void mixer_win_commit(void *ctx, int win) ...@@ -799,10 +968,11 @@ static void mixer_win_commit(void *ctx, int win)
mixer_ctx->win_data[win].enabled = true; mixer_ctx->win_data[win].enabled = true;
} }
static void mixer_win_disable(void *ctx, int win) static void mixer_win_disable(struct exynos_drm_manager *mgr, int zpos)
{ {
struct mixer_context *mixer_ctx = ctx; struct mixer_context *mixer_ctx = mgr->ctx;
struct mixer_resources *res = &mixer_ctx->mixer_res; struct mixer_resources *res = &mixer_ctx->mixer_res;
int win = zpos == DEFAULT_ZPOS ? MIXER_DEFAULT_WIN : zpos;
unsigned long flags; unsigned long flags;
DRM_DEBUG_KMS("win: %d\n", win); DRM_DEBUG_KMS("win: %d\n", win);
...@@ -826,32 +996,9 @@ static void mixer_win_disable(void *ctx, int win) ...@@ -826,32 +996,9 @@ static void mixer_win_disable(void *ctx, int win)
mixer_ctx->win_data[win].enabled = false; mixer_ctx->win_data[win].enabled = false;
} }
static int mixer_check_mode(void *ctx, struct drm_display_mode *mode) static void mixer_wait_for_vblank(struct exynos_drm_manager *mgr)
{ {
struct mixer_context *mixer_ctx = ctx; struct mixer_context *mixer_ctx = mgr->ctx;
u32 w, h;
w = mode->hdisplay;
h = mode->vdisplay;
DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d\n",
mode->hdisplay, mode->vdisplay, mode->vrefresh,
(mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0);
if (mixer_ctx->mxr_ver == MXR_VER_0_0_0_16 ||
mixer_ctx->mxr_ver == MXR_VER_128_0_0_184)
return 0;
if ((w >= 464 && w <= 720 && h >= 261 && h <= 576) ||
(w >= 1024 && w <= 1280 && h >= 576 && h <= 720) ||
(w >= 1664 && w <= 1920 && h >= 936 && h <= 1080))
return 0;
return -EINVAL;
}
static void mixer_wait_for_vblank(void *ctx)
{
struct mixer_context *mixer_ctx = ctx;
mutex_lock(&mixer_ctx->mixer_mutex); mutex_lock(&mixer_ctx->mixer_mutex);
if (!mixer_ctx->powered) { if (!mixer_ctx->powered) {
...@@ -872,21 +1019,23 @@ static void mixer_wait_for_vblank(void *ctx) ...@@ -872,21 +1019,23 @@ static void mixer_wait_for_vblank(void *ctx)
DRM_DEBUG_KMS("vblank wait timed out.\n"); DRM_DEBUG_KMS("vblank wait timed out.\n");
} }
static void mixer_window_suspend(struct mixer_context *ctx) static void mixer_window_suspend(struct exynos_drm_manager *mgr)
{ {
struct mixer_context *ctx = mgr->ctx;
struct hdmi_win_data *win_data; struct hdmi_win_data *win_data;
int i; int i;
for (i = 0; i < MIXER_WIN_NR; i++) { for (i = 0; i < MIXER_WIN_NR; i++) {
win_data = &ctx->win_data[i]; win_data = &ctx->win_data[i];
win_data->resume = win_data->enabled; win_data->resume = win_data->enabled;
mixer_win_disable(ctx, i); mixer_win_disable(mgr, i);
} }
mixer_wait_for_vblank(ctx); mixer_wait_for_vblank(mgr);
} }
static void mixer_window_resume(struct mixer_context *ctx) static void mixer_window_resume(struct exynos_drm_manager *mgr)
{ {
struct mixer_context *ctx = mgr->ctx;
struct hdmi_win_data *win_data; struct hdmi_win_data *win_data;
int i; int i;
...@@ -894,11 +1043,14 @@ static void mixer_window_resume(struct mixer_context *ctx) ...@@ -894,11 +1043,14 @@ static void mixer_window_resume(struct mixer_context *ctx)
win_data = &ctx->win_data[i]; win_data = &ctx->win_data[i];
win_data->enabled = win_data->resume; win_data->enabled = win_data->resume;
win_data->resume = false; win_data->resume = false;
if (win_data->enabled)
mixer_win_commit(mgr, i);
} }
} }
static void mixer_poweron(struct mixer_context *ctx) static void mixer_poweron(struct exynos_drm_manager *mgr)
{ {
struct mixer_context *ctx = mgr->ctx;
struct mixer_resources *res = &ctx->mixer_res; struct mixer_resources *res = &ctx->mixer_res;
mutex_lock(&ctx->mixer_mutex); mutex_lock(&ctx->mixer_mutex);
...@@ -909,6 +1061,8 @@ static void mixer_poweron(struct mixer_context *ctx) ...@@ -909,6 +1061,8 @@ static void mixer_poweron(struct mixer_context *ctx)
ctx->powered = true; ctx->powered = true;
mutex_unlock(&ctx->mixer_mutex); mutex_unlock(&ctx->mixer_mutex);
pm_runtime_get_sync(ctx->dev);
clk_prepare_enable(res->mixer); clk_prepare_enable(res->mixer);
if (ctx->vp_enabled) { if (ctx->vp_enabled) {
clk_prepare_enable(res->vp); clk_prepare_enable(res->vp);
...@@ -918,11 +1072,12 @@ static void mixer_poweron(struct mixer_context *ctx) ...@@ -918,11 +1072,12 @@ static void mixer_poweron(struct mixer_context *ctx)
mixer_reg_write(res, MXR_INT_EN, ctx->int_en); mixer_reg_write(res, MXR_INT_EN, ctx->int_en);
mixer_win_reset(ctx); mixer_win_reset(ctx);
mixer_window_resume(ctx); mixer_window_resume(mgr);
} }
static void mixer_poweroff(struct mixer_context *ctx) static void mixer_poweroff(struct exynos_drm_manager *mgr)
{ {
struct mixer_context *ctx = mgr->ctx;
struct mixer_resources *res = &ctx->mixer_res; struct mixer_resources *res = &ctx->mixer_res;
mutex_lock(&ctx->mixer_mutex); mutex_lock(&ctx->mixer_mutex);
...@@ -930,7 +1085,7 @@ static void mixer_poweroff(struct mixer_context *ctx) ...@@ -930,7 +1085,7 @@ static void mixer_poweroff(struct mixer_context *ctx)
goto out; goto out;
mutex_unlock(&ctx->mixer_mutex); mutex_unlock(&ctx->mixer_mutex);
mixer_window_suspend(ctx); mixer_window_suspend(mgr);
ctx->int_en = mixer_reg_read(res, MXR_INT_EN); ctx->int_en = mixer_reg_read(res, MXR_INT_EN);
...@@ -940,6 +1095,8 @@ static void mixer_poweroff(struct mixer_context *ctx) ...@@ -940,6 +1095,8 @@ static void mixer_poweroff(struct mixer_context *ctx)
clk_disable_unprepare(res->sclk_mixer); clk_disable_unprepare(res->sclk_mixer);
} }
pm_runtime_put_sync(ctx->dev);
mutex_lock(&ctx->mixer_mutex); mutex_lock(&ctx->mixer_mutex);
ctx->powered = false; ctx->powered = false;
...@@ -947,20 +1104,16 @@ static void mixer_poweroff(struct mixer_context *ctx) ...@@ -947,20 +1104,16 @@ static void mixer_poweroff(struct mixer_context *ctx)
mutex_unlock(&ctx->mixer_mutex); mutex_unlock(&ctx->mixer_mutex);
} }
static void mixer_dpms(void *ctx, int mode) static void mixer_dpms(struct exynos_drm_manager *mgr, int mode)
{ {
struct mixer_context *mixer_ctx = ctx;
switch (mode) { switch (mode) {
case DRM_MODE_DPMS_ON: case DRM_MODE_DPMS_ON:
if (pm_runtime_suspended(mixer_ctx->dev)) mixer_poweron(mgr);
pm_runtime_get_sync(mixer_ctx->dev);
break; break;
case DRM_MODE_DPMS_STANDBY: case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND: case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF: case DRM_MODE_DPMS_OFF:
if (!pm_runtime_suspended(mixer_ctx->dev)) mixer_poweroff(mgr);
pm_runtime_put_sync(mixer_ctx->dev);
break; break;
default: default:
DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode); DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode);
...@@ -968,169 +1121,42 @@ static void mixer_dpms(void *ctx, int mode) ...@@ -968,169 +1121,42 @@ static void mixer_dpms(void *ctx, int mode)
} }
} }
static struct exynos_mixer_ops mixer_ops = { /* Only valid for Mixer version 16.0.33.0 */
/* manager */ int mixer_check_mode(struct drm_display_mode *mode)
.iommu_on = mixer_iommu_on,
.enable_vblank = mixer_enable_vblank,
.disable_vblank = mixer_disable_vblank,
.wait_for_vblank = mixer_wait_for_vblank,
.dpms = mixer_dpms,
/* overlay */
.win_mode_set = mixer_win_mode_set,
.win_commit = mixer_win_commit,
.win_disable = mixer_win_disable,
/* display */
.check_mode = mixer_check_mode,
};
static irqreturn_t mixer_irq_handler(int irq, void *arg)
{ {
struct exynos_drm_hdmi_context *drm_hdmi_ctx = arg; u32 w, h;
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
struct mixer_resources *res = &ctx->mixer_res;
u32 val, base, shadow;
spin_lock(&res->reg_slock);
/* read interrupt status for handling and clearing flags for VSYNC */
val = mixer_reg_read(res, MXR_INT_STATUS);
/* handling VSYNC */
if (val & MXR_INT_STATUS_VSYNC) {
/* interlace scan need to check shadow register */
if (ctx->interlace) {
base = mixer_reg_read(res, MXR_GRAPHIC_BASE(0));
shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0));
if (base != shadow)
goto out;
base = mixer_reg_read(res, MXR_GRAPHIC_BASE(1));
shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1));
if (base != shadow)
goto out;
}
drm_handle_vblank(drm_hdmi_ctx->drm_dev, ctx->pipe);
exynos_drm_crtc_finish_pageflip(drm_hdmi_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:
/* clear interrupts */
if (~val & MXR_INT_EN_VSYNC) {
/* vsync interrupt use different bit for read and clear */
val &= ~MXR_INT_EN_VSYNC;
val |= MXR_INT_CLEAR_VSYNC;
}
mixer_reg_write(res, MXR_INT_STATUS, val);
spin_unlock(&res->reg_slock);
return IRQ_HANDLED;
}
static int mixer_resources_init(struct exynos_drm_hdmi_context *ctx,
struct platform_device *pdev)
{
struct mixer_context *mixer_ctx = ctx->ctx;
struct device *dev = &pdev->dev;
struct mixer_resources *mixer_res = &mixer_ctx->mixer_res;
struct resource *res;
int ret;
spin_lock_init(&mixer_res->reg_slock);
mixer_res->mixer = devm_clk_get(dev, "mixer");
if (IS_ERR(mixer_res->mixer)) {
dev_err(dev, "failed to get clock 'mixer'\n");
return -ENODEV;
}
mixer_res->sclk_hdmi = devm_clk_get(dev, "sclk_hdmi");
if (IS_ERR(mixer_res->sclk_hdmi)) {
dev_err(dev, "failed to get clock 'sclk_hdmi'\n");
return -ENODEV;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(dev, "get memory resource failed.\n");
return -ENXIO;
}
mixer_res->mixer_regs = devm_ioremap(dev, res->start,
resource_size(res));
if (mixer_res->mixer_regs == NULL) {
dev_err(dev, "register mapping failed.\n");
return -ENXIO;
}
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); w = mode->hdisplay;
if (res == NULL) { h = mode->vdisplay;
dev_err(dev, "get interrupt resource failed.\n");
return -ENXIO;
}
ret = devm_request_irq(dev, res->start, mixer_irq_handler, DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d\n",
0, "drm_mixer", ctx); mode->hdisplay, mode->vdisplay, mode->vrefresh,
if (ret) { (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0);
dev_err(dev, "request interrupt failed.\n");
return ret;
}
mixer_res->irq = res->start;
if ((w >= 464 && w <= 720 && h >= 261 && h <= 576) ||
(w >= 1024 && w <= 1280 && h >= 576 && h <= 720) ||
(w >= 1664 && w <= 1920 && h >= 936 && h <= 1080))
return 0; return 0;
}
static int vp_resources_init(struct exynos_drm_hdmi_context *ctx, return -EINVAL;
struct platform_device *pdev) }
{
struct mixer_context *mixer_ctx = ctx->ctx;
struct device *dev = &pdev->dev;
struct mixer_resources *mixer_res = &mixer_ctx->mixer_res;
struct resource *res;
mixer_res->vp = devm_clk_get(dev, "vp");
if (IS_ERR(mixer_res->vp)) {
dev_err(dev, "failed to get clock 'vp'\n");
return -ENODEV;
}
mixer_res->sclk_mixer = devm_clk_get(dev, "sclk_mixer");
if (IS_ERR(mixer_res->sclk_mixer)) {
dev_err(dev, "failed to get clock 'sclk_mixer'\n");
return -ENODEV;
}
mixer_res->sclk_dac = devm_clk_get(dev, "sclk_dac");
if (IS_ERR(mixer_res->sclk_dac)) {
dev_err(dev, "failed to get clock 'sclk_dac'\n");
return -ENODEV;
}
if (mixer_res->sclk_hdmi)
clk_set_parent(mixer_res->sclk_mixer, mixer_res->sclk_hdmi);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res == NULL) {
dev_err(dev, "get memory resource failed.\n");
return -ENXIO;
}
mixer_res->vp_regs = devm_ioremap(dev, res->start, static struct exynos_drm_manager_ops mixer_manager_ops = {
resource_size(res)); .initialize = mixer_initialize,
if (mixer_res->vp_regs == NULL) { .remove = mixer_mgr_remove,
dev_err(dev, "register mapping failed.\n"); .dpms = mixer_dpms,
return -ENXIO; .enable_vblank = mixer_enable_vblank,
} .disable_vblank = mixer_disable_vblank,
.wait_for_vblank = mixer_wait_for_vblank,
.win_mode_set = mixer_win_mode_set,
.win_commit = mixer_win_commit,
.win_disable = mixer_win_disable,
};
return 0; static struct exynos_drm_manager mixer_manager = {
} .type = EXYNOS_DISPLAY_TYPE_HDMI,
.ops = &mixer_manager_ops,
};
static struct mixer_drv_data exynos5420_mxr_drv_data = { static struct mixer_drv_data exynos5420_mxr_drv_data = {
.version = MXR_VER_128_0_0_184, .version = MXR_VER_128_0_0_184,
...@@ -1177,21 +1203,16 @@ static struct of_device_id mixer_match_types[] = { ...@@ -1177,21 +1203,16 @@ static struct of_device_id mixer_match_types[] = {
static int mixer_probe(struct platform_device *pdev) static int mixer_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct exynos_drm_hdmi_context *drm_hdmi_ctx;
struct mixer_context *ctx; struct mixer_context *ctx;
struct mixer_drv_data *drv; struct mixer_drv_data *drv;
int ret;
dev_info(dev, "probe start\n"); dev_info(dev, "probe start\n");
drm_hdmi_ctx = devm_kzalloc(dev, sizeof(*drm_hdmi_ctx), ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
GFP_KERNEL); if (!ctx) {
if (!drm_hdmi_ctx) DRM_ERROR("failed to alloc mixer context.\n");
return -ENOMEM;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM; return -ENOMEM;
}
mutex_init(&ctx->mixer_mutex); mutex_init(&ctx->mixer_mutex);
...@@ -1204,46 +1225,20 @@ static int mixer_probe(struct platform_device *pdev) ...@@ -1204,46 +1225,20 @@ static int mixer_probe(struct platform_device *pdev)
platform_get_device_id(pdev)->driver_data; platform_get_device_id(pdev)->driver_data;
} }
ctx->pdev = pdev;
ctx->dev = dev; ctx->dev = dev;
ctx->parent_ctx = (void *)drm_hdmi_ctx;
drm_hdmi_ctx->ctx = (void *)ctx;
ctx->vp_enabled = drv->is_vp_enabled; ctx->vp_enabled = drv->is_vp_enabled;
ctx->mxr_ver = drv->version; ctx->mxr_ver = drv->version;
init_waitqueue_head(&ctx->wait_vsync_queue); init_waitqueue_head(&ctx->wait_vsync_queue);
atomic_set(&ctx->wait_vsync_event, 0); atomic_set(&ctx->wait_vsync_event, 0);
platform_set_drvdata(pdev, drm_hdmi_ctx); mixer_manager.ctx = ctx;
platform_set_drvdata(pdev, &mixer_manager);
/* acquire resources: regs, irqs, clocks */ exynos_drm_manager_register(&mixer_manager);
ret = mixer_resources_init(drm_hdmi_ctx, pdev);
if (ret) {
DRM_ERROR("mixer_resources_init failed\n");
goto fail;
}
if (ctx->vp_enabled) {
/* acquire vp resources: regs, irqs, clocks */
ret = vp_resources_init(drm_hdmi_ctx, pdev);
if (ret) {
DRM_ERROR("vp_resources_init failed\n");
goto fail;
}
}
/* attach mixer driver to common hdmi. */
exynos_mixer_drv_attach(drm_hdmi_ctx);
/* register specific callback point to common hdmi. */
exynos_mixer_ops_register(&mixer_ops);
pm_runtime_enable(dev); pm_runtime_enable(dev);
return 0; return 0;
fail:
dev_info(dev, "probe failed\n");
return ret;
} }
static int mixer_remove(struct platform_device *pdev) static int mixer_remove(struct platform_device *pdev)
...@@ -1255,70 +1250,10 @@ static int mixer_remove(struct platform_device *pdev) ...@@ -1255,70 +1250,10 @@ static int mixer_remove(struct platform_device *pdev)
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP
static int mixer_suspend(struct device *dev)
{
struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev);
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
if (pm_runtime_suspended(dev)) {
DRM_DEBUG_KMS("Already suspended\n");
return 0;
}
mixer_poweroff(ctx);
return 0;
}
static int mixer_resume(struct device *dev)
{
struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev);
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
if (!pm_runtime_suspended(dev)) {
DRM_DEBUG_KMS("Already resumed\n");
return 0;
}
mixer_poweron(ctx);
return 0;
}
#endif
#ifdef CONFIG_PM_RUNTIME
static int mixer_runtime_suspend(struct device *dev)
{
struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev);
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
mixer_poweroff(ctx);
return 0;
}
static int mixer_runtime_resume(struct device *dev)
{
struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev);
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
mixer_poweron(ctx);
return 0;
}
#endif
static const struct dev_pm_ops mixer_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mixer_suspend, mixer_resume)
SET_RUNTIME_PM_OPS(mixer_runtime_suspend, mixer_runtime_resume, NULL)
};
struct platform_driver mixer_driver = { struct platform_driver mixer_driver = {
.driver = { .driver = {
.name = "exynos-mixer", .name = "exynos-mixer",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.pm = &mixer_pm_ops,
.of_match_table = mixer_match_types, .of_match_table = mixer_match_types,
}, },
.probe = mixer_probe, .probe = mixer_probe,
......
/*
* Copyright (C) 2013 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#ifndef _EXYNOS_MIXER_H_
#define _EXYNOS_MIXER_H_
/* This function returns 0 if the given timing is valid for the mixer */
int mixer_check_mode(struct drm_display_mode *mode);
#endif
...@@ -29,11 +29,4 @@ config EXYNOS_LCD_S6E8AX0 ...@@ -29,11 +29,4 @@ config EXYNOS_LCD_S6E8AX0
If you have an S6E8AX0 MIPI AMOLED LCD Panel, say Y to enable its If you have an S6E8AX0 MIPI AMOLED LCD Panel, say Y to enable its
LCD control driver. LCD control driver.
config EXYNOS_DP
bool "EXYNOS DP driver support"
depends on OF && ARCH_EXYNOS
default n
help
This enables support for DP device.
endif # EXYNOS_VIDEO endif # EXYNOS_VIDEO
...@@ -5,4 +5,3 @@ ...@@ -5,4 +5,3 @@
obj-$(CONFIG_EXYNOS_MIPI_DSI) += exynos_mipi_dsi.o exynos_mipi_dsi_common.o \ obj-$(CONFIG_EXYNOS_MIPI_DSI) += exynos_mipi_dsi.o exynos_mipi_dsi_common.o \
exynos_mipi_dsi_lowlevel.o exynos_mipi_dsi_lowlevel.o
obj-$(CONFIG_EXYNOS_LCD_S6E8AX0) += s6e8ax0.o obj-$(CONFIG_EXYNOS_LCD_S6E8AX0) += s6e8ax0.o
obj-$(CONFIG_EXYNOS_DP) += exynos_dp_core.o exynos_dp_reg.o
/*
* Copyright (C) 2013 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#ifndef _DRM_BRIDGE_PTN3460_H_
#define _DRM_BRIDGE_PTN3460_H_
struct drm_device;
struct drm_encoder;
struct i2c_client;
struct device_node;
#ifdef CONFIG_DRM_PTN3460
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node);
#else
static inline int ptn3460_init(struct drm_device *dev,
struct drm_encoder *encoder, struct i2c_client *client,
struct device_node *node)
{
return 0;
}
#endif
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment