diff options
26 files changed, 5483 insertions, 0 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 626bc0cb1046..39573c5f7518 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig | |||
@@ -223,3 +223,5 @@ source "drivers/gpu/drm/omapdrm/Kconfig" | |||
223 | source "drivers/gpu/drm/tilcdc/Kconfig" | 223 | source "drivers/gpu/drm/tilcdc/Kconfig" |
224 | 224 | ||
225 | source "drivers/gpu/drm/qxl/Kconfig" | 225 | source "drivers/gpu/drm/qxl/Kconfig" |
226 | |||
227 | source "drivers/gpu/drm/msm/Kconfig" | ||
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 7b2343a2f5eb..f089adfe70ee 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile | |||
@@ -54,4 +54,5 @@ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ | |||
54 | obj-$(CONFIG_DRM_OMAP) += omapdrm/ | 54 | obj-$(CONFIG_DRM_OMAP) += omapdrm/ |
55 | obj-$(CONFIG_DRM_TILCDC) += tilcdc/ | 55 | obj-$(CONFIG_DRM_TILCDC) += tilcdc/ |
56 | obj-$(CONFIG_DRM_QXL) += qxl/ | 56 | obj-$(CONFIG_DRM_QXL) += qxl/ |
57 | obj-$(CONFIG_DRM_MSM) += msm/ | ||
57 | obj-y += i2c/ | 58 | obj-y += i2c/ |
diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig new file mode 100644 index 000000000000..a06c19cc56f8 --- /dev/null +++ b/drivers/gpu/drm/msm/Kconfig | |||
@@ -0,0 +1,34 @@ | |||
1 | |||
2 | config DRM_MSM | ||
3 | tristate "MSM DRM" | ||
4 | depends on DRM | ||
5 | depends on ARCH_MSM | ||
6 | depends on ARCH_MSM8960 | ||
7 | select DRM_KMS_HELPER | ||
8 | select SHMEM | ||
9 | select TMPFS | ||
10 | default y | ||
11 | help | ||
12 | DRM/KMS driver for MSM/snapdragon. | ||
13 | |||
14 | config DRM_MSM_FBDEV | ||
15 | bool "Enable legacy fbdev support for MSM modesetting driver" | ||
16 | depends on DRM_MSM | ||
17 | select FB_SYS_FILLRECT | ||
18 | select FB_SYS_COPYAREA | ||
19 | select FB_SYS_IMAGEBLIT | ||
20 | select FB_SYS_FOPS | ||
21 | default y | ||
22 | help | ||
23 | Choose this option if you have a need for the legacy fbdev | ||
24 | support. Note that this support also provide the linux console | ||
25 | support on top of the MSM modesetting driver. | ||
26 | |||
27 | config DRM_MSM_REGISTER_LOGGING | ||
28 | bool "MSM DRM register logging" | ||
29 | depends on DRM_MSM | ||
30 | default n | ||
31 | help | ||
32 | Compile in support for logging register reads/writes in a format | ||
33 | that can be parsed by envytools demsm tool. If enabled, register | ||
34 | logging can be switched on via msm.reglog=y module param. | ||
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile new file mode 100644 index 000000000000..4068122a9377 --- /dev/null +++ b/drivers/gpu/drm/msm/Makefile | |||
@@ -0,0 +1,25 @@ | |||
1 | ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/msm | ||
2 | ifeq (, $(findstring -W,$(EXTRA_CFLAGS))) | ||
3 | ccflags-y += -Werror | ||
4 | endif | ||
5 | |||
6 | msm-y := \ | ||
7 | hdmi/hdmi.o \ | ||
8 | hdmi/hdmi_connector.o \ | ||
9 | hdmi/hdmi_i2c.o \ | ||
10 | hdmi/hdmi_phy_8960.o \ | ||
11 | hdmi/hdmi_phy_8x60.o \ | ||
12 | mdp4/mdp4_crtc.o \ | ||
13 | mdp4/mdp4_dtv_encoder.o \ | ||
14 | mdp4/mdp4_format.o \ | ||
15 | mdp4/mdp4_irq.o \ | ||
16 | mdp4/mdp4_kms.o \ | ||
17 | mdp4/mdp4_plane.o \ | ||
18 | msm_connector.o \ | ||
19 | msm_drv.o \ | ||
20 | msm_fb.o \ | ||
21 | msm_gem.o | ||
22 | |||
23 | msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o | ||
24 | |||
25 | obj-$(CONFIG_DRM_MSM) += msm.o | ||
diff --git a/drivers/gpu/drm/msm/NOTES b/drivers/gpu/drm/msm/NOTES new file mode 100644 index 000000000000..e036f6c1db94 --- /dev/null +++ b/drivers/gpu/drm/msm/NOTES | |||
@@ -0,0 +1,69 @@ | |||
1 | NOTES about msm drm/kms driver: | ||
2 | |||
3 | In the current snapdragon SoC's, we have (at least) 3 different | ||
4 | display controller blocks at play: | ||
5 | + MDP3 - ?? seems to be what is on geeksphone peak device | ||
6 | + MDP4 - S3 (APQ8060, touchpad), S4-pro (APQ8064, nexus4 & ifc6410) | ||
7 | + MDSS - snapdragon 800 | ||
8 | |||
9 | (I don't have a completely clear picture on which display controller | ||
10 | maps to which part #) | ||
11 | |||
12 | Plus a handful of blocks around them for HDMI/DSI/etc output. | ||
13 | |||
14 | And on gpu side of things: | ||
15 | + zero, one, or two 2d cores (z180) | ||
16 | + and either a2xx or a3xx 3d core. | ||
17 | |||
18 | But, HDMI/DSI/etc blocks seem like they can be shared across multiple | ||
19 | display controller blocks. And I for sure don't want to have to deal | ||
20 | with N different kms devices from xf86-video-freedreno. Plus, it | ||
21 | seems like we can do some clever tricks like use GPU to trigger | ||
22 | pageflip after rendering completes (ie. have the kms/crtc code build | ||
23 | up gpu cmdstream to update scanout and write FLUSH register after). | ||
24 | |||
25 | So, the approach is one drm driver, with some modularity. Different | ||
26 | 'struct msm_kms' implementations, depending on display controller. | ||
27 | And one or more 'struct msm_gpu' for the various different gpu sub- | ||
28 | modules. | ||
29 | |||
30 | (Second part is not implemented yet. So far this is just basic KMS | ||
31 | driver, and not exposing any custom ioctls to userspace for now.) | ||
32 | |||
33 | The kms module provides the plane, crtc, and encoder objects, and | ||
34 | loads whatever connectors are appropriate. | ||
35 | |||
36 | For MDP4, the mapping is: | ||
37 | |||
38 | plane -> PIPE{RGBn,VGn} \ | ||
39 | crtc -> OVLP{n} + DMA{P,S,E} (??) |-> MDP "device" | ||
40 | encoder -> DTV/LCDC/DSI (within MDP4) / | ||
41 | connector -> HDMI/DSI/etc --> other device(s) | ||
42 | |||
43 | Since the irq's that drm core mostly cares about are vblank/framedone, | ||
44 | we'll let msm_mdp4_kms provide the irq install/uninstall/etc functions | ||
45 | and treat the MDP4 block's irq as "the" irq. Even though the connectors | ||
46 | may have their own irqs which they install themselves. For this reason | ||
47 | the display controller is the "master" device. | ||
48 | |||
49 | Each connector probably ends up being a separate device, just for the | ||
50 | logistics of finding/mapping io region, irq, etc. Idealy we would | ||
51 | have a better way than just stashing the platform device in a global | ||
52 | (ie. like DT super-node.. but I don't have any snapdragon hw yet that | ||
53 | is using DT). | ||
54 | |||
55 | Note that so far I've not been able to get any docs on the hw, and it | ||
56 | seems that access to such docs would prevent me from working on the | ||
57 | freedreno gallium driver. So there may be some mistakes in register | ||
58 | names (I had to invent a few, since no sufficient hint was given in | ||
59 | the downstream android fbdev driver), bitfield sizes, etc. My current | ||
60 | state of understanding the registers is given in the envytools rnndb | ||
61 | files at: | ||
62 | |||
63 | https://github.com/freedreno/envytools/tree/master/rnndb | ||
64 | (the mdp4/hdmi/dsi directories) | ||
65 | |||
66 | These files are used both for a parser tool (in the same tree) to | ||
67 | parse logged register reads/writes (both from downstream android fbdev | ||
68 | driver, and this driver with register logging enabled), as well as to | ||
69 | generate the register level headers. | ||
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c new file mode 100644 index 000000000000..12ecfb928f75 --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c | |||
@@ -0,0 +1,235 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "hdmi.h" | ||
19 | |||
20 | static struct platform_device *hdmi_pdev; | ||
21 | |||
22 | void hdmi_set_mode(struct hdmi *hdmi, bool power_on) | ||
23 | { | ||
24 | uint32_t ctrl = 0; | ||
25 | |||
26 | if (power_on) { | ||
27 | ctrl |= HDMI_CTRL_ENABLE; | ||
28 | if (!hdmi->hdmi_mode) { | ||
29 | ctrl |= HDMI_CTRL_HDMI; | ||
30 | hdmi_write(hdmi, REG_HDMI_CTRL, ctrl); | ||
31 | ctrl &= ~HDMI_CTRL_HDMI; | ||
32 | } else { | ||
33 | ctrl |= HDMI_CTRL_HDMI; | ||
34 | } | ||
35 | } else { | ||
36 | ctrl = HDMI_CTRL_HDMI; | ||
37 | } | ||
38 | |||
39 | hdmi_write(hdmi, REG_HDMI_CTRL, ctrl); | ||
40 | DBG("HDMI Core: %s, HDMI_CTRL=0x%08x", | ||
41 | power_on ? "Enable" : "Disable", ctrl); | ||
42 | } | ||
43 | |||
44 | static irqreturn_t hdmi_irq(int irq, void *dev_id) | ||
45 | { | ||
46 | struct hdmi *hdmi = dev_id; | ||
47 | |||
48 | /* Process HPD: */ | ||
49 | hdmi_connector_irq(hdmi->connector); | ||
50 | |||
51 | /* Process DDC: */ | ||
52 | hdmi_i2c_irq(hdmi->i2c); | ||
53 | |||
54 | /* TODO audio.. */ | ||
55 | |||
56 | return IRQ_HANDLED; | ||
57 | } | ||
58 | |||
59 | void hdmi_destroy(struct hdmi *hdmi) | ||
60 | { | ||
61 | struct hdmi_phy *phy = hdmi->phy; | ||
62 | |||
63 | if (phy) | ||
64 | phy->funcs->destroy(phy); | ||
65 | |||
66 | if (hdmi->i2c) | ||
67 | hdmi_i2c_destroy(hdmi->i2c); | ||
68 | |||
69 | put_device(&hdmi->pdev->dev); | ||
70 | } | ||
71 | |||
72 | /* initialize connector */ | ||
73 | int hdmi_init(struct hdmi *hdmi, struct drm_device *dev, | ||
74 | struct drm_connector *connector) | ||
75 | { | ||
76 | struct platform_device *pdev = hdmi_pdev; | ||
77 | struct hdmi_platform_config *config; | ||
78 | int ret; | ||
79 | |||
80 | if (!pdev) { | ||
81 | dev_err(dev->dev, "no hdmi device\n"); | ||
82 | ret = -ENXIO; | ||
83 | goto fail; | ||
84 | } | ||
85 | |||
86 | config = pdev->dev.platform_data; | ||
87 | |||
88 | get_device(&pdev->dev); | ||
89 | |||
90 | hdmi->dev = dev; | ||
91 | hdmi->pdev = pdev; | ||
92 | hdmi->connector = connector; | ||
93 | |||
94 | /* not sure about which phy maps to which msm.. probably I miss some */ | ||
95 | if (config->phy_init) | ||
96 | hdmi->phy = config->phy_init(hdmi); | ||
97 | else | ||
98 | hdmi->phy = ERR_PTR(-ENXIO); | ||
99 | |||
100 | if (IS_ERR(hdmi->phy)) { | ||
101 | ret = PTR_ERR(hdmi->phy); | ||
102 | dev_err(dev->dev, "failed to load phy: %d\n", ret); | ||
103 | hdmi->phy = NULL; | ||
104 | goto fail; | ||
105 | } | ||
106 | |||
107 | hdmi->mmio = msm_ioremap(pdev, "hdmi_msm_hdmi_addr", "HDMI"); | ||
108 | if (IS_ERR(hdmi->mmio)) { | ||
109 | ret = PTR_ERR(hdmi->mmio); | ||
110 | goto fail; | ||
111 | } | ||
112 | |||
113 | hdmi->mvs = devm_regulator_get(&pdev->dev, "8901_hdmi_mvs"); | ||
114 | if (IS_ERR(hdmi->mvs)) | ||
115 | hdmi->mvs = devm_regulator_get(&pdev->dev, "hdmi_mvs"); | ||
116 | if (IS_ERR(hdmi->mvs)) { | ||
117 | ret = PTR_ERR(hdmi->mvs); | ||
118 | dev_err(dev->dev, "failed to get mvs regulator: %d\n", ret); | ||
119 | goto fail; | ||
120 | } | ||
121 | |||
122 | hdmi->mpp0 = devm_regulator_get(&pdev->dev, "8901_mpp0"); | ||
123 | if (IS_ERR(hdmi->mpp0)) | ||
124 | hdmi->mpp0 = NULL; | ||
125 | |||
126 | hdmi->clk = devm_clk_get(&pdev->dev, "core_clk"); | ||
127 | if (IS_ERR(hdmi->clk)) { | ||
128 | ret = PTR_ERR(hdmi->clk); | ||
129 | dev_err(dev->dev, "failed to get 'clk': %d\n", ret); | ||
130 | goto fail; | ||
131 | } | ||
132 | |||
133 | hdmi->m_pclk = devm_clk_get(&pdev->dev, "master_iface_clk"); | ||
134 | if (IS_ERR(hdmi->m_pclk)) { | ||
135 | ret = PTR_ERR(hdmi->m_pclk); | ||
136 | dev_err(dev->dev, "failed to get 'm_pclk': %d\n", ret); | ||
137 | goto fail; | ||
138 | } | ||
139 | |||
140 | hdmi->s_pclk = devm_clk_get(&pdev->dev, "slave_iface_clk"); | ||
141 | if (IS_ERR(hdmi->s_pclk)) { | ||
142 | ret = PTR_ERR(hdmi->s_pclk); | ||
143 | dev_err(dev->dev, "failed to get 's_pclk': %d\n", ret); | ||
144 | goto fail; | ||
145 | } | ||
146 | |||
147 | hdmi->i2c = hdmi_i2c_init(hdmi); | ||
148 | if (IS_ERR(hdmi->i2c)) { | ||
149 | ret = PTR_ERR(hdmi->i2c); | ||
150 | dev_err(dev->dev, "failed to get i2c: %d\n", ret); | ||
151 | hdmi->i2c = NULL; | ||
152 | goto fail; | ||
153 | } | ||
154 | |||
155 | hdmi->irq = platform_get_irq(pdev, 0); | ||
156 | if (hdmi->irq < 0) { | ||
157 | ret = hdmi->irq; | ||
158 | dev_err(dev->dev, "failed to get irq: %d\n", ret); | ||
159 | goto fail; | ||
160 | } | ||
161 | |||
162 | ret = devm_request_threaded_irq(&pdev->dev, hdmi->irq, | ||
163 | NULL, hdmi_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, | ||
164 | "hdmi_isr", hdmi); | ||
165 | if (ret < 0) { | ||
166 | dev_err(dev->dev, "failed to request IRQ%u: %d\n", | ||
167 | hdmi->irq, ret); | ||
168 | goto fail; | ||
169 | } | ||
170 | |||
171 | return 0; | ||
172 | |||
173 | fail: | ||
174 | if (hdmi) | ||
175 | hdmi_destroy(hdmi); | ||
176 | |||
177 | return ret; | ||
178 | } | ||
179 | |||
180 | /* | ||
181 | * The hdmi device: | ||
182 | */ | ||
183 | |||
184 | static int hdmi_dev_probe(struct platform_device *pdev) | ||
185 | { | ||
186 | static struct hdmi_platform_config config = {}; | ||
187 | #ifdef CONFIG_OF | ||
188 | /* TODO */ | ||
189 | #else | ||
190 | if (cpu_is_apq8064()) { | ||
191 | config.phy_init = hdmi_phy_8960_init; | ||
192 | config.ddc_clk_gpio = 70; | ||
193 | config.ddc_data_gpio = 71; | ||
194 | config.hpd_gpio = 72; | ||
195 | config.pmic_gpio = 13 + NR_GPIO_IRQS; | ||
196 | } else if (cpu_is_msm8960()) { | ||
197 | config.phy_init = hdmi_phy_8960_init; | ||
198 | config.ddc_clk_gpio = 100; | ||
199 | config.ddc_data_gpio = 101; | ||
200 | config.hpd_gpio = 102; | ||
201 | config.pmic_gpio = -1; | ||
202 | } else if (cpu_is_msm8x60()) { | ||
203 | config.phy_init = hdmi_phy_8x60_init; | ||
204 | config.ddc_clk_gpio = 170; | ||
205 | config.ddc_data_gpio = 171; | ||
206 | config.hpd_gpio = 172; | ||
207 | config.pmic_gpio = -1; | ||
208 | } | ||
209 | #endif | ||
210 | pdev->dev.platform_data = &config; | ||
211 | hdmi_pdev = pdev; | ||
212 | return 0; | ||
213 | } | ||
214 | |||
215 | static int hdmi_dev_remove(struct platform_device *pdev) | ||
216 | { | ||
217 | hdmi_pdev = NULL; | ||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | static struct platform_driver hdmi_driver = { | ||
222 | .probe = hdmi_dev_probe, | ||
223 | .remove = hdmi_dev_remove, | ||
224 | .driver.name = "hdmi_msm", | ||
225 | }; | ||
226 | |||
227 | void __init hdmi_register(void) | ||
228 | { | ||
229 | platform_driver_register(&hdmi_driver); | ||
230 | } | ||
231 | |||
232 | void __exit hdmi_unregister(void) | ||
233 | { | ||
234 | platform_driver_unregister(&hdmi_driver); | ||
235 | } | ||
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h new file mode 100644 index 000000000000..34703fea22ca --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi.h | |||
@@ -0,0 +1,112 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #ifndef __HDMI_CONNECTOR_H__ | ||
19 | #define __HDMI_CONNECTOR_H__ | ||
20 | |||
21 | #include <linux/i2c.h> | ||
22 | #include <linux/clk.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/regulator/consumer.h> | ||
25 | |||
26 | #include "msm_drv.h" | ||
27 | #include "hdmi.xml.h" | ||
28 | |||
29 | |||
30 | struct hdmi_phy; | ||
31 | |||
32 | struct hdmi { | ||
33 | struct drm_device *dev; | ||
34 | struct platform_device *pdev; | ||
35 | |||
36 | void __iomem *mmio; | ||
37 | |||
38 | struct regulator *mvs; /* HDMI_5V */ | ||
39 | struct regulator *mpp0; /* External 5V */ | ||
40 | |||
41 | struct clk *clk; | ||
42 | struct clk *m_pclk; | ||
43 | struct clk *s_pclk; | ||
44 | |||
45 | struct hdmi_phy *phy; | ||
46 | struct i2c_adapter *i2c; | ||
47 | struct drm_connector *connector; | ||
48 | |||
49 | bool hdmi_mode; /* are we in hdmi mode? */ | ||
50 | |||
51 | int irq; | ||
52 | }; | ||
53 | |||
54 | /* platform config data (ie. from DT, or pdata) */ | ||
55 | struct hdmi_platform_config { | ||
56 | struct hdmi_phy *(*phy_init)(struct hdmi *hdmi); | ||
57 | int ddc_clk_gpio, ddc_data_gpio, hpd_gpio, pmic_gpio; | ||
58 | }; | ||
59 | |||
60 | void hdmi_set_mode(struct hdmi *hdmi, bool power_on); | ||
61 | void hdmi_destroy(struct hdmi *hdmi); | ||
62 | int hdmi_init(struct hdmi *hdmi, struct drm_device *dev, | ||
63 | struct drm_connector *connector); | ||
64 | |||
65 | static inline void hdmi_write(struct hdmi *hdmi, u32 reg, u32 data) | ||
66 | { | ||
67 | msm_writel(data, hdmi->mmio + reg); | ||
68 | } | ||
69 | |||
70 | static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg) | ||
71 | { | ||
72 | return msm_readl(hdmi->mmio + reg); | ||
73 | } | ||
74 | |||
75 | /* | ||
76 | * The phy appears to be different, for example between 8960 and 8x60, | ||
77 | * so split the phy related functions out and load the correct one at | ||
78 | * runtime: | ||
79 | */ | ||
80 | |||
81 | struct hdmi_phy_funcs { | ||
82 | void (*destroy)(struct hdmi_phy *phy); | ||
83 | void (*reset)(struct hdmi_phy *phy); | ||
84 | void (*powerup)(struct hdmi_phy *phy, unsigned long int pixclock); | ||
85 | void (*powerdown)(struct hdmi_phy *phy); | ||
86 | }; | ||
87 | |||
88 | struct hdmi_phy { | ||
89 | const struct hdmi_phy_funcs *funcs; | ||
90 | }; | ||
91 | |||
92 | /* | ||
93 | * phy can be different on different generations: | ||
94 | */ | ||
95 | struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi); | ||
96 | struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi); | ||
97 | |||
98 | /* | ||
99 | * hdmi connector: | ||
100 | */ | ||
101 | |||
102 | void hdmi_connector_irq(struct drm_connector *connector); | ||
103 | |||
104 | /* | ||
105 | * i2c adapter for ddc: | ||
106 | */ | ||
107 | |||
108 | void hdmi_i2c_irq(struct i2c_adapter *i2c); | ||
109 | void hdmi_i2c_destroy(struct i2c_adapter *i2c); | ||
110 | struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi); | ||
111 | |||
112 | #endif /* __HDMI_CONNECTOR_H__ */ | ||
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c new file mode 100644 index 000000000000..7d63f5ffa7ba --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c | |||
@@ -0,0 +1,461 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include <linux/gpio.h> | ||
19 | |||
20 | #include "msm_connector.h" | ||
21 | #include "hdmi.h" | ||
22 | |||
23 | struct hdmi_connector { | ||
24 | struct msm_connector base; | ||
25 | struct hdmi hdmi; | ||
26 | unsigned long int pixclock; | ||
27 | bool enabled; | ||
28 | }; | ||
29 | #define to_hdmi_connector(x) container_of(x, struct hdmi_connector, base) | ||
30 | |||
31 | static int gpio_config(struct hdmi *hdmi, bool on) | ||
32 | { | ||
33 | struct drm_device *dev = hdmi->dev; | ||
34 | struct hdmi_platform_config *config = | ||
35 | hdmi->pdev->dev.platform_data; | ||
36 | int ret; | ||
37 | |||
38 | if (on) { | ||
39 | ret = gpio_request(config->ddc_clk_gpio, "HDMI_DDC_CLK"); | ||
40 | if (ret) { | ||
41 | dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", | ||
42 | "HDMI_DDC_CLK", config->ddc_clk_gpio, ret); | ||
43 | goto error1; | ||
44 | } | ||
45 | ret = gpio_request(config->ddc_data_gpio, "HDMI_DDC_DATA"); | ||
46 | if (ret) { | ||
47 | dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", | ||
48 | "HDMI_DDC_DATA", config->ddc_data_gpio, ret); | ||
49 | goto error2; | ||
50 | } | ||
51 | ret = gpio_request(config->hpd_gpio, "HDMI_HPD"); | ||
52 | if (ret) { | ||
53 | dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", | ||
54 | "HDMI_HPD", config->hpd_gpio, ret); | ||
55 | goto error3; | ||
56 | } | ||
57 | if (config->pmic_gpio != -1) { | ||
58 | ret = gpio_request(config->pmic_gpio, "PMIC_HDMI_MUX_SEL"); | ||
59 | if (ret) { | ||
60 | dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", | ||
61 | "PMIC_HDMI_MUX_SEL", config->pmic_gpio, ret); | ||
62 | goto error4; | ||
63 | } | ||
64 | gpio_set_value_cansleep(config->pmic_gpio, 0); | ||
65 | } | ||
66 | DBG("gpio on"); | ||
67 | } else { | ||
68 | gpio_free(config->ddc_clk_gpio); | ||
69 | gpio_free(config->ddc_data_gpio); | ||
70 | gpio_free(config->hpd_gpio); | ||
71 | |||
72 | if (config->pmic_gpio != -1) { | ||
73 | gpio_set_value_cansleep(config->pmic_gpio, 1); | ||
74 | gpio_free(config->pmic_gpio); | ||
75 | } | ||
76 | DBG("gpio off"); | ||
77 | } | ||
78 | |||
79 | return 0; | ||
80 | |||
81 | error4: | ||
82 | gpio_free(config->hpd_gpio); | ||
83 | error3: | ||
84 | gpio_free(config->ddc_data_gpio); | ||
85 | error2: | ||
86 | gpio_free(config->ddc_clk_gpio); | ||
87 | error1: | ||
88 | return ret; | ||
89 | } | ||
90 | |||
91 | static int hpd_enable(struct hdmi_connector *hdmi_connector) | ||
92 | { | ||
93 | struct hdmi *hdmi = &hdmi_connector->hdmi; | ||
94 | struct drm_device *dev = hdmi_connector->base.base.dev; | ||
95 | struct hdmi_phy *phy = hdmi->phy; | ||
96 | uint32_t hpd_ctrl; | ||
97 | int ret; | ||
98 | |||
99 | ret = gpio_config(hdmi, true); | ||
100 | if (ret) { | ||
101 | dev_err(dev->dev, "failed to configure GPIOs: %d\n", ret); | ||
102 | goto fail; | ||
103 | } | ||
104 | |||
105 | ret = clk_prepare_enable(hdmi->clk); | ||
106 | if (ret) { | ||
107 | dev_err(dev->dev, "failed to enable 'clk': %d\n", ret); | ||
108 | goto fail; | ||
109 | } | ||
110 | |||
111 | ret = clk_prepare_enable(hdmi->m_pclk); | ||
112 | if (ret) { | ||
113 | dev_err(dev->dev, "failed to enable 'm_pclk': %d\n", ret); | ||
114 | goto fail; | ||
115 | } | ||
116 | |||
117 | ret = clk_prepare_enable(hdmi->s_pclk); | ||
118 | if (ret) { | ||
119 | dev_err(dev->dev, "failed to enable 's_pclk': %d\n", ret); | ||
120 | goto fail; | ||
121 | } | ||
122 | |||
123 | if (hdmi->mpp0) | ||
124 | ret = regulator_enable(hdmi->mpp0); | ||
125 | if (!ret) | ||
126 | ret = regulator_enable(hdmi->mvs); | ||
127 | if (ret) { | ||
128 | dev_err(dev->dev, "failed to enable regulators: %d\n", ret); | ||
129 | goto fail; | ||
130 | } | ||
131 | |||
132 | hdmi_set_mode(hdmi, false); | ||
133 | phy->funcs->reset(phy); | ||
134 | hdmi_set_mode(hdmi, true); | ||
135 | |||
136 | hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b); | ||
137 | |||
138 | /* enable HPD events: */ | ||
139 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, | ||
140 | HDMI_HPD_INT_CTRL_INT_CONNECT | | ||
141 | HDMI_HPD_INT_CTRL_INT_EN); | ||
142 | |||
143 | /* set timeout to 4.1ms (max) for hardware debounce */ | ||
144 | hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); | ||
145 | hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff); | ||
146 | |||
147 | /* Toggle HPD circuit to trigger HPD sense */ | ||
148 | hdmi_write(hdmi, REG_HDMI_HPD_CTRL, | ||
149 | ~HDMI_HPD_CTRL_ENABLE & hpd_ctrl); | ||
150 | hdmi_write(hdmi, REG_HDMI_HPD_CTRL, | ||
151 | HDMI_HPD_CTRL_ENABLE | hpd_ctrl); | ||
152 | |||
153 | return 0; | ||
154 | |||
155 | fail: | ||
156 | return ret; | ||
157 | } | ||
158 | |||
159 | static int hdp_disable(struct hdmi_connector *hdmi_connector) | ||
160 | { | ||
161 | struct hdmi *hdmi = &hdmi_connector->hdmi; | ||
162 | struct drm_device *dev = hdmi_connector->base.base.dev; | ||
163 | int ret = 0; | ||
164 | |||
165 | /* Disable HPD interrupt */ | ||
166 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0); | ||
167 | |||
168 | hdmi_set_mode(hdmi, false); | ||
169 | |||
170 | if (hdmi->mpp0) | ||
171 | ret = regulator_disable(hdmi->mpp0); | ||
172 | if (!ret) | ||
173 | ret = regulator_disable(hdmi->mvs); | ||
174 | if (ret) { | ||
175 | dev_err(dev->dev, "failed to enable regulators: %d\n", ret); | ||
176 | goto fail; | ||
177 | } | ||
178 | |||
179 | clk_disable_unprepare(hdmi->clk); | ||
180 | clk_disable_unprepare(hdmi->m_pclk); | ||
181 | clk_disable_unprepare(hdmi->s_pclk); | ||
182 | |||
183 | ret = gpio_config(hdmi, false); | ||
184 | if (ret) { | ||
185 | dev_err(dev->dev, "failed to unconfigure GPIOs: %d\n", ret); | ||
186 | goto fail; | ||
187 | } | ||
188 | |||
189 | return 0; | ||
190 | |||
191 | fail: | ||
192 | return ret; | ||
193 | } | ||
194 | |||
195 | void hdmi_connector_irq(struct drm_connector *connector) | ||
196 | { | ||
197 | struct msm_connector *msm_connector = to_msm_connector(connector); | ||
198 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(msm_connector); | ||
199 | struct hdmi *hdmi = &hdmi_connector->hdmi; | ||
200 | uint32_t hpd_int_status, hpd_int_ctrl; | ||
201 | |||
202 | /* Process HPD: */ | ||
203 | hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); | ||
204 | hpd_int_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_INT_CTRL); | ||
205 | |||
206 | if ((hpd_int_ctrl & HDMI_HPD_INT_CTRL_INT_EN) && | ||
207 | (hpd_int_status & HDMI_HPD_INT_STATUS_INT)) { | ||
208 | bool detected = !!(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED); | ||
209 | |||
210 | DBG("status=%04x, ctrl=%04x", hpd_int_status, hpd_int_ctrl); | ||
211 | |||
212 | /* ack the irq: */ | ||
213 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, | ||
214 | hpd_int_ctrl | HDMI_HPD_INT_CTRL_INT_ACK); | ||
215 | |||
216 | drm_helper_hpd_irq_event(connector->dev); | ||
217 | |||
218 | /* detect disconnect if we are connected or visa versa: */ | ||
219 | hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN; | ||
220 | if (!detected) | ||
221 | hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT; | ||
222 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl); | ||
223 | } | ||
224 | } | ||
225 | |||
226 | static enum drm_connector_status hdmi_connector_detect( | ||
227 | struct drm_connector *connector, bool force) | ||
228 | { | ||
229 | struct msm_connector *msm_connector = to_msm_connector(connector); | ||
230 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(msm_connector); | ||
231 | struct hdmi *hdmi = &hdmi_connector->hdmi; | ||
232 | uint32_t hpd_int_status; | ||
233 | int retry = 20; | ||
234 | |||
235 | hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); | ||
236 | |||
237 | /* sense seems to in some cases be momentarily de-asserted, don't | ||
238 | * let that trick us into thinking the monitor is gone: | ||
239 | */ | ||
240 | while (retry-- && !(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED)) { | ||
241 | mdelay(10); | ||
242 | hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); | ||
243 | DBG("status=%08x", hpd_int_status); | ||
244 | } | ||
245 | |||
246 | return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ? | ||
247 | connector_status_connected : connector_status_disconnected; | ||
248 | } | ||
249 | |||
250 | static void hdmi_connector_destroy(struct drm_connector *connector) | ||
251 | { | ||
252 | struct msm_connector *msm_connector = to_msm_connector(connector); | ||
253 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(msm_connector); | ||
254 | |||
255 | hdp_disable(hdmi_connector); | ||
256 | |||
257 | drm_sysfs_connector_remove(connector); | ||
258 | drm_connector_cleanup(connector); | ||
259 | |||
260 | hdmi_destroy(&hdmi_connector->hdmi); | ||
261 | |||
262 | kfree(hdmi_connector); | ||
263 | } | ||
264 | |||
265 | static int hdmi_connector_get_modes(struct drm_connector *connector) | ||
266 | { | ||
267 | struct msm_connector *msm_connector = to_msm_connector(connector); | ||
268 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(msm_connector); | ||
269 | struct hdmi *hdmi = &hdmi_connector->hdmi; | ||
270 | struct edid *edid; | ||
271 | uint32_t hdmi_ctrl; | ||
272 | int ret = 0; | ||
273 | |||
274 | hdmi_ctrl = hdmi_read(hdmi, REG_HDMI_CTRL); | ||
275 | hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl | HDMI_CTRL_ENABLE); | ||
276 | |||
277 | edid = drm_get_edid(connector, hdmi->i2c); | ||
278 | |||
279 | hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl); | ||
280 | |||
281 | drm_mode_connector_update_edid_property(connector, edid); | ||
282 | |||
283 | if (edid) { | ||
284 | ret = drm_add_edid_modes(connector, edid); | ||
285 | kfree(edid); | ||
286 | } | ||
287 | |||
288 | return ret; | ||
289 | } | ||
290 | |||
291 | static int hdmi_connector_mode_valid(struct drm_connector *connector, | ||
292 | struct drm_display_mode *mode) | ||
293 | { | ||
294 | struct msm_connector *msm_connector = to_msm_connector(connector); | ||
295 | struct msm_drm_private *priv = connector->dev->dev_private; | ||
296 | struct msm_kms *kms = priv->kms; | ||
297 | long actual, requested; | ||
298 | |||
299 | requested = 1000 * mode->clock; | ||
300 | actual = kms->funcs->round_pixclk(kms, | ||
301 | requested, msm_connector->encoder); | ||
302 | |||
303 | DBG("requested=%ld, actual=%ld", requested, actual); | ||
304 | |||
305 | if (actual != requested) | ||
306 | return MODE_CLOCK_RANGE; | ||
307 | |||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | static const struct drm_connector_funcs hdmi_connector_funcs = { | ||
312 | .dpms = drm_helper_connector_dpms, | ||
313 | .detect = hdmi_connector_detect, | ||
314 | .fill_modes = drm_helper_probe_single_connector_modes, | ||
315 | .destroy = hdmi_connector_destroy, | ||
316 | }; | ||
317 | |||
318 | static const struct drm_connector_helper_funcs hdmi_connector_helper_funcs = { | ||
319 | .get_modes = hdmi_connector_get_modes, | ||
320 | .mode_valid = hdmi_connector_mode_valid, | ||
321 | .best_encoder = msm_connector_attached_encoder, | ||
322 | }; | ||
323 | |||
324 | static void hdmi_connector_dpms(struct msm_connector *msm_connector, int mode) | ||
325 | { | ||
326 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(msm_connector); | ||
327 | struct hdmi *hdmi = &hdmi_connector->hdmi; | ||
328 | struct hdmi_phy *phy = hdmi->phy; | ||
329 | bool enabled = (mode == DRM_MODE_DPMS_ON); | ||
330 | |||
331 | DBG("mode=%d", mode); | ||
332 | |||
333 | if (enabled == hdmi_connector->enabled) | ||
334 | return; | ||
335 | |||
336 | if (enabled) { | ||
337 | phy->funcs->powerup(phy, hdmi_connector->pixclock); | ||
338 | hdmi_set_mode(hdmi, true); | ||
339 | } else { | ||
340 | hdmi_set_mode(hdmi, false); | ||
341 | phy->funcs->powerdown(phy); | ||
342 | } | ||
343 | |||
344 | hdmi_connector->enabled = enabled; | ||
345 | } | ||
346 | |||
347 | static void hdmi_connector_mode_set(struct msm_connector *msm_connector, | ||
348 | struct drm_display_mode *mode) | ||
349 | { | ||
350 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(msm_connector); | ||
351 | struct hdmi *hdmi = &hdmi_connector->hdmi; | ||
352 | int hstart, hend, vstart, vend; | ||
353 | uint32_t frame_ctrl; | ||
354 | |||
355 | hdmi_connector->pixclock = mode->clock * 1000; | ||
356 | |||
357 | hdmi->hdmi_mode = drm_match_cea_mode(mode) > 1; | ||
358 | |||
359 | hstart = mode->htotal - mode->hsync_start; | ||
360 | hend = mode->htotal - mode->hsync_start + mode->hdisplay; | ||
361 | |||
362 | vstart = mode->vtotal - mode->vsync_start - 1; | ||
363 | vend = mode->vtotal - mode->vsync_start + mode->vdisplay - 1; | ||
364 | |||
365 | DBG("htotal=%d, vtotal=%d, hstart=%d, hend=%d, vstart=%d, vend=%d", | ||
366 | mode->htotal, mode->vtotal, hstart, hend, vstart, vend); | ||
367 | |||
368 | hdmi_write(hdmi, REG_HDMI_TOTAL, | ||
369 | HDMI_TOTAL_H_TOTAL(mode->htotal - 1) | | ||
370 | HDMI_TOTAL_V_TOTAL(mode->vtotal - 1)); | ||
371 | |||
372 | hdmi_write(hdmi, REG_HDMI_ACTIVE_HSYNC, | ||
373 | HDMI_ACTIVE_HSYNC_START(hstart) | | ||
374 | HDMI_ACTIVE_HSYNC_END(hend)); | ||
375 | hdmi_write(hdmi, REG_HDMI_ACTIVE_VSYNC, | ||
376 | HDMI_ACTIVE_VSYNC_START(vstart) | | ||
377 | HDMI_ACTIVE_VSYNC_END(vend)); | ||
378 | |||
379 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) { | ||
380 | hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2, | ||
381 | HDMI_VSYNC_TOTAL_F2_V_TOTAL(mode->vtotal)); | ||
382 | hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2, | ||
383 | HDMI_VSYNC_ACTIVE_F2_START(vstart + 1) | | ||
384 | HDMI_VSYNC_ACTIVE_F2_END(vend + 1)); | ||
385 | } else { | ||
386 | hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2, | ||
387 | HDMI_VSYNC_TOTAL_F2_V_TOTAL(0)); | ||
388 | hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2, | ||
389 | HDMI_VSYNC_ACTIVE_F2_START(0) | | ||
390 | HDMI_VSYNC_ACTIVE_F2_END(0)); | ||
391 | } | ||
392 | |||
393 | frame_ctrl = 0; | ||
394 | if (mode->flags & DRM_MODE_FLAG_NHSYNC) | ||
395 | frame_ctrl |= HDMI_FRAME_CTRL_HSYNC_LOW; | ||
396 | if (mode->flags & DRM_MODE_FLAG_NVSYNC) | ||
397 | frame_ctrl |= HDMI_FRAME_CTRL_VSYNC_LOW; | ||
398 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) | ||
399 | frame_ctrl |= HDMI_FRAME_CTRL_INTERLACED_EN; | ||
400 | DBG("frame_ctrl=%08x", frame_ctrl); | ||
401 | hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl); | ||
402 | |||
403 | // TODO until we have audio, this might be safest: | ||
404 | if (hdmi->hdmi_mode) | ||
405 | hdmi_write(hdmi, REG_HDMI_GC, HDMI_GC_MUTE); | ||
406 | } | ||
407 | |||
408 | static const struct msm_connector_funcs msm_connector_funcs = { | ||
409 | .dpms = hdmi_connector_dpms, | ||
410 | .mode_set = hdmi_connector_mode_set, | ||
411 | }; | ||
412 | |||
413 | /* initialize connector */ | ||
414 | struct drm_connector *hdmi_connector_init(struct drm_device *dev, | ||
415 | struct drm_encoder *encoder) | ||
416 | { | ||
417 | struct drm_connector *connector = NULL; | ||
418 | struct hdmi_connector *hdmi_connector; | ||
419 | int ret; | ||
420 | |||
421 | hdmi_connector = kzalloc(sizeof(*hdmi_connector), GFP_KERNEL); | ||
422 | if (!hdmi_connector) { | ||
423 | ret = -ENOMEM; | ||
424 | goto fail; | ||
425 | } | ||
426 | |||
427 | connector = &hdmi_connector->base.base; | ||
428 | |||
429 | msm_connector_init(&hdmi_connector->base, | ||
430 | &msm_connector_funcs, encoder); | ||
431 | drm_connector_init(dev, connector, &hdmi_connector_funcs, | ||
432 | DRM_MODE_CONNECTOR_HDMIA); | ||
433 | drm_connector_helper_add(connector, &hdmi_connector_helper_funcs); | ||
434 | |||
435 | connector->polled = DRM_CONNECTOR_POLL_HPD; | ||
436 | |||
437 | connector->interlace_allowed = 1; | ||
438 | connector->doublescan_allowed = 0; | ||
439 | |||
440 | drm_sysfs_connector_add(connector); | ||
441 | |||
442 | ret = hdmi_init(&hdmi_connector->hdmi, dev, connector); | ||
443 | if (ret) | ||
444 | goto fail; | ||
445 | |||
446 | ret = hpd_enable(hdmi_connector); | ||
447 | if (ret) { | ||
448 | dev_err(dev->dev, "failed to enable HPD: %d\n", ret); | ||
449 | goto fail; | ||
450 | } | ||
451 | |||
452 | drm_mode_connector_attach_encoder(connector, encoder); | ||
453 | |||
454 | return connector; | ||
455 | |||
456 | fail: | ||
457 | if (connector) | ||
458 | hdmi_connector_destroy(connector); | ||
459 | |||
460 | return ERR_PTR(ret); | ||
461 | } | ||
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_i2c.c b/drivers/gpu/drm/msm/hdmi/hdmi_i2c.c new file mode 100644 index 000000000000..f4ab7f70fed1 --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_i2c.c | |||
@@ -0,0 +1,281 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "hdmi.h" | ||
19 | |||
20 | struct hdmi_i2c_adapter { | ||
21 | struct i2c_adapter base; | ||
22 | struct hdmi *hdmi; | ||
23 | bool sw_done; | ||
24 | wait_queue_head_t ddc_event; | ||
25 | }; | ||
26 | #define to_hdmi_i2c_adapter(x) container_of(x, struct hdmi_i2c_adapter, base) | ||
27 | |||
28 | static void init_ddc(struct hdmi_i2c_adapter *hdmi_i2c) | ||
29 | { | ||
30 | struct hdmi *hdmi = hdmi_i2c->hdmi; | ||
31 | |||
32 | hdmi_write(hdmi, REG_HDMI_DDC_CTRL, | ||
33 | HDMI_DDC_CTRL_SW_STATUS_RESET); | ||
34 | hdmi_write(hdmi, REG_HDMI_DDC_CTRL, | ||
35 | HDMI_DDC_CTRL_SOFT_RESET); | ||
36 | |||
37 | hdmi_write(hdmi, REG_HDMI_DDC_SPEED, | ||
38 | HDMI_DDC_SPEED_THRESHOLD(2) | | ||
39 | HDMI_DDC_SPEED_PRESCALE(10)); | ||
40 | |||
41 | hdmi_write(hdmi, REG_HDMI_DDC_SETUP, | ||
42 | HDMI_DDC_SETUP_TIMEOUT(0xff)); | ||
43 | |||
44 | /* enable reference timer for 27us */ | ||
45 | hdmi_write(hdmi, REG_HDMI_DDC_REF, | ||
46 | HDMI_DDC_REF_REFTIMER_ENABLE | | ||
47 | HDMI_DDC_REF_REFTIMER(27)); | ||
48 | } | ||
49 | |||
50 | static int ddc_clear_irq(struct hdmi_i2c_adapter *hdmi_i2c) | ||
51 | { | ||
52 | struct hdmi *hdmi = hdmi_i2c->hdmi; | ||
53 | struct drm_device *dev = hdmi->dev; | ||
54 | uint32_t retry = 0xffff; | ||
55 | uint32_t ddc_int_ctrl; | ||
56 | |||
57 | do { | ||
58 | --retry; | ||
59 | |||
60 | hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL, | ||
61 | HDMI_DDC_INT_CTRL_SW_DONE_ACK | | ||
62 | HDMI_DDC_INT_CTRL_SW_DONE_MASK); | ||
63 | |||
64 | ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL); | ||
65 | |||
66 | } while ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT) && retry); | ||
67 | |||
68 | if (!retry) { | ||
69 | dev_err(dev->dev, "timeout waiting for DDC\n"); | ||
70 | return -ETIMEDOUT; | ||
71 | } | ||
72 | |||
73 | hdmi_i2c->sw_done = false; | ||
74 | |||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | #define MAX_TRANSACTIONS 4 | ||
79 | |||
80 | static bool sw_done(struct hdmi_i2c_adapter *hdmi_i2c) | ||
81 | { | ||
82 | struct hdmi *hdmi = hdmi_i2c->hdmi; | ||
83 | |||
84 | if (!hdmi_i2c->sw_done) { | ||
85 | uint32_t ddc_int_ctrl; | ||
86 | |||
87 | ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL); | ||
88 | |||
89 | if ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_MASK) && | ||
90 | (ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT)) { | ||
91 | hdmi_i2c->sw_done = true; | ||
92 | hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL, | ||
93 | HDMI_DDC_INT_CTRL_SW_DONE_ACK); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | return hdmi_i2c->sw_done; | ||
98 | } | ||
99 | |||
100 | static int hdmi_i2c_xfer(struct i2c_adapter *i2c, | ||
101 | struct i2c_msg *msgs, int num) | ||
102 | { | ||
103 | struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c); | ||
104 | struct hdmi *hdmi = hdmi_i2c->hdmi; | ||
105 | struct drm_device *dev = hdmi->dev; | ||
106 | static const uint32_t nack[] = { | ||
107 | HDMI_DDC_SW_STATUS_NACK0, HDMI_DDC_SW_STATUS_NACK1, | ||
108 | HDMI_DDC_SW_STATUS_NACK2, HDMI_DDC_SW_STATUS_NACK3, | ||
109 | }; | ||
110 | int indices[MAX_TRANSACTIONS]; | ||
111 | int ret, i, j, index = 0; | ||
112 | uint32_t ddc_status, ddc_data, i2c_trans; | ||
113 | |||
114 | num = min(num, MAX_TRANSACTIONS); | ||
115 | |||
116 | WARN_ON(!(hdmi_read(hdmi, REG_HDMI_CTRL) & HDMI_CTRL_ENABLE)); | ||
117 | |||
118 | if (num == 0) | ||
119 | return num; | ||
120 | |||
121 | init_ddc(hdmi_i2c); | ||
122 | |||
123 | ret = ddc_clear_irq(hdmi_i2c); | ||
124 | if (ret) | ||
125 | return ret; | ||
126 | |||
127 | for (i = 0; i < num; i++) { | ||
128 | struct i2c_msg *p = &msgs[i]; | ||
129 | uint32_t raw_addr = p->addr << 1; | ||
130 | |||
131 | if (p->flags & I2C_M_RD) | ||
132 | raw_addr |= 1; | ||
133 | |||
134 | ddc_data = HDMI_DDC_DATA_DATA(raw_addr) | | ||
135 | HDMI_DDC_DATA_DATA_RW(DDC_WRITE); | ||
136 | |||
137 | if (i == 0) { | ||
138 | ddc_data |= HDMI_DDC_DATA_INDEX(0) | | ||
139 | HDMI_DDC_DATA_INDEX_WRITE; | ||
140 | } | ||
141 | |||
142 | hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data); | ||
143 | index++; | ||
144 | |||
145 | indices[i] = index; | ||
146 | |||
147 | if (p->flags & I2C_M_RD) { | ||
148 | index += p->len; | ||
149 | } else { | ||
150 | for (j = 0; j < p->len; j++) { | ||
151 | ddc_data = HDMI_DDC_DATA_DATA(p->buf[j]) | | ||
152 | HDMI_DDC_DATA_DATA_RW(DDC_WRITE); | ||
153 | hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data); | ||
154 | index++; | ||
155 | } | ||
156 | } | ||
157 | |||
158 | i2c_trans = HDMI_I2C_TRANSACTION_REG_CNT(p->len) | | ||
159 | HDMI_I2C_TRANSACTION_REG_RW( | ||
160 | (p->flags & I2C_M_RD) ? DDC_READ : DDC_WRITE) | | ||
161 | HDMI_I2C_TRANSACTION_REG_START; | ||
162 | |||
163 | if (i == (num - 1)) | ||
164 | i2c_trans |= HDMI_I2C_TRANSACTION_REG_STOP; | ||
165 | |||
166 | hdmi_write(hdmi, REG_HDMI_I2C_TRANSACTION(i), i2c_trans); | ||
167 | } | ||
168 | |||
169 | /* trigger the transfer: */ | ||
170 | hdmi_write(hdmi, REG_HDMI_DDC_CTRL, | ||
171 | HDMI_DDC_CTRL_TRANSACTION_CNT(num - 1) | | ||
172 | HDMI_DDC_CTRL_GO); | ||
173 | |||
174 | ret = wait_event_timeout(hdmi_i2c->ddc_event, sw_done(hdmi_i2c), HZ/4); | ||
175 | if (ret <= 0) { | ||
176 | if (ret == 0) | ||
177 | ret = -ETIMEDOUT; | ||
178 | dev_warn(dev->dev, "DDC timeout: %d\n", ret); | ||
179 | DBG("sw_status=%08x, hw_status=%08x, int_ctrl=%08x", | ||
180 | hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS), | ||
181 | hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS), | ||
182 | hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL)); | ||
183 | return ret; | ||
184 | } | ||
185 | |||
186 | ddc_status = hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS); | ||
187 | |||
188 | /* read back results of any read transactions: */ | ||
189 | for (i = 0; i < num; i++) { | ||
190 | struct i2c_msg *p = &msgs[i]; | ||
191 | |||
192 | if (!(p->flags & I2C_M_RD)) | ||
193 | continue; | ||
194 | |||
195 | /* check for NACK: */ | ||
196 | if (ddc_status & nack[i]) { | ||
197 | DBG("ddc_status=%08x", ddc_status); | ||
198 | break; | ||
199 | } | ||
200 | |||
201 | ddc_data = HDMI_DDC_DATA_DATA_RW(DDC_READ) | | ||
202 | HDMI_DDC_DATA_INDEX(indices[i]) | | ||
203 | HDMI_DDC_DATA_INDEX_WRITE; | ||
204 | |||
205 | hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data); | ||
206 | |||
207 | /* discard first byte: */ | ||
208 | hdmi_read(hdmi, REG_HDMI_DDC_DATA); | ||
209 | |||
210 | for (j = 0; j < p->len; j++) { | ||
211 | ddc_data = hdmi_read(hdmi, REG_HDMI_DDC_DATA); | ||
212 | p->buf[j] = FIELD(ddc_data, HDMI_DDC_DATA_DATA); | ||
213 | } | ||
214 | } | ||
215 | |||
216 | return i; | ||
217 | } | ||
218 | |||
219 | static u32 hdmi_i2c_func(struct i2c_adapter *adapter) | ||
220 | { | ||
221 | return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; | ||
222 | } | ||
223 | |||
224 | static const struct i2c_algorithm hdmi_i2c_algorithm = { | ||
225 | .master_xfer = hdmi_i2c_xfer, | ||
226 | .functionality = hdmi_i2c_func, | ||
227 | }; | ||
228 | |||
229 | void hdmi_i2c_irq(struct i2c_adapter *i2c) | ||
230 | { | ||
231 | struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c); | ||
232 | |||
233 | if (sw_done(hdmi_i2c)) | ||
234 | wake_up_all(&hdmi_i2c->ddc_event); | ||
235 | } | ||
236 | |||
237 | void hdmi_i2c_destroy(struct i2c_adapter *i2c) | ||
238 | { | ||
239 | struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c); | ||
240 | i2c_del_adapter(i2c); | ||
241 | kfree(hdmi_i2c); | ||
242 | } | ||
243 | |||
244 | struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi) | ||
245 | { | ||
246 | struct drm_device *dev = hdmi->dev; | ||
247 | struct hdmi_i2c_adapter *hdmi_i2c; | ||
248 | struct i2c_adapter *i2c = NULL; | ||
249 | int ret; | ||
250 | |||
251 | hdmi_i2c = kzalloc(sizeof(*hdmi_i2c), GFP_KERNEL); | ||
252 | if (!hdmi_i2c) { | ||
253 | ret = -ENOMEM; | ||
254 | goto fail; | ||
255 | } | ||
256 | |||
257 | i2c = &hdmi_i2c->base; | ||
258 | |||
259 | hdmi_i2c->hdmi = hdmi; | ||
260 | init_waitqueue_head(&hdmi_i2c->ddc_event); | ||
261 | |||
262 | |||
263 | i2c->owner = THIS_MODULE; | ||
264 | i2c->class = I2C_CLASS_DDC; | ||
265 | snprintf(i2c->name, sizeof(i2c->name), "msm hdmi i2c"); | ||
266 | i2c->dev.parent = &hdmi->pdev->dev; | ||
267 | i2c->algo = &hdmi_i2c_algorithm; | ||
268 | |||
269 | ret = i2c_add_adapter(i2c); | ||
270 | if (ret) { | ||
271 | dev_err(dev->dev, "failed to register hdmi i2c: %d\n", ret); | ||
272 | goto fail; | ||
273 | } | ||
274 | |||
275 | return i2c; | ||
276 | |||
277 | fail: | ||
278 | if (i2c) | ||
279 | hdmi_i2c_destroy(i2c); | ||
280 | return ERR_PTR(ret); | ||
281 | } | ||
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy_8960.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8960.c new file mode 100644 index 000000000000..e5b7ed5b8f01 --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8960.c | |||
@@ -0,0 +1,141 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "hdmi.h" | ||
19 | |||
20 | struct hdmi_phy_8960 { | ||
21 | struct hdmi_phy base; | ||
22 | struct hdmi *hdmi; | ||
23 | }; | ||
24 | #define to_hdmi_phy_8960(x) container_of(x, struct hdmi_phy_8960, base) | ||
25 | |||
26 | static void hdmi_phy_8960_destroy(struct hdmi_phy *phy) | ||
27 | { | ||
28 | struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy); | ||
29 | kfree(phy_8960); | ||
30 | } | ||
31 | |||
32 | static void hdmi_phy_8960_reset(struct hdmi_phy *phy) | ||
33 | { | ||
34 | struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy); | ||
35 | struct hdmi *hdmi = phy_8960->hdmi; | ||
36 | unsigned int val; | ||
37 | |||
38 | val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL); | ||
39 | |||
40 | if (val & HDMI_PHY_CTRL_SW_RESET_LOW) { | ||
41 | /* pull low */ | ||
42 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
43 | val & ~HDMI_PHY_CTRL_SW_RESET); | ||
44 | } else { | ||
45 | /* pull high */ | ||
46 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
47 | val | HDMI_PHY_CTRL_SW_RESET); | ||
48 | } | ||
49 | |||
50 | if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) { | ||
51 | /* pull low */ | ||
52 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
53 | val & ~HDMI_PHY_CTRL_SW_RESET_PLL); | ||
54 | } else { | ||
55 | /* pull high */ | ||
56 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
57 | val | HDMI_PHY_CTRL_SW_RESET_PLL); | ||
58 | } | ||
59 | |||
60 | msleep(100); | ||
61 | |||
62 | if (val & HDMI_PHY_CTRL_SW_RESET_LOW) { | ||
63 | /* pull high */ | ||
64 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
65 | val | HDMI_PHY_CTRL_SW_RESET); | ||
66 | } else { | ||
67 | /* pull low */ | ||
68 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
69 | val & ~HDMI_PHY_CTRL_SW_RESET); | ||
70 | } | ||
71 | |||
72 | if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) { | ||
73 | /* pull high */ | ||
74 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
75 | val | HDMI_PHY_CTRL_SW_RESET_PLL); | ||
76 | } else { | ||
77 | /* pull low */ | ||
78 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
79 | val & ~HDMI_PHY_CTRL_SW_RESET_PLL); | ||
80 | } | ||
81 | } | ||
82 | |||
83 | static void hdmi_phy_8960_powerup(struct hdmi_phy *phy, | ||
84 | unsigned long int pixclock) | ||
85 | { | ||
86 | struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy); | ||
87 | struct hdmi *hdmi = phy_8960->hdmi; | ||
88 | |||
89 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG0, 0x1b); | ||
90 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG1, 0xf2); | ||
91 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG4, 0x00); | ||
92 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG5, 0x00); | ||
93 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG6, 0x00); | ||
94 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG7, 0x00); | ||
95 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG8, 0x00); | ||
96 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG9, 0x00); | ||
97 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG10, 0x00); | ||
98 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG11, 0x00); | ||
99 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG3, 0x20); | ||
100 | } | ||
101 | |||
102 | static void hdmi_phy_8960_powerdown(struct hdmi_phy *phy) | ||
103 | { | ||
104 | struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy); | ||
105 | struct hdmi *hdmi = phy_8960->hdmi; | ||
106 | |||
107 | hdmi_write(hdmi, REG_HDMI_8960_PHY_REG2, 0x7f); | ||
108 | } | ||
109 | |||
110 | static const struct hdmi_phy_funcs hdmi_phy_8960_funcs = { | ||
111 | .destroy = hdmi_phy_8960_destroy, | ||
112 | .reset = hdmi_phy_8960_reset, | ||
113 | .powerup = hdmi_phy_8960_powerup, | ||
114 | .powerdown = hdmi_phy_8960_powerdown, | ||
115 | }; | ||
116 | |||
117 | struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi) | ||
118 | { | ||
119 | struct hdmi_phy_8960 *phy_8960; | ||
120 | struct hdmi_phy *phy = NULL; | ||
121 | int ret; | ||
122 | |||
123 | phy_8960 = kzalloc(sizeof(*phy_8960), GFP_KERNEL); | ||
124 | if (!phy_8960) { | ||
125 | ret = -ENOMEM; | ||
126 | goto fail; | ||
127 | } | ||
128 | |||
129 | phy = &phy_8960->base; | ||
130 | |||
131 | phy->funcs = &hdmi_phy_8960_funcs; | ||
132 | |||
133 | phy_8960->hdmi = hdmi; | ||
134 | |||
135 | return phy; | ||
136 | |||
137 | fail: | ||
138 | if (phy) | ||
139 | hdmi_phy_8960_destroy(phy); | ||
140 | return ERR_PTR(ret); | ||
141 | } | ||
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy_8x60.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8x60.c new file mode 100644 index 000000000000..391433c1af7c --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy_8x60.c | |||
@@ -0,0 +1,214 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "hdmi.h" | ||
19 | |||
20 | struct hdmi_phy_8x60 { | ||
21 | struct hdmi_phy base; | ||
22 | struct hdmi *hdmi; | ||
23 | }; | ||
24 | #define to_hdmi_phy_8x60(x) container_of(x, struct hdmi_phy_8x60, base) | ||
25 | |||
26 | static void hdmi_phy_8x60_destroy(struct hdmi_phy *phy) | ||
27 | { | ||
28 | struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy); | ||
29 | kfree(phy_8x60); | ||
30 | } | ||
31 | |||
32 | static void hdmi_phy_8x60_reset(struct hdmi_phy *phy) | ||
33 | { | ||
34 | struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy); | ||
35 | struct hdmi *hdmi = phy_8x60->hdmi; | ||
36 | unsigned int val; | ||
37 | |||
38 | val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL); | ||
39 | |||
40 | if (val & HDMI_PHY_CTRL_SW_RESET_LOW) { | ||
41 | /* pull low */ | ||
42 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
43 | val & ~HDMI_PHY_CTRL_SW_RESET); | ||
44 | } else { | ||
45 | /* pull high */ | ||
46 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
47 | val | HDMI_PHY_CTRL_SW_RESET); | ||
48 | } | ||
49 | |||
50 | msleep(100); | ||
51 | |||
52 | if (val & HDMI_PHY_CTRL_SW_RESET_LOW) { | ||
53 | /* pull high */ | ||
54 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
55 | val | HDMI_PHY_CTRL_SW_RESET); | ||
56 | } else { | ||
57 | /* pull low */ | ||
58 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
59 | val & ~HDMI_PHY_CTRL_SW_RESET); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | static void hdmi_phy_8x60_powerup(struct hdmi_phy *phy, | ||
64 | unsigned long int pixclock) | ||
65 | { | ||
66 | struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy); | ||
67 | struct hdmi *hdmi = phy_8x60->hdmi; | ||
68 | |||
69 | /* De-serializer delay D/C for non-lbk mode: */ | ||
70 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG0, | ||
71 | HDMI_8x60_PHY_REG0_DESER_DEL_CTRL(3)); | ||
72 | |||
73 | if (pixclock == 27000000) { | ||
74 | /* video_format == HDMI_VFRMT_720x480p60_16_9 */ | ||
75 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG1, | ||
76 | HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(5) | | ||
77 | HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(3)); | ||
78 | } else { | ||
79 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG1, | ||
80 | HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(5) | | ||
81 | HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(4)); | ||
82 | } | ||
83 | |||
84 | /* No matter what, start from the power down mode: */ | ||
85 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2, | ||
86 | HDMI_8x60_PHY_REG2_PD_PWRGEN | | ||
87 | HDMI_8x60_PHY_REG2_PD_PLL | | ||
88 | HDMI_8x60_PHY_REG2_PD_DRIVE_4 | | ||
89 | HDMI_8x60_PHY_REG2_PD_DRIVE_3 | | ||
90 | HDMI_8x60_PHY_REG2_PD_DRIVE_2 | | ||
91 | HDMI_8x60_PHY_REG2_PD_DRIVE_1 | | ||
92 | HDMI_8x60_PHY_REG2_PD_DESER); | ||
93 | |||
94 | /* Turn PowerGen on: */ | ||
95 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2, | ||
96 | HDMI_8x60_PHY_REG2_PD_PLL | | ||
97 | HDMI_8x60_PHY_REG2_PD_DRIVE_4 | | ||
98 | HDMI_8x60_PHY_REG2_PD_DRIVE_3 | | ||
99 | HDMI_8x60_PHY_REG2_PD_DRIVE_2 | | ||
100 | HDMI_8x60_PHY_REG2_PD_DRIVE_1 | | ||
101 | HDMI_8x60_PHY_REG2_PD_DESER); | ||
102 | |||
103 | /* Turn PLL power on: */ | ||
104 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2, | ||
105 | HDMI_8x60_PHY_REG2_PD_DRIVE_4 | | ||
106 | HDMI_8x60_PHY_REG2_PD_DRIVE_3 | | ||
107 | HDMI_8x60_PHY_REG2_PD_DRIVE_2 | | ||
108 | HDMI_8x60_PHY_REG2_PD_DRIVE_1 | | ||
109 | HDMI_8x60_PHY_REG2_PD_DESER); | ||
110 | |||
111 | /* Write to HIGH after PLL power down de-assert: */ | ||
112 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG3, | ||
113 | HDMI_8x60_PHY_REG3_PLL_ENABLE); | ||
114 | |||
115 | /* ASIC power on; PHY REG9 = 0 */ | ||
116 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG9, 0); | ||
117 | |||
118 | /* Enable PLL lock detect, PLL lock det will go high after lock | ||
119 | * Enable the re-time logic | ||
120 | */ | ||
121 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG12, | ||
122 | HDMI_8x60_PHY_REG12_RETIMING_EN | | ||
123 | HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN); | ||
124 | |||
125 | /* Drivers are on: */ | ||
126 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2, | ||
127 | HDMI_8x60_PHY_REG2_PD_DESER); | ||
128 | |||
129 | /* If the RX detector is needed: */ | ||
130 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2, | ||
131 | HDMI_8x60_PHY_REG2_RCV_SENSE_EN | | ||
132 | HDMI_8x60_PHY_REG2_PD_DESER); | ||
133 | |||
134 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG4, 0); | ||
135 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG5, 0); | ||
136 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG6, 0); | ||
137 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG7, 0); | ||
138 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG8, 0); | ||
139 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG9, 0); | ||
140 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG10, 0); | ||
141 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG11, 0); | ||
142 | |||
143 | /* If we want to use lock enable based on counting: */ | ||
144 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG12, | ||
145 | HDMI_8x60_PHY_REG12_RETIMING_EN | | ||
146 | HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN | | ||
147 | HDMI_8x60_PHY_REG12_FORCE_LOCK); | ||
148 | } | ||
149 | |||
150 | static void hdmi_phy_8x60_powerdown(struct hdmi_phy *phy) | ||
151 | { | ||
152 | struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy); | ||
153 | struct hdmi *hdmi = phy_8x60->hdmi; | ||
154 | |||
155 | /* Assert RESET PHY from controller */ | ||
156 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, | ||
157 | HDMI_PHY_CTRL_SW_RESET); | ||
158 | udelay(10); | ||
159 | /* De-assert RESET PHY from controller */ | ||
160 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, 0); | ||
161 | /* Turn off Driver */ | ||
162 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2, | ||
163 | HDMI_8x60_PHY_REG2_PD_DRIVE_4 | | ||
164 | HDMI_8x60_PHY_REG2_PD_DRIVE_3 | | ||
165 | HDMI_8x60_PHY_REG2_PD_DRIVE_2 | | ||
166 | HDMI_8x60_PHY_REG2_PD_DRIVE_1 | | ||
167 | HDMI_8x60_PHY_REG2_PD_DESER); | ||
168 | udelay(10); | ||
169 | /* Disable PLL */ | ||
170 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG3, 0); | ||
171 | /* Power down PHY, but keep RX-sense: */ | ||
172 | hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2, | ||
173 | HDMI_8x60_PHY_REG2_RCV_SENSE_EN | | ||
174 | HDMI_8x60_PHY_REG2_PD_PWRGEN | | ||
175 | HDMI_8x60_PHY_REG2_PD_PLL | | ||
176 | HDMI_8x60_PHY_REG2_PD_DRIVE_4 | | ||
177 | HDMI_8x60_PHY_REG2_PD_DRIVE_3 | | ||
178 | HDMI_8x60_PHY_REG2_PD_DRIVE_2 | | ||
179 | HDMI_8x60_PHY_REG2_PD_DRIVE_1 | | ||
180 | HDMI_8x60_PHY_REG2_PD_DESER); | ||
181 | } | ||
182 | |||
183 | static const struct hdmi_phy_funcs hdmi_phy_8x60_funcs = { | ||
184 | .destroy = hdmi_phy_8x60_destroy, | ||
185 | .reset = hdmi_phy_8x60_reset, | ||
186 | .powerup = hdmi_phy_8x60_powerup, | ||
187 | .powerdown = hdmi_phy_8x60_powerdown, | ||
188 | }; | ||
189 | |||
190 | struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi) | ||
191 | { | ||
192 | struct hdmi_phy_8x60 *phy_8x60; | ||
193 | struct hdmi_phy *phy = NULL; | ||
194 | int ret; | ||
195 | |||
196 | phy_8x60 = kzalloc(sizeof(*phy_8x60), GFP_KERNEL); | ||
197 | if (!phy_8x60) { | ||
198 | ret = -ENOMEM; | ||
199 | goto fail; | ||
200 | } | ||
201 | |||
202 | phy = &phy_8x60->base; | ||
203 | |||
204 | phy->funcs = &hdmi_phy_8x60_funcs; | ||
205 | |||
206 | phy_8x60->hdmi = hdmi; | ||
207 | |||
208 | return phy; | ||
209 | |||
210 | fail: | ||
211 | if (phy) | ||
212 | hdmi_phy_8x60_destroy(phy); | ||
213 | return ERR_PTR(ret); | ||
214 | } | ||
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c new file mode 100644 index 000000000000..bda0fc40b207 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp4/mdp4_crtc.c | |||
@@ -0,0 +1,684 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "mdp4_kms.h" | ||
19 | |||
20 | #include <drm/drm_mode.h> | ||
21 | #include "drm_crtc.h" | ||
22 | #include "drm_crtc_helper.h" | ||
23 | #include "drm_flip_work.h" | ||
24 | |||
25 | struct mdp4_crtc { | ||
26 | struct drm_crtc base; | ||
27 | char name[8]; | ||
28 | struct drm_plane *plane; | ||
29 | int id; | ||
30 | int ovlp; | ||
31 | enum mdp4_dma dma; | ||
32 | bool enabled; | ||
33 | |||
34 | /* which mixer/encoder we route output to: */ | ||
35 | int mixer; | ||
36 | |||
37 | struct { | ||
38 | spinlock_t lock; | ||
39 | bool stale; | ||
40 | uint32_t width, height; | ||
41 | |||
42 | /* next cursor to scan-out: */ | ||
43 | uint32_t next_iova; | ||
44 | struct drm_gem_object *next_bo; | ||
45 | |||
46 | /* current cursor being scanned out: */ | ||
47 | struct drm_gem_object *scanout_bo; | ||
48 | } cursor; | ||
49 | |||
50 | |||
51 | /* if there is a pending flip, these will be non-null: */ | ||
52 | struct drm_pending_vblank_event *event; | ||
53 | struct work_struct pageflip_work; | ||
54 | |||
55 | /* the fb that we currently hold a scanout ref to: */ | ||
56 | struct drm_framebuffer *fb; | ||
57 | |||
58 | /* for unref'ing framebuffers after scanout completes: */ | ||
59 | struct drm_flip_work unref_fb_work; | ||
60 | |||
61 | /* for unref'ing cursor bo's after scanout completes: */ | ||
62 | struct drm_flip_work unref_cursor_work; | ||
63 | |||
64 | struct mdp4_irq vblank; | ||
65 | struct mdp4_irq err; | ||
66 | }; | ||
67 | #define to_mdp4_crtc(x) container_of(x, struct mdp4_crtc, base) | ||
68 | |||
69 | static struct mdp4_kms *get_kms(struct drm_crtc *crtc) | ||
70 | { | ||
71 | struct msm_drm_private *priv = crtc->dev->dev_private; | ||
72 | return to_mdp4_kms(priv->kms); | ||
73 | } | ||
74 | |||
75 | static void update_fb(struct drm_crtc *crtc, bool async, | ||
76 | struct drm_framebuffer *new_fb) | ||
77 | { | ||
78 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
79 | struct drm_framebuffer *old_fb = mdp4_crtc->fb; | ||
80 | |||
81 | if (old_fb) | ||
82 | drm_flip_work_queue(&mdp4_crtc->unref_fb_work, old_fb); | ||
83 | |||
84 | /* grab reference to incoming scanout fb: */ | ||
85 | drm_framebuffer_reference(new_fb); | ||
86 | mdp4_crtc->base.fb = new_fb; | ||
87 | mdp4_crtc->fb = new_fb; | ||
88 | |||
89 | if (!async) { | ||
90 | /* enable vblank to pick up the old_fb */ | ||
91 | mdp4_irq_register(get_kms(crtc), &mdp4_crtc->vblank); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | static void complete_flip(struct drm_crtc *crtc, bool canceled) | ||
96 | { | ||
97 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
98 | struct drm_device *dev = crtc->dev; | ||
99 | struct drm_pending_vblank_event *event; | ||
100 | unsigned long flags; | ||
101 | |||
102 | spin_lock_irqsave(&dev->event_lock, flags); | ||
103 | event = mdp4_crtc->event; | ||
104 | if (event) { | ||
105 | mdp4_crtc->event = NULL; | ||
106 | if (canceled) | ||
107 | event->base.destroy(&event->base); | ||
108 | else | ||
109 | drm_send_vblank_event(dev, mdp4_crtc->id, event); | ||
110 | } | ||
111 | spin_unlock_irqrestore(&dev->event_lock, flags); | ||
112 | } | ||
113 | |||
114 | static void crtc_flush(struct drm_crtc *crtc) | ||
115 | { | ||
116 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
117 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
118 | uint32_t flush = 0; | ||
119 | |||
120 | flush |= pipe2flush(mdp4_plane_pipe(mdp4_crtc->plane)); | ||
121 | flush |= ovlp2flush(mdp4_crtc->ovlp); | ||
122 | |||
123 | DBG("%s: flush=%08x", mdp4_crtc->name, flush); | ||
124 | |||
125 | mdp4_write(mdp4_kms, REG_MDP4_OVERLAY_FLUSH, flush); | ||
126 | } | ||
127 | |||
128 | static void pageflip_worker(struct work_struct *work) | ||
129 | { | ||
130 | struct mdp4_crtc *mdp4_crtc = | ||
131 | container_of(work, struct mdp4_crtc, pageflip_work); | ||
132 | struct drm_crtc *crtc = &mdp4_crtc->base; | ||
133 | |||
134 | mdp4_plane_set_scanout(mdp4_crtc->plane, crtc->fb); | ||
135 | crtc_flush(crtc); | ||
136 | |||
137 | /* enable vblank to complete flip: */ | ||
138 | mdp4_irq_register(get_kms(crtc), &mdp4_crtc->vblank); | ||
139 | } | ||
140 | |||
141 | static void unref_fb_worker(struct drm_flip_work *work, void *val) | ||
142 | { | ||
143 | struct mdp4_crtc *mdp4_crtc = | ||
144 | container_of(work, struct mdp4_crtc, unref_fb_work); | ||
145 | struct drm_device *dev = mdp4_crtc->base.dev; | ||
146 | |||
147 | mutex_lock(&dev->mode_config.mutex); | ||
148 | drm_framebuffer_unreference(val); | ||
149 | mutex_unlock(&dev->mode_config.mutex); | ||
150 | } | ||
151 | |||
152 | static void unref_cursor_worker(struct drm_flip_work *work, void *val) | ||
153 | { | ||
154 | struct mdp4_crtc *mdp4_crtc = | ||
155 | container_of(work, struct mdp4_crtc, unref_cursor_work); | ||
156 | struct mdp4_kms *mdp4_kms = get_kms(&mdp4_crtc->base); | ||
157 | |||
158 | msm_gem_put_iova(val, mdp4_kms->id); | ||
159 | drm_gem_object_unreference_unlocked(val); | ||
160 | } | ||
161 | |||
162 | static void mdp4_crtc_destroy(struct drm_crtc *crtc) | ||
163 | { | ||
164 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
165 | |||
166 | mdp4_crtc->plane->funcs->destroy(mdp4_crtc->plane); | ||
167 | |||
168 | drm_crtc_cleanup(crtc); | ||
169 | drm_flip_work_cleanup(&mdp4_crtc->unref_fb_work); | ||
170 | drm_flip_work_cleanup(&mdp4_crtc->unref_cursor_work); | ||
171 | |||
172 | kfree(mdp4_crtc); | ||
173 | } | ||
174 | |||
175 | static void mdp4_crtc_dpms(struct drm_crtc *crtc, int mode) | ||
176 | { | ||
177 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
178 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
179 | bool enabled = (mode == DRM_MODE_DPMS_ON); | ||
180 | |||
181 | DBG("%s: mode=%d", mdp4_crtc->name, mode); | ||
182 | |||
183 | if (enabled != mdp4_crtc->enabled) { | ||
184 | if (enabled) { | ||
185 | mdp4_enable(mdp4_kms); | ||
186 | mdp4_irq_register(mdp4_kms, &mdp4_crtc->err); | ||
187 | } else { | ||
188 | mdp4_irq_unregister(mdp4_kms, &mdp4_crtc->err); | ||
189 | mdp4_disable(mdp4_kms); | ||
190 | } | ||
191 | mdp4_crtc->enabled = enabled; | ||
192 | } | ||
193 | } | ||
194 | |||
195 | static bool mdp4_crtc_mode_fixup(struct drm_crtc *crtc, | ||
196 | const struct drm_display_mode *mode, | ||
197 | struct drm_display_mode *adjusted_mode) | ||
198 | { | ||
199 | return true; | ||
200 | } | ||
201 | |||
202 | static void blend_setup(struct drm_crtc *crtc) | ||
203 | { | ||
204 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
205 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
206 | int i, ovlp = mdp4_crtc->ovlp; | ||
207 | uint32_t mixer_cfg = 0; | ||
208 | |||
209 | /* | ||
210 | * This probably would also need to be triggered by any attached | ||
211 | * plane when it changes.. for now since we are only using a single | ||
212 | * private plane, the configuration is hard-coded: | ||
213 | */ | ||
214 | |||
215 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_LOW0(ovlp), 0); | ||
216 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_LOW1(ovlp), 0); | ||
217 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_HIGH0(ovlp), 0); | ||
218 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_HIGH1(ovlp), 0); | ||
219 | |||
220 | for (i = 0; i < 4; i++) { | ||
221 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_FG_ALPHA(ovlp, i), 0); | ||
222 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_BG_ALPHA(ovlp, i), 0); | ||
223 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_OP(ovlp, i), | ||
224 | MDP4_OVLP_STAGE_OP_FG_ALPHA(FG_CONST) | | ||
225 | MDP4_OVLP_STAGE_OP_BG_ALPHA(BG_CONST)); | ||
226 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_CO3(ovlp, i), 0); | ||
227 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_LOW0(ovlp, i), 0); | ||
228 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_LOW1(ovlp, i), 0); | ||
229 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_HIGH0(ovlp, i), 0); | ||
230 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_HIGH1(ovlp, i), 0); | ||
231 | } | ||
232 | |||
233 | /* TODO single register for all CRTCs, so this won't work properly | ||
234 | * when multiple CRTCs are active.. | ||
235 | */ | ||
236 | switch (mdp4_plane_pipe(mdp4_crtc->plane)) { | ||
237 | case VG1: | ||
238 | mixer_cfg = MDP4_LAYERMIXER_IN_CFG_PIPE0(STAGE_BASE) | | ||
239 | COND(mdp4_crtc->mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE0_MIXER1); | ||
240 | break; | ||
241 | case VG2: | ||
242 | mixer_cfg = MDP4_LAYERMIXER_IN_CFG_PIPE1(STAGE_BASE) | | ||
243 | COND(mdp4_crtc->mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE1_MIXER1); | ||
244 | break; | ||
245 | case RGB1: | ||
246 | mixer_cfg = MDP4_LAYERMIXER_IN_CFG_PIPE2(STAGE_BASE) | | ||
247 | COND(mdp4_crtc->mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE2_MIXER1); | ||
248 | break; | ||
249 | case RGB2: | ||
250 | mixer_cfg = MDP4_LAYERMIXER_IN_CFG_PIPE3(STAGE_BASE) | | ||
251 | COND(mdp4_crtc->mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE3_MIXER1); | ||
252 | break; | ||
253 | case RGB3: | ||
254 | mixer_cfg = MDP4_LAYERMIXER_IN_CFG_PIPE4(STAGE_BASE) | | ||
255 | COND(mdp4_crtc->mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE4_MIXER1); | ||
256 | break; | ||
257 | case VG3: | ||
258 | mixer_cfg = MDP4_LAYERMIXER_IN_CFG_PIPE5(STAGE_BASE) | | ||
259 | COND(mdp4_crtc->mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE5_MIXER1); | ||
260 | break; | ||
261 | case VG4: | ||
262 | mixer_cfg = MDP4_LAYERMIXER_IN_CFG_PIPE6(STAGE_BASE) | | ||
263 | COND(mdp4_crtc->mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE6_MIXER1); | ||
264 | break; | ||
265 | default: | ||
266 | WARN_ON("invalid pipe"); | ||
267 | break; | ||
268 | } | ||
269 | mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG, mixer_cfg); | ||
270 | } | ||
271 | |||
272 | static int mdp4_crtc_mode_set(struct drm_crtc *crtc, | ||
273 | struct drm_display_mode *mode, | ||
274 | struct drm_display_mode *adjusted_mode, | ||
275 | int x, int y, | ||
276 | struct drm_framebuffer *old_fb) | ||
277 | { | ||
278 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
279 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
280 | enum mdp4_dma dma = mdp4_crtc->dma; | ||
281 | int ret, ovlp = mdp4_crtc->ovlp; | ||
282 | |||
283 | mode = adjusted_mode; | ||
284 | |||
285 | DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", | ||
286 | mdp4_crtc->name, mode->base.id, mode->name, | ||
287 | mode->vrefresh, mode->clock, | ||
288 | mode->hdisplay, mode->hsync_start, | ||
289 | mode->hsync_end, mode->htotal, | ||
290 | mode->vdisplay, mode->vsync_start, | ||
291 | mode->vsync_end, mode->vtotal, | ||
292 | mode->type, mode->flags); | ||
293 | |||
294 | mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_SIZE(dma), | ||
295 | MDP4_DMA_SRC_SIZE_WIDTH(mode->hdisplay) | | ||
296 | MDP4_DMA_SRC_SIZE_HEIGHT(mode->vdisplay)); | ||
297 | |||
298 | /* take data from pipe: */ | ||
299 | mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_BASE(dma), 0); | ||
300 | mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_STRIDE(dma), | ||
301 | crtc->fb->pitches[0]); | ||
302 | mdp4_write(mdp4_kms, REG_MDP4_DMA_DST_SIZE(dma), | ||
303 | MDP4_DMA_DST_SIZE_WIDTH(0) | | ||
304 | MDP4_DMA_DST_SIZE_HEIGHT(0)); | ||
305 | |||
306 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_BASE(ovlp), 0); | ||
307 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_SIZE(ovlp), | ||
308 | MDP4_OVLP_SIZE_WIDTH(mode->hdisplay) | | ||
309 | MDP4_OVLP_SIZE_HEIGHT(mode->vdisplay)); | ||
310 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_STRIDE(ovlp), | ||
311 | crtc->fb->pitches[0]); | ||
312 | |||
313 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_CFG(ovlp), 1); | ||
314 | |||
315 | update_fb(crtc, false, crtc->fb); | ||
316 | |||
317 | ret = mdp4_plane_mode_set(mdp4_crtc->plane, crtc, crtc->fb, | ||
318 | 0, 0, mode->hdisplay, mode->vdisplay, | ||
319 | x << 16, y << 16, | ||
320 | mode->hdisplay << 16, mode->vdisplay << 16); | ||
321 | if (ret) { | ||
322 | dev_err(crtc->dev->dev, "%s: failed to set mode on plane: %d\n", | ||
323 | mdp4_crtc->name, ret); | ||
324 | return ret; | ||
325 | } | ||
326 | |||
327 | if (dma == DMA_E) { | ||
328 | mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(0), 0x00ff0000); | ||
329 | mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(1), 0x00ff0000); | ||
330 | mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(2), 0x00ff0000); | ||
331 | } | ||
332 | |||
333 | return 0; | ||
334 | } | ||
335 | |||
336 | static void mdp4_crtc_prepare(struct drm_crtc *crtc) | ||
337 | { | ||
338 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
339 | DBG("%s", mdp4_crtc->name); | ||
340 | /* make sure we hold a ref to mdp clks while setting up mode: */ | ||
341 | mdp4_enable(get_kms(crtc)); | ||
342 | mdp4_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); | ||
343 | } | ||
344 | |||
345 | static void mdp4_crtc_commit(struct drm_crtc *crtc) | ||
346 | { | ||
347 | mdp4_crtc_dpms(crtc, DRM_MODE_DPMS_ON); | ||
348 | crtc_flush(crtc); | ||
349 | /* drop the ref to mdp clk's that we got in prepare: */ | ||
350 | mdp4_disable(get_kms(crtc)); | ||
351 | } | ||
352 | |||
353 | static int mdp4_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, | ||
354 | struct drm_framebuffer *old_fb) | ||
355 | { | ||
356 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
357 | struct drm_plane *plane = mdp4_crtc->plane; | ||
358 | struct drm_display_mode *mode = &crtc->mode; | ||
359 | |||
360 | update_fb(crtc, false, crtc->fb); | ||
361 | |||
362 | return mdp4_plane_mode_set(plane, crtc, crtc->fb, | ||
363 | 0, 0, mode->hdisplay, mode->vdisplay, | ||
364 | x << 16, y << 16, | ||
365 | mode->hdisplay << 16, mode->vdisplay << 16); | ||
366 | } | ||
367 | |||
368 | static void mdp4_crtc_load_lut(struct drm_crtc *crtc) | ||
369 | { | ||
370 | } | ||
371 | |||
372 | static int mdp4_crtc_page_flip(struct drm_crtc *crtc, | ||
373 | struct drm_framebuffer *new_fb, | ||
374 | struct drm_pending_vblank_event *event) | ||
375 | { | ||
376 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
377 | struct drm_device *dev = crtc->dev; | ||
378 | struct drm_gem_object *obj; | ||
379 | |||
380 | if (mdp4_crtc->event) { | ||
381 | dev_err(dev->dev, "already pending flip!\n"); | ||
382 | return -EBUSY; | ||
383 | } | ||
384 | |||
385 | obj = msm_framebuffer_bo(new_fb, 0); | ||
386 | |||
387 | mdp4_crtc->event = event; | ||
388 | update_fb(crtc, true, new_fb); | ||
389 | |||
390 | return msm_gem_queue_inactive_work(obj, | ||
391 | &mdp4_crtc->pageflip_work); | ||
392 | } | ||
393 | |||
394 | static int mdp4_crtc_set_property(struct drm_crtc *crtc, | ||
395 | struct drm_property *property, uint64_t val) | ||
396 | { | ||
397 | // XXX | ||
398 | return -EINVAL; | ||
399 | } | ||
400 | |||
401 | #define CURSOR_WIDTH 64 | ||
402 | #define CURSOR_HEIGHT 64 | ||
403 | |||
404 | /* called from IRQ to update cursor related registers (if needed). The | ||
405 | * cursor registers, other than x/y position, appear not to be double | ||
406 | * buffered, and changing them other than from vblank seems to trigger | ||
407 | * underflow. | ||
408 | */ | ||
409 | static void update_cursor(struct drm_crtc *crtc) | ||
410 | { | ||
411 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
412 | enum mdp4_dma dma = mdp4_crtc->dma; | ||
413 | unsigned long flags; | ||
414 | |||
415 | spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags); | ||
416 | if (mdp4_crtc->cursor.stale) { | ||
417 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
418 | struct drm_gem_object *next_bo = mdp4_crtc->cursor.next_bo; | ||
419 | struct drm_gem_object *prev_bo = mdp4_crtc->cursor.scanout_bo; | ||
420 | uint32_t iova = mdp4_crtc->cursor.next_iova; | ||
421 | |||
422 | if (next_bo) { | ||
423 | /* take a obj ref + iova ref when we start scanning out: */ | ||
424 | drm_gem_object_reference(next_bo); | ||
425 | msm_gem_get_iova_locked(next_bo, mdp4_kms->id, &iova); | ||
426 | |||
427 | /* enable cursor: */ | ||
428 | mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_SIZE(dma), | ||
429 | MDP4_DMA_CURSOR_SIZE_WIDTH(mdp4_crtc->cursor.width) | | ||
430 | MDP4_DMA_CURSOR_SIZE_HEIGHT(mdp4_crtc->cursor.height)); | ||
431 | mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BASE(dma), iova); | ||
432 | mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BLEND_CONFIG(dma), | ||
433 | MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT(CURSOR_ARGB) | | ||
434 | MDP4_DMA_CURSOR_BLEND_CONFIG_CURSOR_EN); | ||
435 | } else { | ||
436 | /* disable cursor: */ | ||
437 | mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BASE(dma), 0); | ||
438 | mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BLEND_CONFIG(dma), | ||
439 | MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT(CURSOR_ARGB)); | ||
440 | } | ||
441 | |||
442 | /* and drop the iova ref + obj rev when done scanning out: */ | ||
443 | if (prev_bo) | ||
444 | drm_flip_work_queue(&mdp4_crtc->unref_cursor_work, prev_bo); | ||
445 | |||
446 | mdp4_crtc->cursor.scanout_bo = next_bo; | ||
447 | mdp4_crtc->cursor.stale = false; | ||
448 | } | ||
449 | spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags); | ||
450 | } | ||
451 | |||
452 | static int mdp4_crtc_cursor_set(struct drm_crtc *crtc, | ||
453 | struct drm_file *file_priv, uint32_t handle, | ||
454 | uint32_t width, uint32_t height) | ||
455 | { | ||
456 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
457 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
458 | struct drm_device *dev = crtc->dev; | ||
459 | struct drm_gem_object *cursor_bo, *old_bo; | ||
460 | unsigned long flags; | ||
461 | uint32_t iova; | ||
462 | int ret; | ||
463 | |||
464 | if ((width > CURSOR_WIDTH) || (height > CURSOR_HEIGHT)) { | ||
465 | dev_err(dev->dev, "bad cursor size: %dx%d\n", width, height); | ||
466 | return -EINVAL; | ||
467 | } | ||
468 | |||
469 | if (handle) { | ||
470 | cursor_bo = drm_gem_object_lookup(dev, file_priv, handle); | ||
471 | if (!cursor_bo) | ||
472 | return -ENOENT; | ||
473 | } else { | ||
474 | cursor_bo = NULL; | ||
475 | } | ||
476 | |||
477 | if (cursor_bo) { | ||
478 | ret = msm_gem_get_iova(cursor_bo, mdp4_kms->id, &iova); | ||
479 | if (ret) | ||
480 | goto fail; | ||
481 | } else { | ||
482 | iova = 0; | ||
483 | } | ||
484 | |||
485 | spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags); | ||
486 | old_bo = mdp4_crtc->cursor.next_bo; | ||
487 | mdp4_crtc->cursor.next_bo = cursor_bo; | ||
488 | mdp4_crtc->cursor.next_iova = iova; | ||
489 | mdp4_crtc->cursor.width = width; | ||
490 | mdp4_crtc->cursor.height = height; | ||
491 | mdp4_crtc->cursor.stale = true; | ||
492 | spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags); | ||
493 | |||
494 | if (old_bo) { | ||
495 | /* drop our previous reference: */ | ||
496 | msm_gem_put_iova(old_bo, mdp4_kms->id); | ||
497 | drm_gem_object_unreference_unlocked(old_bo); | ||
498 | } | ||
499 | |||
500 | return 0; | ||
501 | |||
502 | fail: | ||
503 | drm_gem_object_unreference_unlocked(cursor_bo); | ||
504 | return ret; | ||
505 | } | ||
506 | |||
507 | static int mdp4_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) | ||
508 | { | ||
509 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
510 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
511 | enum mdp4_dma dma = mdp4_crtc->dma; | ||
512 | |||
513 | mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_POS(dma), | ||
514 | MDP4_DMA_CURSOR_POS_X(x) | | ||
515 | MDP4_DMA_CURSOR_POS_Y(y)); | ||
516 | |||
517 | return 0; | ||
518 | } | ||
519 | |||
520 | static const struct drm_crtc_funcs mdp4_crtc_funcs = { | ||
521 | .set_config = drm_crtc_helper_set_config, | ||
522 | .destroy = mdp4_crtc_destroy, | ||
523 | .page_flip = mdp4_crtc_page_flip, | ||
524 | .set_property = mdp4_crtc_set_property, | ||
525 | .cursor_set = mdp4_crtc_cursor_set, | ||
526 | .cursor_move = mdp4_crtc_cursor_move, | ||
527 | }; | ||
528 | |||
529 | static const struct drm_crtc_helper_funcs mdp4_crtc_helper_funcs = { | ||
530 | .dpms = mdp4_crtc_dpms, | ||
531 | .mode_fixup = mdp4_crtc_mode_fixup, | ||
532 | .mode_set = mdp4_crtc_mode_set, | ||
533 | .prepare = mdp4_crtc_prepare, | ||
534 | .commit = mdp4_crtc_commit, | ||
535 | .mode_set_base = mdp4_crtc_mode_set_base, | ||
536 | .load_lut = mdp4_crtc_load_lut, | ||
537 | }; | ||
538 | |||
539 | static void mdp4_crtc_vblank_irq(struct mdp4_irq *irq, uint32_t irqstatus) | ||
540 | { | ||
541 | struct mdp4_crtc *mdp4_crtc = container_of(irq, struct mdp4_crtc, vblank); | ||
542 | struct drm_crtc *crtc = &mdp4_crtc->base; | ||
543 | struct msm_drm_private *priv = crtc->dev->dev_private; | ||
544 | |||
545 | update_cursor(crtc); | ||
546 | complete_flip(crtc, false); | ||
547 | mdp4_irq_unregister(get_kms(crtc), &mdp4_crtc->vblank); | ||
548 | |||
549 | drm_flip_work_commit(&mdp4_crtc->unref_fb_work, priv->wq); | ||
550 | drm_flip_work_commit(&mdp4_crtc->unref_cursor_work, priv->wq); | ||
551 | } | ||
552 | |||
553 | static void mdp4_crtc_err_irq(struct mdp4_irq *irq, uint32_t irqstatus) | ||
554 | { | ||
555 | struct mdp4_crtc *mdp4_crtc = container_of(irq, struct mdp4_crtc, err); | ||
556 | struct drm_crtc *crtc = &mdp4_crtc->base; | ||
557 | DBG("%s: error: %08x", mdp4_crtc->name, irqstatus); | ||
558 | crtc_flush(crtc); | ||
559 | } | ||
560 | |||
561 | uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc) | ||
562 | { | ||
563 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
564 | return mdp4_crtc->vblank.irqmask; | ||
565 | } | ||
566 | |||
567 | void mdp4_crtc_cancel_pending_flip(struct drm_crtc *crtc) | ||
568 | { | ||
569 | complete_flip(crtc, true); | ||
570 | } | ||
571 | |||
572 | /* set dma config, ie. the format the encoder wants. */ | ||
573 | void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config) | ||
574 | { | ||
575 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
576 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
577 | |||
578 | mdp4_write(mdp4_kms, REG_MDP4_DMA_CONFIG(mdp4_crtc->dma), config); | ||
579 | } | ||
580 | |||
581 | /* set interface for routing crtc->encoder: */ | ||
582 | void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf) | ||
583 | { | ||
584 | struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); | ||
585 | struct mdp4_kms *mdp4_kms = get_kms(crtc); | ||
586 | uint32_t intf_sel; | ||
587 | |||
588 | intf_sel = mdp4_read(mdp4_kms, REG_MDP4_DISP_INTF_SEL); | ||
589 | |||
590 | switch (mdp4_crtc->dma) { | ||
591 | case DMA_P: | ||
592 | intf_sel &= ~MDP4_DISP_INTF_SEL_PRIM__MASK; | ||
593 | intf_sel |= MDP4_DISP_INTF_SEL_PRIM(intf); | ||
594 | break; | ||
595 | case DMA_S: | ||
596 | intf_sel &= ~MDP4_DISP_INTF_SEL_SEC__MASK; | ||
597 | intf_sel |= MDP4_DISP_INTF_SEL_SEC(intf); | ||
598 | break; | ||
599 | case DMA_E: | ||
600 | intf_sel &= ~MDP4_DISP_INTF_SEL_EXT__MASK; | ||
601 | intf_sel |= MDP4_DISP_INTF_SEL_EXT(intf); | ||
602 | break; | ||
603 | } | ||
604 | |||
605 | if (intf == INTF_DSI_VIDEO) { | ||
606 | intf_sel &= ~MDP4_DISP_INTF_SEL_DSI_CMD; | ||
607 | intf_sel |= MDP4_DISP_INTF_SEL_DSI_VIDEO; | ||
608 | mdp4_crtc->mixer = 0; | ||
609 | } else if (intf == INTF_DSI_CMD) { | ||
610 | intf_sel &= ~MDP4_DISP_INTF_SEL_DSI_VIDEO; | ||
611 | intf_sel |= MDP4_DISP_INTF_SEL_DSI_CMD; | ||
612 | mdp4_crtc->mixer = 0; | ||
613 | } else if (intf == INTF_LCDC_DTV){ | ||
614 | mdp4_crtc->mixer = 1; | ||
615 | } | ||
616 | |||
617 | blend_setup(crtc); | ||
618 | |||
619 | DBG("%s: intf_sel=%08x", mdp4_crtc->name, intf_sel); | ||
620 | |||
621 | mdp4_write(mdp4_kms, REG_MDP4_DISP_INTF_SEL, intf_sel); | ||
622 | } | ||
623 | |||
624 | static const char *dma_names[] = { | ||
625 | "DMA_P", "DMA_S", "DMA_E", | ||
626 | }; | ||
627 | |||
628 | /* initialize crtc */ | ||
629 | struct drm_crtc *mdp4_crtc_init(struct drm_device *dev, | ||
630 | struct drm_plane *plane, int id, int ovlp_id, | ||
631 | enum mdp4_dma dma_id) | ||
632 | { | ||
633 | struct drm_crtc *crtc = NULL; | ||
634 | struct mdp4_crtc *mdp4_crtc; | ||
635 | int ret; | ||
636 | |||
637 | mdp4_crtc = kzalloc(sizeof(*mdp4_crtc), GFP_KERNEL); | ||
638 | if (!mdp4_crtc) { | ||
639 | ret = -ENOMEM; | ||
640 | goto fail; | ||
641 | } | ||
642 | |||
643 | crtc = &mdp4_crtc->base; | ||
644 | |||
645 | mdp4_crtc->plane = plane; | ||
646 | mdp4_crtc->plane->crtc = crtc; | ||
647 | |||
648 | mdp4_crtc->ovlp = ovlp_id; | ||
649 | mdp4_crtc->dma = dma_id; | ||
650 | |||
651 | mdp4_crtc->vblank.irqmask = dma2irq(mdp4_crtc->dma); | ||
652 | mdp4_crtc->vblank.irq = mdp4_crtc_vblank_irq; | ||
653 | |||
654 | mdp4_crtc->err.irqmask = dma2err(mdp4_crtc->dma); | ||
655 | mdp4_crtc->err.irq = mdp4_crtc_err_irq; | ||
656 | |||
657 | snprintf(mdp4_crtc->name, sizeof(mdp4_crtc->name), "%s:%d", | ||
658 | dma_names[dma_id], ovlp_id); | ||
659 | |||
660 | spin_lock_init(&mdp4_crtc->cursor.lock); | ||
661 | |||
662 | ret = drm_flip_work_init(&mdp4_crtc->unref_fb_work, 16, | ||
663 | "unref fb", unref_fb_worker); | ||
664 | if (ret) | ||
665 | goto fail; | ||
666 | |||
667 | ret = drm_flip_work_init(&mdp4_crtc->unref_cursor_work, 64, | ||
668 | "unref cursor", unref_cursor_worker); | ||
669 | |||
670 | INIT_WORK(&mdp4_crtc->pageflip_work, pageflip_worker); | ||
671 | |||
672 | drm_crtc_init(dev, crtc, &mdp4_crtc_funcs); | ||
673 | drm_crtc_helper_add(crtc, &mdp4_crtc_helper_funcs); | ||
674 | |||
675 | mdp4_plane_install_properties(mdp4_crtc->plane, &crtc->base); | ||
676 | |||
677 | return crtc; | ||
678 | |||
679 | fail: | ||
680 | if (crtc) | ||
681 | mdp4_crtc_destroy(crtc); | ||
682 | |||
683 | return ERR_PTR(ret); | ||
684 | } | ||
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_dtv_encoder.c b/drivers/gpu/drm/msm/mdp4/mdp4_dtv_encoder.c new file mode 100644 index 000000000000..06d49e309d34 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp4/mdp4_dtv_encoder.c | |||
@@ -0,0 +1,317 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include <mach/clk.h> | ||
19 | |||
20 | #include "mdp4_kms.h" | ||
21 | #include "msm_connector.h" | ||
22 | |||
23 | #include "drm_crtc.h" | ||
24 | #include "drm_crtc_helper.h" | ||
25 | |||
26 | |||
27 | struct mdp4_dtv_encoder { | ||
28 | struct drm_encoder base; | ||
29 | struct clk *src_clk; | ||
30 | struct clk *hdmi_clk; | ||
31 | struct clk *mdp_clk; | ||
32 | unsigned long int pixclock; | ||
33 | bool enabled; | ||
34 | uint32_t bsc; | ||
35 | }; | ||
36 | #define to_mdp4_dtv_encoder(x) container_of(x, struct mdp4_dtv_encoder, base) | ||
37 | |||
38 | static struct mdp4_kms *get_kms(struct drm_encoder *encoder) | ||
39 | { | ||
40 | struct msm_drm_private *priv = encoder->dev->dev_private; | ||
41 | return to_mdp4_kms(priv->kms); | ||
42 | } | ||
43 | |||
44 | #ifdef CONFIG_MSM_BUS_SCALING | ||
45 | #include <mach/board.h> | ||
46 | /* not ironically named at all.. no, really.. */ | ||
47 | static void bs_init(struct mdp4_dtv_encoder *mdp4_dtv_encoder) | ||
48 | { | ||
49 | struct drm_device *dev = mdp4_dtv_encoder->base.dev; | ||
50 | struct lcdc_platform_data *dtv_pdata = mdp4_find_pdata("dtv.0"); | ||
51 | |||
52 | if (!dtv_pdata) { | ||
53 | dev_err(dev->dev, "could not find dtv pdata\n"); | ||
54 | return; | ||
55 | } | ||
56 | |||
57 | if (dtv_pdata->bus_scale_table) { | ||
58 | mdp4_dtv_encoder->bsc = msm_bus_scale_register_client( | ||
59 | dtv_pdata->bus_scale_table); | ||
60 | DBG("bus scale client: %08x", mdp4_dtv_encoder->bsc); | ||
61 | DBG("lcdc_power_save: %p", dtv_pdata->lcdc_power_save); | ||
62 | if (dtv_pdata->lcdc_power_save) | ||
63 | dtv_pdata->lcdc_power_save(1); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | static void bs_fini(struct mdp4_dtv_encoder *mdp4_dtv_encoder) | ||
68 | { | ||
69 | if (mdp4_dtv_encoder->bsc) { | ||
70 | msm_bus_scale_unregister_client(mdp4_dtv_encoder->bsc); | ||
71 | mdp4_dtv_encoder->bsc = 0; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | static void bs_set(struct mdp4_dtv_encoder *mdp4_dtv_encoder, int idx) | ||
76 | { | ||
77 | if (mdp4_dtv_encoder->bsc) { | ||
78 | DBG("set bus scaling: %d", idx); | ||
79 | msm_bus_scale_client_update_request(mdp4_dtv_encoder->bsc, idx); | ||
80 | } | ||
81 | } | ||
82 | #else | ||
83 | static void bs_init(struct mdp4_dtv_encoder *mdp4_dtv_encoder) {} | ||
84 | static void bs_fini(struct mdp4_dtv_encoder *mdp4_dtv_encoder) {} | ||
85 | static void bs_set(struct mdp4_dtv_encoder *mdp4_dtv_encoder, int idx) {} | ||
86 | #endif | ||
87 | |||
88 | static void mdp4_dtv_encoder_destroy(struct drm_encoder *encoder) | ||
89 | { | ||
90 | struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); | ||
91 | bs_fini(mdp4_dtv_encoder); | ||
92 | drm_encoder_cleanup(encoder); | ||
93 | kfree(mdp4_dtv_encoder); | ||
94 | } | ||
95 | |||
96 | static const struct drm_encoder_funcs mdp4_dtv_encoder_funcs = { | ||
97 | .destroy = mdp4_dtv_encoder_destroy, | ||
98 | }; | ||
99 | |||
100 | static void mdp4_dtv_encoder_dpms(struct drm_encoder *encoder, int mode) | ||
101 | { | ||
102 | struct drm_device *dev = encoder->dev; | ||
103 | struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); | ||
104 | struct msm_connector *msm_connector = get_connector(encoder); | ||
105 | struct mdp4_kms *mdp4_kms = get_kms(encoder); | ||
106 | bool enabled = (mode == DRM_MODE_DPMS_ON); | ||
107 | |||
108 | DBG("mode=%d", mode); | ||
109 | |||
110 | if (enabled == mdp4_dtv_encoder->enabled) | ||
111 | return; | ||
112 | |||
113 | if (enabled) { | ||
114 | unsigned long pc = mdp4_dtv_encoder->pixclock; | ||
115 | int ret; | ||
116 | |||
117 | bs_set(mdp4_dtv_encoder, 1); | ||
118 | |||
119 | if (msm_connector) | ||
120 | msm_connector->funcs->dpms(msm_connector, mode); | ||
121 | |||
122 | DBG("setting src_clk=%lu", pc); | ||
123 | |||
124 | ret = clk_set_rate(mdp4_dtv_encoder->src_clk, pc); | ||
125 | if (ret) | ||
126 | dev_err(dev->dev, "failed to set src_clk to %lu: %d\n", pc, ret); | ||
127 | clk_prepare_enable(mdp4_dtv_encoder->src_clk); | ||
128 | ret = clk_prepare_enable(mdp4_dtv_encoder->hdmi_clk); | ||
129 | if (ret) | ||
130 | dev_err(dev->dev, "failed to enable hdmi_clk: %d\n", ret); | ||
131 | ret = clk_prepare_enable(mdp4_dtv_encoder->mdp_clk); | ||
132 | if (ret) | ||
133 | dev_err(dev->dev, "failed to enabled mdp_clk: %d\n", ret); | ||
134 | |||
135 | mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 1); | ||
136 | } else { | ||
137 | mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0); | ||
138 | |||
139 | /* | ||
140 | * Wait for a vsync so we know the ENABLE=0 latched before | ||
141 | * the (connector) source of the vsync's gets disabled, | ||
142 | * otherwise we end up in a funny state if we re-enable | ||
143 | * before the disable latches, which results that some of | ||
144 | * the settings changes for the new modeset (like new | ||
145 | * scanout buffer) don't latch properly.. | ||
146 | */ | ||
147 | mdp4_irq_wait(mdp4_kms, MDP4_IRQ_EXTERNAL_VSYNC); | ||
148 | |||
149 | clk_disable_unprepare(mdp4_dtv_encoder->src_clk); | ||
150 | clk_disable_unprepare(mdp4_dtv_encoder->hdmi_clk); | ||
151 | clk_disable_unprepare(mdp4_dtv_encoder->mdp_clk); | ||
152 | |||
153 | if (msm_connector) | ||
154 | msm_connector->funcs->dpms(msm_connector, mode); | ||
155 | |||
156 | bs_set(mdp4_dtv_encoder, 0); | ||
157 | } | ||
158 | |||
159 | mdp4_dtv_encoder->enabled = enabled; | ||
160 | } | ||
161 | |||
162 | static bool mdp4_dtv_encoder_mode_fixup(struct drm_encoder *encoder, | ||
163 | const struct drm_display_mode *mode, | ||
164 | struct drm_display_mode *adjusted_mode) | ||
165 | { | ||
166 | return true; | ||
167 | } | ||
168 | |||
169 | static void mdp4_dtv_encoder_mode_set(struct drm_encoder *encoder, | ||
170 | struct drm_display_mode *mode, | ||
171 | struct drm_display_mode *adjusted_mode) | ||
172 | { | ||
173 | struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); | ||
174 | struct msm_connector *msm_connector = get_connector(encoder); | ||
175 | struct mdp4_kms *mdp4_kms = get_kms(encoder); | ||
176 | uint32_t dtv_hsync_skew, vsync_period, vsync_len, ctrl_pol; | ||
177 | uint32_t display_v_start, display_v_end; | ||
178 | uint32_t hsync_start_x, hsync_end_x; | ||
179 | |||
180 | mode = adjusted_mode; | ||
181 | |||
182 | DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", | ||
183 | mode->base.id, mode->name, | ||
184 | mode->vrefresh, mode->clock, | ||
185 | mode->hdisplay, mode->hsync_start, | ||
186 | mode->hsync_end, mode->htotal, | ||
187 | mode->vdisplay, mode->vsync_start, | ||
188 | mode->vsync_end, mode->vtotal, | ||
189 | mode->type, mode->flags); | ||
190 | |||
191 | mdp4_dtv_encoder->pixclock = mode->clock * 1000; | ||
192 | |||
193 | DBG("pixclock=%lu", mdp4_dtv_encoder->pixclock); | ||
194 | |||
195 | ctrl_pol = 0; | ||
196 | if (mode->flags & DRM_MODE_FLAG_NHSYNC) | ||
197 | ctrl_pol |= MDP4_DTV_CTRL_POLARITY_HSYNC_LOW; | ||
198 | if (mode->flags & DRM_MODE_FLAG_NVSYNC) | ||
199 | ctrl_pol |= MDP4_DTV_CTRL_POLARITY_VSYNC_LOW; | ||
200 | /* probably need to get DATA_EN polarity from panel.. */ | ||
201 | |||
202 | dtv_hsync_skew = 0; /* get this from panel? */ | ||
203 | |||
204 | hsync_start_x = (mode->htotal - mode->hsync_start); | ||
205 | hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1; | ||
206 | |||
207 | vsync_period = mode->vtotal * mode->htotal; | ||
208 | vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal; | ||
209 | display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dtv_hsync_skew; | ||
210 | display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dtv_hsync_skew - 1; | ||
211 | |||
212 | mdp4_write(mdp4_kms, REG_MDP4_DTV_HSYNC_CTRL, | ||
213 | MDP4_DTV_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) | | ||
214 | MDP4_DTV_HSYNC_CTRL_PERIOD(mode->htotal)); | ||
215 | mdp4_write(mdp4_kms, REG_MDP4_DTV_VSYNC_PERIOD, vsync_period); | ||
216 | mdp4_write(mdp4_kms, REG_MDP4_DTV_VSYNC_LEN, vsync_len); | ||
217 | mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_HCTRL, | ||
218 | MDP4_DTV_DISPLAY_HCTRL_START(hsync_start_x) | | ||
219 | MDP4_DTV_DISPLAY_HCTRL_END(hsync_end_x)); | ||
220 | mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_VSTART, display_v_start); | ||
221 | mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_VEND, display_v_end); | ||
222 | mdp4_write(mdp4_kms, REG_MDP4_DTV_BORDER_CLR, 0); | ||
223 | mdp4_write(mdp4_kms, REG_MDP4_DTV_UNDERFLOW_CLR, | ||
224 | MDP4_DTV_UNDERFLOW_CLR_ENABLE_RECOVERY | | ||
225 | MDP4_DTV_UNDERFLOW_CLR_COLOR(0xff)); | ||
226 | mdp4_write(mdp4_kms, REG_MDP4_DTV_HSYNC_SKEW, dtv_hsync_skew); | ||
227 | mdp4_write(mdp4_kms, REG_MDP4_DTV_CTRL_POLARITY, ctrl_pol); | ||
228 | mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_HCTL, | ||
229 | MDP4_DTV_ACTIVE_HCTL_START(0) | | ||
230 | MDP4_DTV_ACTIVE_HCTL_END(0)); | ||
231 | mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_VSTART, 0); | ||
232 | mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_VEND, 0); | ||
233 | |||
234 | if (msm_connector) | ||
235 | msm_connector->funcs->mode_set(msm_connector, mode); | ||
236 | } | ||
237 | |||
238 | static void mdp4_dtv_encoder_prepare(struct drm_encoder *encoder) | ||
239 | { | ||
240 | mdp4_dtv_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); | ||
241 | } | ||
242 | |||
243 | static void mdp4_dtv_encoder_commit(struct drm_encoder *encoder) | ||
244 | { | ||
245 | mdp4_crtc_set_config(encoder->crtc, | ||
246 | MDP4_DMA_CONFIG_R_BPC(BPC8) | | ||
247 | MDP4_DMA_CONFIG_G_BPC(BPC8) | | ||
248 | MDP4_DMA_CONFIG_B_BPC(BPC8) | | ||
249 | MDP4_DMA_CONFIG_PACK(0x21)); | ||
250 | mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV); | ||
251 | mdp4_dtv_encoder_dpms(encoder, DRM_MODE_DPMS_ON); | ||
252 | } | ||
253 | |||
254 | static const struct drm_encoder_helper_funcs mdp4_dtv_encoder_helper_funcs = { | ||
255 | .dpms = mdp4_dtv_encoder_dpms, | ||
256 | .mode_fixup = mdp4_dtv_encoder_mode_fixup, | ||
257 | .mode_set = mdp4_dtv_encoder_mode_set, | ||
258 | .prepare = mdp4_dtv_encoder_prepare, | ||
259 | .commit = mdp4_dtv_encoder_commit, | ||
260 | }; | ||
261 | |||
262 | long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate) | ||
263 | { | ||
264 | struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); | ||
265 | return clk_round_rate(mdp4_dtv_encoder->src_clk, rate); | ||
266 | } | ||
267 | |||
268 | /* initialize encoder */ | ||
269 | struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev) | ||
270 | { | ||
271 | struct drm_encoder *encoder = NULL; | ||
272 | struct mdp4_dtv_encoder *mdp4_dtv_encoder; | ||
273 | int ret; | ||
274 | |||
275 | mdp4_dtv_encoder = kzalloc(sizeof(*mdp4_dtv_encoder), GFP_KERNEL); | ||
276 | if (!mdp4_dtv_encoder) { | ||
277 | ret = -ENOMEM; | ||
278 | goto fail; | ||
279 | } | ||
280 | |||
281 | encoder = &mdp4_dtv_encoder->base; | ||
282 | |||
283 | drm_encoder_init(dev, encoder, &mdp4_dtv_encoder_funcs, | ||
284 | DRM_MODE_ENCODER_TMDS); | ||
285 | drm_encoder_helper_add(encoder, &mdp4_dtv_encoder_helper_funcs); | ||
286 | |||
287 | mdp4_dtv_encoder->src_clk = devm_clk_get(dev->dev, "src_clk"); | ||
288 | if (IS_ERR(mdp4_dtv_encoder->src_clk)) { | ||
289 | dev_err(dev->dev, "failed to get src_clk\n"); | ||
290 | ret = PTR_ERR(mdp4_dtv_encoder->src_clk); | ||
291 | goto fail; | ||
292 | } | ||
293 | |||
294 | mdp4_dtv_encoder->hdmi_clk = devm_clk_get(dev->dev, "hdmi_clk"); | ||
295 | if (IS_ERR(mdp4_dtv_encoder->hdmi_clk)) { | ||
296 | dev_err(dev->dev, "failed to get hdmi_clk\n"); | ||
297 | ret = PTR_ERR(mdp4_dtv_encoder->hdmi_clk); | ||
298 | goto fail; | ||
299 | } | ||
300 | |||
301 | mdp4_dtv_encoder->mdp_clk = devm_clk_get(dev->dev, "mdp_clk"); | ||
302 | if (IS_ERR(mdp4_dtv_encoder->mdp_clk)) { | ||
303 | dev_err(dev->dev, "failed to get mdp_clk\n"); | ||
304 | ret = PTR_ERR(mdp4_dtv_encoder->mdp_clk); | ||
305 | goto fail; | ||
306 | } | ||
307 | |||
308 | bs_init(mdp4_dtv_encoder); | ||
309 | |||
310 | return encoder; | ||
311 | |||
312 | fail: | ||
313 | if (encoder) | ||
314 | mdp4_dtv_encoder_destroy(encoder); | ||
315 | |||
316 | return ERR_PTR(ret); | ||
317 | } | ||
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_format.c b/drivers/gpu/drm/msm/mdp4/mdp4_format.c new file mode 100644 index 000000000000..7b645f2e837a --- /dev/null +++ b/drivers/gpu/drm/msm/mdp4/mdp4_format.c | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | |||
19 | #include "msm_drv.h" | ||
20 | #include "mdp4_kms.h" | ||
21 | |||
22 | #define FMT(name, a, r, g, b, e0, e1, e2, e3, alpha, tight, c, cnt) { \ | ||
23 | .base = { .pixel_format = DRM_FORMAT_ ## name }, \ | ||
24 | .bpc_a = BPC ## a ## A, \ | ||
25 | .bpc_r = BPC ## r, \ | ||
26 | .bpc_g = BPC ## g, \ | ||
27 | .bpc_b = BPC ## b, \ | ||
28 | .unpack = { e0, e1, e2, e3 }, \ | ||
29 | .alpha_enable = alpha, \ | ||
30 | .unpack_tight = tight, \ | ||
31 | .cpp = c, \ | ||
32 | .unpack_count = cnt, \ | ||
33 | } | ||
34 | |||
35 | #define BPC0A 0 | ||
36 | |||
37 | static const struct mdp4_format formats[] = { | ||
38 | /* name a r g b e0 e1 e2 e3 alpha tight cpp cnt */ | ||
39 | FMT(ARGB8888, 8, 8, 8, 8, 1, 0, 2, 3, true, true, 4, 4), | ||
40 | FMT(XRGB8888, 8, 8, 8, 8, 1, 0, 2, 3, false, true, 4, 4), | ||
41 | FMT(RGB888, 0, 8, 8, 8, 1, 0, 2, 0, false, true, 3, 3), | ||
42 | FMT(BGR888, 0, 8, 8, 8, 2, 0, 1, 0, false, true, 3, 3), | ||
43 | FMT(RGB565, 0, 5, 6, 5, 1, 0, 2, 0, false, true, 2, 3), | ||
44 | FMT(BGR565, 0, 5, 6, 5, 2, 0, 1, 0, false, true, 2, 3), | ||
45 | }; | ||
46 | |||
47 | const struct msm_format *mdp4_get_format(struct msm_kms *kms, uint32_t format) | ||
48 | { | ||
49 | int i; | ||
50 | for (i = 0; i < ARRAY_SIZE(formats); i++) { | ||
51 | const struct mdp4_format *f = &formats[i]; | ||
52 | if (f->base.pixel_format == format) | ||
53 | return &f->base; | ||
54 | } | ||
55 | return NULL; | ||
56 | } | ||
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_irq.c b/drivers/gpu/drm/msm/mdp4/mdp4_irq.c new file mode 100644 index 000000000000..5c6b7fca4edd --- /dev/null +++ b/drivers/gpu/drm/msm/mdp4/mdp4_irq.c | |||
@@ -0,0 +1,203 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | |||
19 | #include "msm_drv.h" | ||
20 | #include "mdp4_kms.h" | ||
21 | |||
22 | |||
23 | struct mdp4_irq_wait { | ||
24 | struct mdp4_irq irq; | ||
25 | int count; | ||
26 | }; | ||
27 | |||
28 | static DECLARE_WAIT_QUEUE_HEAD(wait_event); | ||
29 | |||
30 | static DEFINE_SPINLOCK(list_lock); | ||
31 | |||
32 | static void update_irq(struct mdp4_kms *mdp4_kms) | ||
33 | { | ||
34 | struct mdp4_irq *irq; | ||
35 | uint32_t irqmask = mdp4_kms->vblank_mask; | ||
36 | |||
37 | BUG_ON(!spin_is_locked(&list_lock)); | ||
38 | |||
39 | list_for_each_entry(irq, &mdp4_kms->irq_list, node) | ||
40 | irqmask |= irq->irqmask; | ||
41 | |||
42 | mdp4_write(mdp4_kms, REG_MDP4_INTR_ENABLE, irqmask); | ||
43 | } | ||
44 | |||
45 | static void update_irq_unlocked(struct mdp4_kms *mdp4_kms) | ||
46 | { | ||
47 | unsigned long flags; | ||
48 | spin_lock_irqsave(&list_lock, flags); | ||
49 | update_irq(mdp4_kms); | ||
50 | spin_unlock_irqrestore(&list_lock, flags); | ||
51 | } | ||
52 | |||
53 | static void mdp4_irq_error_handler(struct mdp4_irq *irq, uint32_t irqstatus) | ||
54 | { | ||
55 | DRM_ERROR("errors: %08x\n", irqstatus); | ||
56 | } | ||
57 | |||
58 | void mdp4_irq_preinstall(struct msm_kms *kms) | ||
59 | { | ||
60 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
61 | mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, 0xffffffff); | ||
62 | } | ||
63 | |||
64 | int mdp4_irq_postinstall(struct msm_kms *kms) | ||
65 | { | ||
66 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
67 | struct mdp4_irq *error_handler = &mdp4_kms->error_handler; | ||
68 | |||
69 | INIT_LIST_HEAD(&mdp4_kms->irq_list); | ||
70 | |||
71 | error_handler->irq = mdp4_irq_error_handler; | ||
72 | error_handler->irqmask = MDP4_IRQ_PRIMARY_INTF_UDERRUN | | ||
73 | MDP4_IRQ_EXTERNAL_INTF_UDERRUN; | ||
74 | |||
75 | mdp4_irq_register(mdp4_kms, error_handler); | ||
76 | |||
77 | return 0; | ||
78 | } | ||
79 | |||
80 | void mdp4_irq_uninstall(struct msm_kms *kms) | ||
81 | { | ||
82 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
83 | mdp4_write(mdp4_kms, REG_MDP4_INTR_ENABLE, 0x00000000); | ||
84 | } | ||
85 | |||
86 | irqreturn_t mdp4_irq(struct msm_kms *kms) | ||
87 | { | ||
88 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
89 | struct drm_device *dev = mdp4_kms->dev; | ||
90 | struct msm_drm_private *priv = dev->dev_private; | ||
91 | struct mdp4_irq *handler, *n; | ||
92 | unsigned long flags; | ||
93 | unsigned int id; | ||
94 | uint32_t status; | ||
95 | |||
96 | status = mdp4_read(mdp4_kms, REG_MDP4_INTR_STATUS); | ||
97 | mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, status); | ||
98 | |||
99 | VERB("status=%08x", status); | ||
100 | |||
101 | for (id = 0; id < priv->num_crtcs; id++) | ||
102 | if (status & mdp4_crtc_vblank(priv->crtcs[id])) | ||
103 | drm_handle_vblank(dev, id); | ||
104 | |||
105 | spin_lock_irqsave(&list_lock, flags); | ||
106 | mdp4_kms->in_irq = true; | ||
107 | list_for_each_entry_safe(handler, n, &mdp4_kms->irq_list, node) { | ||
108 | if (handler->irqmask & status) { | ||
109 | spin_unlock_irqrestore(&list_lock, flags); | ||
110 | handler->irq(handler, handler->irqmask & status); | ||
111 | spin_lock_irqsave(&list_lock, flags); | ||
112 | } | ||
113 | } | ||
114 | mdp4_kms->in_irq = false; | ||
115 | update_irq(mdp4_kms); | ||
116 | spin_unlock_irqrestore(&list_lock, flags); | ||
117 | |||
118 | return IRQ_HANDLED; | ||
119 | } | ||
120 | |||
121 | int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc) | ||
122 | { | ||
123 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
124 | unsigned long flags; | ||
125 | |||
126 | spin_lock_irqsave(&list_lock, flags); | ||
127 | mdp4_kms->vblank_mask |= mdp4_crtc_vblank(crtc); | ||
128 | update_irq(mdp4_kms); | ||
129 | spin_unlock_irqrestore(&list_lock, flags); | ||
130 | |||
131 | return 0; | ||
132 | } | ||
133 | |||
134 | void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc) | ||
135 | { | ||
136 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
137 | unsigned long flags; | ||
138 | |||
139 | spin_lock_irqsave(&list_lock, flags); | ||
140 | mdp4_kms->vblank_mask &= ~mdp4_crtc_vblank(crtc); | ||
141 | update_irq(mdp4_kms); | ||
142 | spin_unlock_irqrestore(&list_lock, flags); | ||
143 | } | ||
144 | |||
145 | static void wait_irq(struct mdp4_irq *irq, uint32_t irqstatus) | ||
146 | { | ||
147 | struct mdp4_irq_wait *wait = | ||
148 | container_of(irq, struct mdp4_irq_wait, irq); | ||
149 | wait->count--; | ||
150 | wake_up_all(&wait_event); | ||
151 | } | ||
152 | |||
153 | void mdp4_irq_wait(struct mdp4_kms *mdp4_kms, uint32_t irqmask) | ||
154 | { | ||
155 | struct mdp4_irq_wait wait = { | ||
156 | .irq = { | ||
157 | .irq = wait_irq, | ||
158 | .irqmask = irqmask, | ||
159 | }, | ||
160 | .count = 1, | ||
161 | }; | ||
162 | mdp4_irq_register(mdp4_kms, &wait.irq); | ||
163 | wait_event(wait_event, (wait.count <= 0)); | ||
164 | mdp4_irq_unregister(mdp4_kms, &wait.irq); | ||
165 | } | ||
166 | |||
167 | void mdp4_irq_register(struct mdp4_kms *mdp4_kms, struct mdp4_irq *irq) | ||
168 | { | ||
169 | unsigned long flags; | ||
170 | bool needs_update = false; | ||
171 | |||
172 | spin_lock_irqsave(&list_lock, flags); | ||
173 | |||
174 | if (!irq->registered) { | ||
175 | irq->registered = true; | ||
176 | list_add(&irq->node, &mdp4_kms->irq_list); | ||
177 | needs_update = !mdp4_kms->in_irq; | ||
178 | } | ||
179 | |||
180 | spin_unlock_irqrestore(&list_lock, flags); | ||
181 | |||
182 | if (needs_update) | ||
183 | update_irq_unlocked(mdp4_kms); | ||
184 | } | ||
185 | |||
186 | void mdp4_irq_unregister(struct mdp4_kms *mdp4_kms, struct mdp4_irq *irq) | ||
187 | { | ||
188 | unsigned long flags; | ||
189 | bool needs_update = false; | ||
190 | |||
191 | spin_lock_irqsave(&list_lock, flags); | ||
192 | |||
193 | if (irq->registered) { | ||
194 | irq->registered = false; | ||
195 | list_del(&irq->node); | ||
196 | needs_update = !mdp4_kms->in_irq; | ||
197 | } | ||
198 | |||
199 | spin_unlock_irqrestore(&list_lock, flags); | ||
200 | |||
201 | if (needs_update) | ||
202 | update_irq_unlocked(mdp4_kms); | ||
203 | } | ||
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/mdp4/mdp4_kms.c new file mode 100644 index 000000000000..960cd894da78 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp4/mdp4_kms.c | |||
@@ -0,0 +1,368 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | |||
19 | #include "msm_drv.h" | ||
20 | #include "mdp4_kms.h" | ||
21 | |||
22 | #include <mach/iommu.h> | ||
23 | |||
24 | static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev); | ||
25 | |||
26 | static int mdp4_hw_init(struct msm_kms *kms) | ||
27 | { | ||
28 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
29 | struct drm_device *dev = mdp4_kms->dev; | ||
30 | uint32_t version, major, minor, dmap_cfg, vg_cfg; | ||
31 | unsigned long clk; | ||
32 | int ret = 0; | ||
33 | |||
34 | pm_runtime_get_sync(dev->dev); | ||
35 | |||
36 | version = mdp4_read(mdp4_kms, REG_MDP4_VERSION); | ||
37 | |||
38 | major = FIELD(version, MDP4_VERSION_MAJOR); | ||
39 | minor = FIELD(version, MDP4_VERSION_MINOR); | ||
40 | |||
41 | DBG("found MDP version v%d.%d", major, minor); | ||
42 | |||
43 | if (major != 4) { | ||
44 | dev_err(dev->dev, "unexpected MDP version: v%d.%d\n", | ||
45 | major, minor); | ||
46 | ret = -ENXIO; | ||
47 | goto out; | ||
48 | } | ||
49 | |||
50 | mdp4_kms->rev = minor; | ||
51 | |||
52 | if (mdp4_kms->dsi_pll_vdda) { | ||
53 | if ((mdp4_kms->rev == 2) || (mdp4_kms->rev == 4)) { | ||
54 | ret = regulator_set_voltage(mdp4_kms->dsi_pll_vdda, | ||
55 | 1200000, 1200000); | ||
56 | if (ret) { | ||
57 | dev_err(dev->dev, | ||
58 | "failed to set dsi_pll_vdda voltage: %d\n", ret); | ||
59 | goto out; | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | |||
64 | if (mdp4_kms->dsi_pll_vddio) { | ||
65 | if (mdp4_kms->rev == 2) { | ||
66 | ret = regulator_set_voltage(mdp4_kms->dsi_pll_vddio, | ||
67 | 1800000, 1800000); | ||
68 | if (ret) { | ||
69 | dev_err(dev->dev, | ||
70 | "failed to set dsi_pll_vddio voltage: %d\n", ret); | ||
71 | goto out; | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | if (mdp4_kms->rev > 1) { | ||
77 | mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER0, 0x0707ffff); | ||
78 | mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER1, 0x03073f3f); | ||
79 | } | ||
80 | |||
81 | mdp4_write(mdp4_kms, REG_MDP4_PORTMAP_MODE, 0x3); | ||
82 | |||
83 | /* max read pending cmd config, 3 pending requests: */ | ||
84 | mdp4_write(mdp4_kms, REG_MDP4_READ_CNFG, 0x02222); | ||
85 | |||
86 | clk = clk_get_rate(mdp4_kms->clk); | ||
87 | |||
88 | if ((mdp4_kms->rev >= 1) || (clk >= 90000000)) { | ||
89 | dmap_cfg = 0x47; /* 16 bytes-burst x 8 req */ | ||
90 | vg_cfg = 0x47; /* 16 bytes-burs x 8 req */ | ||
91 | } else { | ||
92 | dmap_cfg = 0x27; /* 8 bytes-burst x 8 req */ | ||
93 | vg_cfg = 0x43; /* 16 bytes-burst x 4 req */ | ||
94 | } | ||
95 | |||
96 | DBG("fetch config: dmap=%02x, vg=%02x", dmap_cfg, vg_cfg); | ||
97 | |||
98 | mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_P), dmap_cfg); | ||
99 | mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_E), dmap_cfg); | ||
100 | |||
101 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG1), vg_cfg); | ||
102 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG2), vg_cfg); | ||
103 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB1), vg_cfg); | ||
104 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB2), vg_cfg); | ||
105 | |||
106 | if (mdp4_kms->rev >= 2) | ||
107 | mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG_UPDATE_METHOD, 1); | ||
108 | |||
109 | /* disable CSC matrix / YUV by default: */ | ||
110 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG1), 0); | ||
111 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG2), 0); | ||
112 | mdp4_write(mdp4_kms, REG_MDP4_DMA_P_OP_MODE, 0); | ||
113 | mdp4_write(mdp4_kms, REG_MDP4_DMA_S_OP_MODE, 0); | ||
114 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(1), 0); | ||
115 | mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(2), 0); | ||
116 | |||
117 | if (mdp4_kms->rev > 1) | ||
118 | mdp4_write(mdp4_kms, REG_MDP4_RESET_STATUS, 1); | ||
119 | |||
120 | out: | ||
121 | pm_runtime_put_sync(dev->dev); | ||
122 | |||
123 | return ret; | ||
124 | } | ||
125 | |||
126 | static long mdp4_round_pixclk(struct msm_kms *kms, unsigned long rate, | ||
127 | struct drm_encoder *encoder) | ||
128 | { | ||
129 | /* if we had >1 encoder, we'd need something more clever: */ | ||
130 | return mdp4_dtv_round_pixclk(encoder, rate); | ||
131 | } | ||
132 | |||
133 | static void mdp4_preclose(struct msm_kms *kms, struct drm_file *file) | ||
134 | { | ||
135 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
136 | struct msm_drm_private *priv = mdp4_kms->dev->dev_private; | ||
137 | unsigned i; | ||
138 | |||
139 | for (i = 0; i < priv->num_crtcs; i++) | ||
140 | mdp4_crtc_cancel_pending_flip(priv->crtcs[i]); | ||
141 | } | ||
142 | |||
143 | static void mdp4_destroy(struct msm_kms *kms) | ||
144 | { | ||
145 | struct mdp4_kms *mdp4_kms = to_mdp4_kms(kms); | ||
146 | kfree(mdp4_kms); | ||
147 | } | ||
148 | |||
149 | static const struct msm_kms_funcs kms_funcs = { | ||
150 | .hw_init = mdp4_hw_init, | ||
151 | .irq_preinstall = mdp4_irq_preinstall, | ||
152 | .irq_postinstall = mdp4_irq_postinstall, | ||
153 | .irq_uninstall = mdp4_irq_uninstall, | ||
154 | .irq = mdp4_irq, | ||
155 | .enable_vblank = mdp4_enable_vblank, | ||
156 | .disable_vblank = mdp4_disable_vblank, | ||
157 | .get_format = mdp4_get_format, | ||
158 | .round_pixclk = mdp4_round_pixclk, | ||
159 | .preclose = mdp4_preclose, | ||
160 | .destroy = mdp4_destroy, | ||
161 | }; | ||
162 | |||
163 | int mdp4_disable(struct mdp4_kms *mdp4_kms) | ||
164 | { | ||
165 | DBG(""); | ||
166 | |||
167 | clk_disable_unprepare(mdp4_kms->clk); | ||
168 | if (mdp4_kms->pclk) | ||
169 | clk_disable_unprepare(mdp4_kms->pclk); | ||
170 | clk_disable_unprepare(mdp4_kms->lut_clk); | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | int mdp4_enable(struct mdp4_kms *mdp4_kms) | ||
176 | { | ||
177 | DBG(""); | ||
178 | |||
179 | clk_prepare_enable(mdp4_kms->clk); | ||
180 | if (mdp4_kms->pclk) | ||
181 | clk_prepare_enable(mdp4_kms->pclk); | ||
182 | clk_prepare_enable(mdp4_kms->lut_clk); | ||
183 | |||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | static int modeset_init(struct mdp4_kms *mdp4_kms) | ||
188 | { | ||
189 | struct drm_device *dev = mdp4_kms->dev; | ||
190 | struct msm_drm_private *priv = dev->dev_private; | ||
191 | struct drm_plane *plane; | ||
192 | struct drm_crtc *crtc; | ||
193 | struct drm_encoder *encoder; | ||
194 | struct drm_connector *connector; | ||
195 | int ret; | ||
196 | |||
197 | /* | ||
198 | * NOTE: this is a bit simplistic until we add support | ||
199 | * for more than just RGB1->DMA_E->DTV->HDMI | ||
200 | */ | ||
201 | |||
202 | /* the CRTCs get constructed with a private plane: */ | ||
203 | plane = mdp4_plane_init(dev, RGB1, true); | ||
204 | if (IS_ERR(plane)) { | ||
205 | dev_err(dev->dev, "failed to construct plane for RGB1\n"); | ||
206 | ret = PTR_ERR(plane); | ||
207 | goto fail; | ||
208 | } | ||
209 | |||
210 | crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, 1, DMA_E); | ||
211 | if (IS_ERR(crtc)) { | ||
212 | dev_err(dev->dev, "failed to construct crtc for DMA_E\n"); | ||
213 | ret = PTR_ERR(crtc); | ||
214 | goto fail; | ||
215 | } | ||
216 | priv->crtcs[priv->num_crtcs++] = crtc; | ||
217 | |||
218 | encoder = mdp4_dtv_encoder_init(dev); | ||
219 | if (IS_ERR(encoder)) { | ||
220 | dev_err(dev->dev, "failed to construct DTV encoder\n"); | ||
221 | ret = PTR_ERR(encoder); | ||
222 | goto fail; | ||
223 | } | ||
224 | encoder->possible_crtcs = 0x1; /* DTV can be hooked to DMA_E */ | ||
225 | priv->encoders[priv->num_encoders++] = encoder; | ||
226 | |||
227 | connector = hdmi_connector_init(dev, encoder); | ||
228 | if (IS_ERR(connector)) { | ||
229 | dev_err(dev->dev, "failed to construct HDMI connector\n"); | ||
230 | ret = PTR_ERR(connector); | ||
231 | goto fail; | ||
232 | } | ||
233 | priv->connectors[priv->num_connectors++] = connector; | ||
234 | |||
235 | return 0; | ||
236 | |||
237 | fail: | ||
238 | return ret; | ||
239 | } | ||
240 | |||
241 | static const char *iommu_ports[] = { | ||
242 | "mdp_port0_cb0", "mdp_port1_cb0", | ||
243 | }; | ||
244 | |||
245 | struct msm_kms *mdp4_kms_init(struct drm_device *dev) | ||
246 | { | ||
247 | struct platform_device *pdev = dev->platformdev; | ||
248 | struct mdp4_platform_config *config = mdp4_get_config(pdev); | ||
249 | struct mdp4_kms *mdp4_kms; | ||
250 | struct msm_kms *kms = NULL; | ||
251 | int ret; | ||
252 | |||
253 | mdp4_kms = kzalloc(sizeof(*mdp4_kms), GFP_KERNEL); | ||
254 | if (!mdp4_kms) { | ||
255 | dev_err(dev->dev, "failed to allocate kms\n"); | ||
256 | ret = -ENOMEM; | ||
257 | goto fail; | ||
258 | } | ||
259 | |||
260 | kms = &mdp4_kms->base; | ||
261 | kms->funcs = &kms_funcs; | ||
262 | |||
263 | mdp4_kms->dev = dev; | ||
264 | |||
265 | mdp4_kms->mmio = msm_ioremap(pdev, NULL, "MDP4"); | ||
266 | if (IS_ERR(mdp4_kms->mmio)) { | ||
267 | ret = PTR_ERR(mdp4_kms->mmio); | ||
268 | goto fail; | ||
269 | } | ||
270 | |||
271 | mdp4_kms->dsi_pll_vdda = devm_regulator_get(&pdev->dev, "dsi_pll_vdda"); | ||
272 | if (IS_ERR(mdp4_kms->dsi_pll_vdda)) | ||
273 | mdp4_kms->dsi_pll_vdda = NULL; | ||
274 | |||
275 | mdp4_kms->dsi_pll_vddio = devm_regulator_get(&pdev->dev, "dsi_pll_vddio"); | ||
276 | if (IS_ERR(mdp4_kms->dsi_pll_vddio)) | ||
277 | mdp4_kms->dsi_pll_vddio = NULL; | ||
278 | |||
279 | mdp4_kms->vdd = devm_regulator_get(&pdev->dev, "vdd"); | ||
280 | if (IS_ERR(mdp4_kms->vdd)) | ||
281 | mdp4_kms->vdd = NULL; | ||
282 | |||
283 | if (mdp4_kms->vdd) { | ||
284 | ret = regulator_enable(mdp4_kms->vdd); | ||
285 | if (ret) { | ||
286 | dev_err(dev->dev, "failed to enable regulator vdd: %d\n", ret); | ||
287 | goto fail; | ||
288 | } | ||
289 | } | ||
290 | |||
291 | mdp4_kms->clk = devm_clk_get(&pdev->dev, "core_clk"); | ||
292 | if (IS_ERR(mdp4_kms->clk)) { | ||
293 | dev_err(dev->dev, "failed to get core_clk\n"); | ||
294 | ret = PTR_ERR(mdp4_kms->clk); | ||
295 | goto fail; | ||
296 | } | ||
297 | |||
298 | mdp4_kms->pclk = devm_clk_get(&pdev->dev, "iface_clk"); | ||
299 | if (IS_ERR(mdp4_kms->pclk)) | ||
300 | mdp4_kms->pclk = NULL; | ||
301 | |||
302 | // XXX if (rev >= MDP_REV_42) { ??? | ||
303 | mdp4_kms->lut_clk = devm_clk_get(&pdev->dev, "lut_clk"); | ||
304 | if (IS_ERR(mdp4_kms->lut_clk)) { | ||
305 | dev_err(dev->dev, "failed to get lut_clk\n"); | ||
306 | ret = PTR_ERR(mdp4_kms->lut_clk); | ||
307 | goto fail; | ||
308 | } | ||
309 | |||
310 | clk_set_rate(mdp4_kms->clk, config->max_clk); | ||
311 | clk_set_rate(mdp4_kms->lut_clk, config->max_clk); | ||
312 | |||
313 | if (!config->iommu) { | ||
314 | dev_err(dev->dev, "no iommu\n"); | ||
315 | ret = -ENXIO; | ||
316 | goto fail; | ||
317 | } | ||
318 | |||
319 | /* make sure things are off before attaching iommu (bootloader could | ||
320 | * have left things on, in which case we'll start getting faults if | ||
321 | * we don't disable): | ||
322 | */ | ||
323 | mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0); | ||
324 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0); | ||
325 | mdp4_write(mdp4_kms, REG_MDP4_DSI_ENABLE, 0); | ||
326 | mdelay(16); | ||
327 | |||
328 | ret = msm_iommu_attach(dev, config->iommu, | ||
329 | iommu_ports, ARRAY_SIZE(iommu_ports)); | ||
330 | if (ret) | ||
331 | goto fail; | ||
332 | |||
333 | mdp4_kms->id = msm_register_iommu(dev, config->iommu); | ||
334 | if (mdp4_kms->id < 0) { | ||
335 | ret = mdp4_kms->id; | ||
336 | dev_err(dev->dev, "failed to register mdp4 iommu: %d\n", ret); | ||
337 | goto fail; | ||
338 | } | ||
339 | |||
340 | ret = modeset_init(mdp4_kms); | ||
341 | if (ret) { | ||
342 | dev_err(dev->dev, "modeset_init failed: %d\n", ret); | ||
343 | goto fail; | ||
344 | } | ||
345 | |||
346 | return kms; | ||
347 | |||
348 | fail: | ||
349 | if (kms) | ||
350 | mdp4_destroy(kms); | ||
351 | return ERR_PTR(ret); | ||
352 | } | ||
353 | |||
354 | static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev) | ||
355 | { | ||
356 | static struct mdp4_platform_config config = {}; | ||
357 | #ifdef CONFIG_OF | ||
358 | /* TODO */ | ||
359 | #else | ||
360 | if (cpu_is_apq8064()) | ||
361 | config.max_clk = 266667000; | ||
362 | else | ||
363 | config.max_clk = 200000000; | ||
364 | |||
365 | config.iommu = msm_get_iommu_domain(DISPLAY_READ_DOMAIN); | ||
366 | #endif | ||
367 | return &config; | ||
368 | } | ||
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_kms.h b/drivers/gpu/drm/msm/mdp4/mdp4_kms.h new file mode 100644 index 000000000000..1e83554955f3 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp4/mdp4_kms.h | |||
@@ -0,0 +1,194 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #ifndef __MDP4_KMS_H__ | ||
19 | #define __MDP4_KMS_H__ | ||
20 | |||
21 | #include <linux/clk.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/regulator/consumer.h> | ||
24 | |||
25 | #include "msm_drv.h" | ||
26 | #include "mdp4.xml.h" | ||
27 | |||
28 | |||
29 | /* For transiently registering for different MDP4 irqs that various parts | ||
30 | * of the KMS code need during setup/configuration. We these are not | ||
31 | * necessarily the same as what drm_vblank_get/put() are requesting, and | ||
32 | * the hysteresis in drm_vblank_put() is not necessarily desirable for | ||
33 | * internal housekeeping related irq usage. | ||
34 | */ | ||
35 | struct mdp4_irq { | ||
36 | struct list_head node; | ||
37 | uint32_t irqmask; | ||
38 | bool registered; | ||
39 | void (*irq)(struct mdp4_irq *irq, uint32_t irqstatus); | ||
40 | }; | ||
41 | |||
42 | struct mdp4_kms { | ||
43 | struct msm_kms base; | ||
44 | |||
45 | struct drm_device *dev; | ||
46 | |||
47 | int rev; | ||
48 | |||
49 | /* mapper-id used to request GEM buffer mapped for scanout: */ | ||
50 | int id; | ||
51 | |||
52 | void __iomem *mmio; | ||
53 | |||
54 | struct regulator *dsi_pll_vdda; | ||
55 | struct regulator *dsi_pll_vddio; | ||
56 | struct regulator *vdd; | ||
57 | |||
58 | struct clk *clk; | ||
59 | struct clk *pclk; | ||
60 | struct clk *lut_clk; | ||
61 | |||
62 | /* irq handling: */ | ||
63 | bool in_irq; | ||
64 | struct list_head irq_list; /* list of mdp4_irq */ | ||
65 | uint32_t vblank_mask; /* irq bits set for userspace vblank */ | ||
66 | struct mdp4_irq error_handler; | ||
67 | }; | ||
68 | #define to_mdp4_kms(x) container_of(x, struct mdp4_kms, base) | ||
69 | |||
70 | /* platform config data (ie. from DT, or pdata) */ | ||
71 | struct mdp4_platform_config { | ||
72 | struct iommu_domain *iommu; | ||
73 | uint32_t max_clk; | ||
74 | }; | ||
75 | |||
76 | struct mdp4_format { | ||
77 | struct msm_format base; | ||
78 | enum mpd4_bpc bpc_r, bpc_g, bpc_b; | ||
79 | enum mpd4_bpc_alpha bpc_a; | ||
80 | uint8_t unpack[4]; | ||
81 | bool alpha_enable, unpack_tight; | ||
82 | uint8_t cpp, unpack_count; | ||
83 | }; | ||
84 | #define to_mdp4_format(x) container_of(x, struct mdp4_format, base) | ||
85 | |||
86 | static inline void mdp4_write(struct mdp4_kms *mdp4_kms, u32 reg, u32 data) | ||
87 | { | ||
88 | msm_writel(data, mdp4_kms->mmio + reg); | ||
89 | } | ||
90 | |||
91 | static inline u32 mdp4_read(struct mdp4_kms *mdp4_kms, u32 reg) | ||
92 | { | ||
93 | return msm_readl(mdp4_kms->mmio + reg); | ||
94 | } | ||
95 | |||
96 | static inline uint32_t pipe2flush(enum mpd4_pipe pipe) | ||
97 | { | ||
98 | switch (pipe) { | ||
99 | case VG1: return MDP4_OVERLAY_FLUSH_VG1; | ||
100 | case VG2: return MDP4_OVERLAY_FLUSH_VG2; | ||
101 | case RGB1: return MDP4_OVERLAY_FLUSH_RGB1; | ||
102 | case RGB2: return MDP4_OVERLAY_FLUSH_RGB1; | ||
103 | default: return 0; | ||
104 | } | ||
105 | } | ||
106 | |||
107 | static inline uint32_t ovlp2flush(int ovlp) | ||
108 | { | ||
109 | switch (ovlp) { | ||
110 | case 0: return MDP4_OVERLAY_FLUSH_OVLP0; | ||
111 | case 1: return MDP4_OVERLAY_FLUSH_OVLP1; | ||
112 | default: return 0; | ||
113 | } | ||
114 | } | ||
115 | |||
116 | static inline uint32_t dma2irq(enum mdp4_dma dma) | ||
117 | { | ||
118 | switch (dma) { | ||
119 | case DMA_P: return MDP4_IRQ_DMA_P_DONE; | ||
120 | case DMA_S: return MDP4_IRQ_DMA_S_DONE; | ||
121 | case DMA_E: return MDP4_IRQ_DMA_E_DONE; | ||
122 | default: return 0; | ||
123 | } | ||
124 | } | ||
125 | |||
126 | static inline uint32_t dma2err(enum mdp4_dma dma) | ||
127 | { | ||
128 | switch (dma) { | ||
129 | case DMA_P: return MDP4_IRQ_PRIMARY_INTF_UDERRUN; | ||
130 | case DMA_S: return 0; // ??? | ||
131 | case DMA_E: return MDP4_IRQ_EXTERNAL_INTF_UDERRUN; | ||
132 | default: return 0; | ||
133 | } | ||
134 | } | ||
135 | |||
136 | int mdp4_disable(struct mdp4_kms *mdp4_kms); | ||
137 | int mdp4_enable(struct mdp4_kms *mdp4_kms); | ||
138 | |||
139 | void mdp4_irq_preinstall(struct msm_kms *kms); | ||
140 | int mdp4_irq_postinstall(struct msm_kms *kms); | ||
141 | void mdp4_irq_uninstall(struct msm_kms *kms); | ||
142 | irqreturn_t mdp4_irq(struct msm_kms *kms); | ||
143 | void mdp4_irq_wait(struct mdp4_kms *mdp4_kms, uint32_t irqmask); | ||
144 | void mdp4_irq_register(struct mdp4_kms *mdp4_kms, struct mdp4_irq *irq); | ||
145 | void mdp4_irq_unregister(struct mdp4_kms *mdp4_kms, struct mdp4_irq *irq); | ||
146 | int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc); | ||
147 | void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc); | ||
148 | |||
149 | const struct msm_format *mdp4_get_format(struct msm_kms *kms, uint32_t format); | ||
150 | |||
151 | void mdp4_plane_install_properties(struct drm_plane *plane, | ||
152 | struct drm_mode_object *obj); | ||
153 | void mdp4_plane_set_scanout(struct drm_plane *plane, | ||
154 | struct drm_framebuffer *fb); | ||
155 | int mdp4_plane_mode_set(struct drm_plane *plane, | ||
156 | struct drm_crtc *crtc, struct drm_framebuffer *fb, | ||
157 | int crtc_x, int crtc_y, | ||
158 | unsigned int crtc_w, unsigned int crtc_h, | ||
159 | uint32_t src_x, uint32_t src_y, | ||
160 | uint32_t src_w, uint32_t src_h); | ||
161 | enum mpd4_pipe mdp4_plane_pipe(struct drm_plane *plane); | ||
162 | struct drm_plane *mdp4_plane_init(struct drm_device *dev, | ||
163 | enum mpd4_pipe pipe_id, bool private_plane); | ||
164 | |||
165 | uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc); | ||
166 | void mdp4_crtc_cancel_pending_flip(struct drm_crtc *crtc); | ||
167 | void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config); | ||
168 | void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf); | ||
169 | struct drm_crtc *mdp4_crtc_init(struct drm_device *dev, | ||
170 | struct drm_plane *plane, int id, int ovlp_id, | ||
171 | enum mdp4_dma dma_id); | ||
172 | |||
173 | long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate); | ||
174 | struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev); | ||
175 | |||
176 | #ifdef CONFIG_MSM_BUS_SCALING | ||
177 | static inline int match_dev_name(struct device *dev, void *data) | ||
178 | { | ||
179 | return !strcmp(dev_name(dev), data); | ||
180 | } | ||
181 | /* bus scaling data is associated with extra pointless platform devices, | ||
182 | * "dtv", etc.. this is a bit of a hack, but we need a way for encoders | ||
183 | * to find their pdata to make the bus-scaling stuff work. | ||
184 | */ | ||
185 | static inline void *mdp4_find_pdata(const char *devname) | ||
186 | { | ||
187 | struct device *dev; | ||
188 | dev = bus_find_device(&platform_bus_type, NULL, | ||
189 | (void *)devname, match_dev_name); | ||
190 | return dev ? dev->platform_data : NULL; | ||
191 | } | ||
192 | #endif | ||
193 | |||
194 | #endif /* __MDP4_KMS_H__ */ | ||
diff --git a/drivers/gpu/drm/msm/mdp4/mdp4_plane.c b/drivers/gpu/drm/msm/mdp4/mdp4_plane.c new file mode 100644 index 000000000000..3468229d58b3 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp4/mdp4_plane.c | |||
@@ -0,0 +1,243 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "mdp4_kms.h" | ||
19 | |||
20 | |||
21 | struct mdp4_plane { | ||
22 | struct drm_plane base; | ||
23 | const char *name; | ||
24 | |||
25 | enum mpd4_pipe pipe; | ||
26 | |||
27 | uint32_t nformats; | ||
28 | uint32_t formats[32]; | ||
29 | |||
30 | bool enabled; | ||
31 | }; | ||
32 | #define to_mdp4_plane(x) container_of(x, struct mdp4_plane, base) | ||
33 | |||
34 | static struct mdp4_kms *get_kms(struct drm_plane *plane) | ||
35 | { | ||
36 | struct msm_drm_private *priv = plane->dev->dev_private; | ||
37 | return to_mdp4_kms(priv->kms); | ||
38 | } | ||
39 | |||
40 | static int mdp4_plane_update(struct drm_plane *plane, | ||
41 | struct drm_crtc *crtc, struct drm_framebuffer *fb, | ||
42 | int crtc_x, int crtc_y, | ||
43 | unsigned int crtc_w, unsigned int crtc_h, | ||
44 | uint32_t src_x, uint32_t src_y, | ||
45 | uint32_t src_w, uint32_t src_h) | ||
46 | { | ||
47 | struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); | ||
48 | |||
49 | mdp4_plane->enabled = true; | ||
50 | |||
51 | if (plane->fb) | ||
52 | drm_framebuffer_unreference(plane->fb); | ||
53 | |||
54 | drm_framebuffer_reference(fb); | ||
55 | |||
56 | return mdp4_plane_mode_set(plane, crtc, fb, | ||
57 | crtc_x, crtc_y, crtc_w, crtc_h, | ||
58 | src_x, src_y, src_w, src_h); | ||
59 | } | ||
60 | |||
61 | static int mdp4_plane_disable(struct drm_plane *plane) | ||
62 | { | ||
63 | struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); | ||
64 | DBG("%s: TODO", mdp4_plane->name); // XXX | ||
65 | return 0; | ||
66 | } | ||
67 | |||
68 | static void mdp4_plane_destroy(struct drm_plane *plane) | ||
69 | { | ||
70 | struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); | ||
71 | |||
72 | mdp4_plane_disable(plane); | ||
73 | drm_plane_cleanup(plane); | ||
74 | |||
75 | kfree(mdp4_plane); | ||
76 | } | ||
77 | |||
78 | /* helper to install properties which are common to planes and crtcs */ | ||
79 | void mdp4_plane_install_properties(struct drm_plane *plane, | ||
80 | struct drm_mode_object *obj) | ||
81 | { | ||
82 | // XXX | ||
83 | } | ||
84 | |||
85 | int mdp4_plane_set_property(struct drm_plane *plane, | ||
86 | struct drm_property *property, uint64_t val) | ||
87 | { | ||
88 | // XXX | ||
89 | return -EINVAL; | ||
90 | } | ||
91 | |||
92 | static const struct drm_plane_funcs mdp4_plane_funcs = { | ||
93 | .update_plane = mdp4_plane_update, | ||
94 | .disable_plane = mdp4_plane_disable, | ||
95 | .destroy = mdp4_plane_destroy, | ||
96 | .set_property = mdp4_plane_set_property, | ||
97 | }; | ||
98 | |||
99 | void mdp4_plane_set_scanout(struct drm_plane *plane, | ||
100 | struct drm_framebuffer *fb) | ||
101 | { | ||
102 | struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); | ||
103 | struct mdp4_kms *mdp4_kms = get_kms(plane); | ||
104 | enum mpd4_pipe pipe = mdp4_plane->pipe; | ||
105 | uint32_t iova; | ||
106 | |||
107 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_A(pipe), | ||
108 | MDP4_PIPE_SRC_STRIDE_A_P0(fb->pitches[0]) | | ||
109 | MDP4_PIPE_SRC_STRIDE_A_P1(fb->pitches[1])); | ||
110 | |||
111 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_B(pipe), | ||
112 | MDP4_PIPE_SRC_STRIDE_B_P2(fb->pitches[2]) | | ||
113 | MDP4_PIPE_SRC_STRIDE_B_P3(fb->pitches[3])); | ||
114 | |||
115 | msm_gem_get_iova(msm_framebuffer_bo(fb, 0), mdp4_kms->id, &iova); | ||
116 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRCP0_BASE(pipe), iova); | ||
117 | |||
118 | plane->fb = fb; | ||
119 | } | ||
120 | |||
121 | #define MDP4_VG_PHASE_STEP_DEFAULT 0x20000000 | ||
122 | |||
123 | int mdp4_plane_mode_set(struct drm_plane *plane, | ||
124 | struct drm_crtc *crtc, struct drm_framebuffer *fb, | ||
125 | int crtc_x, int crtc_y, | ||
126 | unsigned int crtc_w, unsigned int crtc_h, | ||
127 | uint32_t src_x, uint32_t src_y, | ||
128 | uint32_t src_w, uint32_t src_h) | ||
129 | { | ||
130 | struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); | ||
131 | struct mdp4_kms *mdp4_kms = get_kms(plane); | ||
132 | enum mpd4_pipe pipe = mdp4_plane->pipe; | ||
133 | const struct mdp4_format *format; | ||
134 | uint32_t op_mode = 0; | ||
135 | uint32_t phasex_step = MDP4_VG_PHASE_STEP_DEFAULT; | ||
136 | uint32_t phasey_step = MDP4_VG_PHASE_STEP_DEFAULT; | ||
137 | |||
138 | /* src values are in Q16 fixed point, convert to integer: */ | ||
139 | src_x = src_x >> 16; | ||
140 | src_y = src_y >> 16; | ||
141 | src_w = src_w >> 16; | ||
142 | src_h = src_h >> 16; | ||
143 | |||
144 | if (src_w != crtc_w) { | ||
145 | op_mode |= MDP4_PIPE_OP_MODE_SCALEX_EN; | ||
146 | /* TODO calc phasex_step */ | ||
147 | } | ||
148 | |||
149 | if (src_h != crtc_h) { | ||
150 | op_mode |= MDP4_PIPE_OP_MODE_SCALEY_EN; | ||
151 | /* TODO calc phasey_step */ | ||
152 | } | ||
153 | |||
154 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_SIZE(pipe), | ||
155 | MDP4_PIPE_SRC_SIZE_WIDTH(src_w) | | ||
156 | MDP4_PIPE_SRC_SIZE_HEIGHT(src_h)); | ||
157 | |||
158 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_XY(pipe), | ||
159 | MDP4_PIPE_SRC_XY_X(src_x) | | ||
160 | MDP4_PIPE_SRC_XY_Y(src_y)); | ||
161 | |||
162 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_SIZE(pipe), | ||
163 | MDP4_PIPE_DST_SIZE_WIDTH(crtc_w) | | ||
164 | MDP4_PIPE_DST_SIZE_HEIGHT(crtc_h)); | ||
165 | |||
166 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_XY(pipe), | ||
167 | MDP4_PIPE_SRC_XY_X(crtc_x) | | ||
168 | MDP4_PIPE_SRC_XY_Y(crtc_y)); | ||
169 | |||
170 | mdp4_plane_set_scanout(plane, fb); | ||
171 | |||
172 | format = to_mdp4_format(msm_framebuffer_format(fb)); | ||
173 | |||
174 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_FORMAT(pipe), | ||
175 | MDP4_PIPE_SRC_FORMAT_A_BPC(format->bpc_a) | | ||
176 | MDP4_PIPE_SRC_FORMAT_R_BPC(format->bpc_r) | | ||
177 | MDP4_PIPE_SRC_FORMAT_G_BPC(format->bpc_g) | | ||
178 | MDP4_PIPE_SRC_FORMAT_B_BPC(format->bpc_b) | | ||
179 | COND(format->alpha_enable, MDP4_PIPE_SRC_FORMAT_ALPHA_ENABLE) | | ||
180 | MDP4_PIPE_SRC_FORMAT_CPP(format->cpp - 1) | | ||
181 | MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT(format->unpack_count - 1) | | ||
182 | COND(format->unpack_tight, MDP4_PIPE_SRC_FORMAT_UNPACK_TIGHT)); | ||
183 | |||
184 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_UNPACK(pipe), | ||
185 | MDP4_PIPE_SRC_UNPACK_ELEM0(format->unpack[0]) | | ||
186 | MDP4_PIPE_SRC_UNPACK_ELEM1(format->unpack[1]) | | ||
187 | MDP4_PIPE_SRC_UNPACK_ELEM2(format->unpack[2]) | | ||
188 | MDP4_PIPE_SRC_UNPACK_ELEM3(format->unpack[3])); | ||
189 | |||
190 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(pipe), op_mode); | ||
191 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEX_STEP(pipe), phasex_step); | ||
192 | mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEY_STEP(pipe), phasey_step); | ||
193 | |||
194 | plane->crtc = crtc; | ||
195 | |||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | static const char *pipe_names[] = { | ||
200 | "VG1", "VG2", | ||
201 | "RGB1", "RGB2", "RGB3", | ||
202 | "VG3", "VG4", | ||
203 | }; | ||
204 | |||
205 | enum mpd4_pipe mdp4_plane_pipe(struct drm_plane *plane) | ||
206 | { | ||
207 | struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); | ||
208 | return mdp4_plane->pipe; | ||
209 | } | ||
210 | |||
211 | /* initialize plane */ | ||
212 | struct drm_plane *mdp4_plane_init(struct drm_device *dev, | ||
213 | enum mpd4_pipe pipe_id, bool private_plane) | ||
214 | { | ||
215 | struct msm_drm_private *priv = dev->dev_private; | ||
216 | struct drm_plane *plane = NULL; | ||
217 | struct mdp4_plane *mdp4_plane; | ||
218 | int ret; | ||
219 | |||
220 | mdp4_plane = kzalloc(sizeof(*mdp4_plane), GFP_KERNEL); | ||
221 | if (!mdp4_plane) { | ||
222 | ret = -ENOMEM; | ||
223 | goto fail; | ||
224 | } | ||
225 | |||
226 | plane = &mdp4_plane->base; | ||
227 | |||
228 | mdp4_plane->pipe = pipe_id; | ||
229 | mdp4_plane->name = pipe_names[pipe_id]; | ||
230 | |||
231 | drm_plane_init(dev, plane, (1 << priv->num_crtcs) - 1, &mdp4_plane_funcs, | ||
232 | mdp4_plane->formats, mdp4_plane->nformats, private_plane); | ||
233 | |||
234 | mdp4_plane_install_properties(plane, &plane->base); | ||
235 | |||
236 | return plane; | ||
237 | |||
238 | fail: | ||
239 | if (plane) | ||
240 | mdp4_plane_destroy(plane); | ||
241 | |||
242 | return ERR_PTR(ret); | ||
243 | } | ||
diff --git a/drivers/gpu/drm/msm/msm_connector.c b/drivers/gpu/drm/msm/msm_connector.c new file mode 100644 index 000000000000..aeea8879e36f --- /dev/null +++ b/drivers/gpu/drm/msm/msm_connector.c | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "msm_drv.h" | ||
19 | #include "msm_connector.h" | ||
20 | |||
21 | void msm_connector_init(struct msm_connector *connector, | ||
22 | const struct msm_connector_funcs *funcs, | ||
23 | struct drm_encoder *encoder) | ||
24 | { | ||
25 | connector->funcs = funcs; | ||
26 | connector->encoder = encoder; | ||
27 | } | ||
28 | |||
29 | struct drm_encoder *msm_connector_attached_encoder( | ||
30 | struct drm_connector *connector) | ||
31 | { | ||
32 | struct msm_connector *msm_connector = to_msm_connector(connector); | ||
33 | return msm_connector->encoder; | ||
34 | } | ||
diff --git a/drivers/gpu/drm/msm/msm_connector.h b/drivers/gpu/drm/msm/msm_connector.h new file mode 100644 index 000000000000..0b41866adc08 --- /dev/null +++ b/drivers/gpu/drm/msm/msm_connector.h | |||
@@ -0,0 +1,68 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #ifndef __MSM_CONNECTOR_H__ | ||
19 | #define __MSM_CONNECTOR_H__ | ||
20 | |||
21 | #include "msm_drv.h" | ||
22 | |||
23 | /* | ||
24 | * Base class for MSM connectors. Typically a connector is a bit more | ||
25 | * passive. But with the split between (for example) DTV within MDP4, | ||
26 | * and HDMI encoder, we really need two parts to an encoder. Instead | ||
27 | * what we do is have the part external to the display controller block | ||
28 | * in the connector, which is called from the encoder to delegate the | ||
29 | * appropriate parts of modeset. | ||
30 | */ | ||
31 | |||
32 | struct msm_connector; | ||
33 | |||
34 | struct msm_connector_funcs { | ||
35 | void (*dpms)(struct msm_connector *connector, int mode); | ||
36 | void (*mode_set)(struct msm_connector *connector, | ||
37 | struct drm_display_mode *mode); | ||
38 | }; | ||
39 | |||
40 | struct msm_connector { | ||
41 | struct drm_connector base; | ||
42 | struct drm_encoder *encoder; | ||
43 | const struct msm_connector_funcs *funcs; | ||
44 | }; | ||
45 | #define to_msm_connector(x) container_of(x, struct msm_connector, base) | ||
46 | |||
47 | void msm_connector_init(struct msm_connector *connector, | ||
48 | const struct msm_connector_funcs *funcs, | ||
49 | struct drm_encoder *encoder); | ||
50 | |||
51 | struct drm_encoder *msm_connector_attached_encoder( | ||
52 | struct drm_connector *connector); | ||
53 | |||
54 | static inline struct msm_connector *get_connector(struct drm_encoder *encoder) | ||
55 | { | ||
56 | struct msm_drm_private *priv = encoder->dev->dev_private; | ||
57 | int i; | ||
58 | |||
59 | for (i = 0; i < priv->num_connectors; i++) { | ||
60 | struct drm_connector *connector = priv->connectors[i]; | ||
61 | if (msm_connector_attached_encoder(connector) == encoder) | ||
62 | return to_msm_connector(connector); | ||
63 | } | ||
64 | |||
65 | return NULL; | ||
66 | } | ||
67 | |||
68 | #endif /* __MSM_CONNECTOR_H__ */ | ||
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c new file mode 100644 index 000000000000..b5ae0dbe1eb8 --- /dev/null +++ b/drivers/gpu/drm/msm/msm_drv.c | |||
@@ -0,0 +1,532 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "msm_drv.h" | ||
19 | |||
20 | #include <mach/iommu.h> | ||
21 | |||
22 | static void msm_fb_output_poll_changed(struct drm_device *dev) | ||
23 | { | ||
24 | struct msm_drm_private *priv = dev->dev_private; | ||
25 | if (priv->fbdev) | ||
26 | drm_fb_helper_hotplug_event(priv->fbdev); | ||
27 | } | ||
28 | |||
29 | static const struct drm_mode_config_funcs mode_config_funcs = { | ||
30 | .fb_create = msm_framebuffer_create, | ||
31 | .output_poll_changed = msm_fb_output_poll_changed, | ||
32 | }; | ||
33 | |||
34 | static int msm_fault_handler(struct iommu_domain *iommu, struct device *dev, | ||
35 | unsigned long iova, int flags, void *arg) | ||
36 | { | ||
37 | DBG("*** fault: iova=%08lx, flags=%d", iova, flags); | ||
38 | return 0; | ||
39 | } | ||
40 | |||
41 | int msm_register_iommu(struct drm_device *dev, struct iommu_domain *iommu) | ||
42 | { | ||
43 | struct msm_drm_private *priv = dev->dev_private; | ||
44 | int idx = priv->num_iommus++; | ||
45 | |||
46 | if (WARN_ON(idx >= ARRAY_SIZE(priv->iommus))) | ||
47 | return -EINVAL; | ||
48 | |||
49 | priv->iommus[idx] = iommu; | ||
50 | |||
51 | iommu_set_fault_handler(iommu, msm_fault_handler, dev); | ||
52 | |||
53 | /* need to iommu_attach_device() somewhere?? on resume?? */ | ||
54 | |||
55 | return idx; | ||
56 | } | ||
57 | |||
58 | int msm_iommu_attach(struct drm_device *dev, struct iommu_domain *iommu, | ||
59 | const char **names, int cnt) | ||
60 | { | ||
61 | int i, ret; | ||
62 | |||
63 | for (i = 0; i < cnt; i++) { | ||
64 | struct device *ctx = msm_iommu_get_ctx(names[i]); | ||
65 | if (!ctx) | ||
66 | continue; | ||
67 | ret = iommu_attach_device(iommu, ctx); | ||
68 | if (ret) { | ||
69 | dev_warn(dev->dev, "could not attach iommu to %s", names[i]); | ||
70 | return ret; | ||
71 | } | ||
72 | } | ||
73 | return 0; | ||
74 | } | ||
75 | |||
76 | #ifdef CONFIG_DRM_MSM_REGISTER_LOGGING | ||
77 | static bool reglog = false; | ||
78 | MODULE_PARM_DESC(reglog, "Enable register read/write logging"); | ||
79 | module_param(reglog, bool, 0600); | ||
80 | #else | ||
81 | #define reglog 0 | ||
82 | #endif | ||
83 | |||
84 | void __iomem *msm_ioremap(struct platform_device *pdev, const char *name, | ||
85 | const char *dbgname) | ||
86 | { | ||
87 | struct resource *res; | ||
88 | unsigned long size; | ||
89 | void __iomem *ptr; | ||
90 | |||
91 | if (name) | ||
92 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); | ||
93 | else | ||
94 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
95 | |||
96 | if (!res) { | ||
97 | dev_err(&pdev->dev, "failed to get memory resource: %s\n", name); | ||
98 | return ERR_PTR(-EINVAL); | ||
99 | } | ||
100 | |||
101 | size = resource_size(res); | ||
102 | |||
103 | ptr = devm_ioremap_nocache(&pdev->dev, res->start, size); | ||
104 | if (!ptr) { | ||
105 | dev_err(&pdev->dev, "failed to ioremap: %s\n", name); | ||
106 | return ERR_PTR(-ENOMEM); | ||
107 | } | ||
108 | |||
109 | if (reglog) | ||
110 | printk(KERN_DEBUG "IO:region %s %08x %08lx\n", dbgname, (u32)ptr, size); | ||
111 | |||
112 | return ptr; | ||
113 | } | ||
114 | |||
115 | void msm_writel(u32 data, void __iomem *addr) | ||
116 | { | ||
117 | if (reglog) | ||
118 | printk(KERN_DEBUG "IO:W %08x %08x\n", (u32)addr, data); | ||
119 | writel(data, addr); | ||
120 | } | ||
121 | |||
122 | u32 msm_readl(const void __iomem *addr) | ||
123 | { | ||
124 | u32 val = readl(addr); | ||
125 | if (reglog) | ||
126 | printk(KERN_ERR "IO:R %08x %08x\n", (u32)addr, val); | ||
127 | return val; | ||
128 | } | ||
129 | |||
130 | /* | ||
131 | * DRM operations: | ||
132 | */ | ||
133 | |||
134 | static int msm_unload(struct drm_device *dev) | ||
135 | { | ||
136 | struct msm_drm_private *priv = dev->dev_private; | ||
137 | struct msm_kms *kms = priv->kms; | ||
138 | |||
139 | drm_kms_helper_poll_fini(dev); | ||
140 | drm_mode_config_cleanup(dev); | ||
141 | drm_vblank_cleanup(dev); | ||
142 | |||
143 | pm_runtime_get_sync(dev->dev); | ||
144 | drm_irq_uninstall(dev); | ||
145 | pm_runtime_put_sync(dev->dev); | ||
146 | |||
147 | flush_workqueue(priv->wq); | ||
148 | destroy_workqueue(priv->wq); | ||
149 | |||
150 | if (kms) { | ||
151 | pm_runtime_disable(dev->dev); | ||
152 | kms->funcs->destroy(kms); | ||
153 | } | ||
154 | |||
155 | |||
156 | dev->dev_private = NULL; | ||
157 | |||
158 | kfree(priv); | ||
159 | |||
160 | return 0; | ||
161 | } | ||
162 | |||
163 | static int msm_load(struct drm_device *dev, unsigned long flags) | ||
164 | { | ||
165 | struct platform_device *pdev = dev->platformdev; | ||
166 | struct msm_drm_private *priv; | ||
167 | struct msm_kms *kms; | ||
168 | int ret; | ||
169 | |||
170 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | ||
171 | if (!priv) { | ||
172 | dev_err(dev->dev, "failed to allocate private data\n"); | ||
173 | return -ENOMEM; | ||
174 | } | ||
175 | |||
176 | dev->dev_private = priv; | ||
177 | |||
178 | priv->wq = alloc_ordered_workqueue("msm", 0); | ||
179 | |||
180 | INIT_LIST_HEAD(&priv->inactive_list); | ||
181 | |||
182 | drm_mode_config_init(dev); | ||
183 | |||
184 | kms = mdp4_kms_init(dev); | ||
185 | if (IS_ERR(kms)) { | ||
186 | /* | ||
187 | * NOTE: once we have GPU support, having no kms should not | ||
188 | * be considered fatal.. ideally we would still support gpu | ||
189 | * and (for example) use dmabuf/prime to share buffers with | ||
190 | * imx drm driver on iMX5 | ||
191 | */ | ||
192 | dev_err(dev->dev, "failed to load kms\n"); | ||
193 | ret = PTR_ERR(priv->kms); | ||
194 | goto fail; | ||
195 | } | ||
196 | |||
197 | priv->kms = kms; | ||
198 | |||
199 | if (kms) { | ||
200 | pm_runtime_enable(dev->dev); | ||
201 | ret = kms->funcs->hw_init(kms); | ||
202 | if (ret) { | ||
203 | dev_err(dev->dev, "kms hw init failed: %d\n", ret); | ||
204 | goto fail; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | dev->mode_config.min_width = 0; | ||
209 | dev->mode_config.min_height = 0; | ||
210 | dev->mode_config.max_width = 2048; | ||
211 | dev->mode_config.max_height = 2048; | ||
212 | dev->mode_config.funcs = &mode_config_funcs; | ||
213 | |||
214 | ret = drm_vblank_init(dev, 1); | ||
215 | if (ret < 0) { | ||
216 | dev_err(dev->dev, "failed to initialize vblank\n"); | ||
217 | goto fail; | ||
218 | } | ||
219 | |||
220 | pm_runtime_get_sync(dev->dev); | ||
221 | ret = drm_irq_install(dev); | ||
222 | pm_runtime_put_sync(dev->dev); | ||
223 | if (ret < 0) { | ||
224 | dev_err(dev->dev, "failed to install IRQ handler\n"); | ||
225 | goto fail; | ||
226 | } | ||
227 | |||
228 | platform_set_drvdata(pdev, dev); | ||
229 | |||
230 | #ifdef CONFIG_DRM_MSM_FBDEV | ||
231 | priv->fbdev = msm_fbdev_init(dev); | ||
232 | #endif | ||
233 | |||
234 | drm_kms_helper_poll_init(dev); | ||
235 | |||
236 | return 0; | ||
237 | |||
238 | fail: | ||
239 | msm_unload(dev); | ||
240 | return ret; | ||
241 | } | ||
242 | |||
243 | static void msm_preclose(struct drm_device *dev, struct drm_file *file) | ||
244 | { | ||
245 | struct msm_drm_private *priv = dev->dev_private; | ||
246 | struct msm_kms *kms = priv->kms; | ||
247 | if (kms) | ||
248 | kms->funcs->preclose(kms, file); | ||
249 | } | ||
250 | |||
251 | static void msm_lastclose(struct drm_device *dev) | ||
252 | { | ||
253 | struct msm_drm_private *priv = dev->dev_private; | ||
254 | if (priv->fbdev) { | ||
255 | drm_modeset_lock_all(dev); | ||
256 | drm_fb_helper_restore_fbdev_mode(priv->fbdev); | ||
257 | drm_modeset_unlock_all(dev); | ||
258 | } | ||
259 | } | ||
260 | |||
261 | static irqreturn_t msm_irq(DRM_IRQ_ARGS) | ||
262 | { | ||
263 | struct drm_device *dev = arg; | ||
264 | struct msm_drm_private *priv = dev->dev_private; | ||
265 | struct msm_kms *kms = priv->kms; | ||
266 | BUG_ON(!kms); | ||
267 | return kms->funcs->irq(kms); | ||
268 | } | ||
269 | |||
270 | static void msm_irq_preinstall(struct drm_device *dev) | ||
271 | { | ||
272 | struct msm_drm_private *priv = dev->dev_private; | ||
273 | struct msm_kms *kms = priv->kms; | ||
274 | BUG_ON(!kms); | ||
275 | kms->funcs->irq_preinstall(kms); | ||
276 | } | ||
277 | |||
278 | static int msm_irq_postinstall(struct drm_device *dev) | ||
279 | { | ||
280 | struct msm_drm_private *priv = dev->dev_private; | ||
281 | struct msm_kms *kms = priv->kms; | ||
282 | BUG_ON(!kms); | ||
283 | return kms->funcs->irq_postinstall(kms); | ||
284 | } | ||
285 | |||
286 | static void msm_irq_uninstall(struct drm_device *dev) | ||
287 | { | ||
288 | struct msm_drm_private *priv = dev->dev_private; | ||
289 | struct msm_kms *kms = priv->kms; | ||
290 | BUG_ON(!kms); | ||
291 | kms->funcs->irq_uninstall(kms); | ||
292 | } | ||
293 | |||
294 | static int msm_enable_vblank(struct drm_device *dev, int crtc_id) | ||
295 | { | ||
296 | struct msm_drm_private *priv = dev->dev_private; | ||
297 | struct msm_kms *kms = priv->kms; | ||
298 | if (!kms) | ||
299 | return -ENXIO; | ||
300 | DBG("dev=%p, crtc=%d", dev, crtc_id); | ||
301 | return kms->funcs->enable_vblank(kms, priv->crtcs[crtc_id]); | ||
302 | } | ||
303 | |||
304 | static void msm_disable_vblank(struct drm_device *dev, int crtc_id) | ||
305 | { | ||
306 | struct msm_drm_private *priv = dev->dev_private; | ||
307 | struct msm_kms *kms = priv->kms; | ||
308 | if (!kms) | ||
309 | return; | ||
310 | DBG("dev=%p, crtc=%d", dev, crtc_id); | ||
311 | kms->funcs->disable_vblank(kms, priv->crtcs[crtc_id]); | ||
312 | } | ||
313 | |||
314 | /* | ||
315 | * DRM debugfs: | ||
316 | */ | ||
317 | |||
318 | #ifdef CONFIG_DEBUG_FS | ||
319 | static int msm_gem_show(struct drm_device *dev, struct seq_file *m) | ||
320 | { | ||
321 | struct msm_drm_private *priv = dev->dev_private; | ||
322 | |||
323 | seq_printf(m, "All Objects:\n"); | ||
324 | msm_gem_describe_objects(&priv->inactive_list, m); | ||
325 | |||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | static int msm_mm_show(struct drm_device *dev, struct seq_file *m) | ||
330 | { | ||
331 | return drm_mm_dump_table(m, dev->mm_private); | ||
332 | } | ||
333 | |||
334 | static int msm_fb_show(struct drm_device *dev, struct seq_file *m) | ||
335 | { | ||
336 | struct msm_drm_private *priv = dev->dev_private; | ||
337 | struct drm_framebuffer *fb, *fbdev_fb = NULL; | ||
338 | |||
339 | if (priv->fbdev) { | ||
340 | seq_printf(m, "fbcon "); | ||
341 | fbdev_fb = priv->fbdev->fb; | ||
342 | msm_framebuffer_describe(fbdev_fb, m); | ||
343 | } | ||
344 | |||
345 | mutex_lock(&dev->mode_config.fb_lock); | ||
346 | list_for_each_entry(fb, &dev->mode_config.fb_list, head) { | ||
347 | if (fb == fbdev_fb) | ||
348 | continue; | ||
349 | |||
350 | seq_printf(m, "user "); | ||
351 | msm_framebuffer_describe(fb, m); | ||
352 | } | ||
353 | mutex_unlock(&dev->mode_config.fb_lock); | ||
354 | |||
355 | return 0; | ||
356 | } | ||
357 | |||
358 | static int show_locked(struct seq_file *m, void *arg) | ||
359 | { | ||
360 | struct drm_info_node *node = (struct drm_info_node *) m->private; | ||
361 | struct drm_device *dev = node->minor->dev; | ||
362 | int (*show)(struct drm_device *dev, struct seq_file *m) = | ||
363 | node->info_ent->data; | ||
364 | int ret; | ||
365 | |||
366 | ret = mutex_lock_interruptible(&dev->struct_mutex); | ||
367 | if (ret) | ||
368 | return ret; | ||
369 | |||
370 | ret = show(dev, m); | ||
371 | |||
372 | mutex_unlock(&dev->struct_mutex); | ||
373 | |||
374 | return ret; | ||
375 | } | ||
376 | |||
377 | static struct drm_info_list msm_debugfs_list[] = { | ||
378 | {"gem", show_locked, 0, msm_gem_show}, | ||
379 | { "mm", show_locked, 0, msm_mm_show }, | ||
380 | { "fb", show_locked, 0, msm_fb_show }, | ||
381 | }; | ||
382 | |||
383 | static int msm_debugfs_init(struct drm_minor *minor) | ||
384 | { | ||
385 | struct drm_device *dev = minor->dev; | ||
386 | int ret; | ||
387 | |||
388 | ret = drm_debugfs_create_files(msm_debugfs_list, | ||
389 | ARRAY_SIZE(msm_debugfs_list), | ||
390 | minor->debugfs_root, minor); | ||
391 | |||
392 | if (ret) { | ||
393 | dev_err(dev->dev, "could not install msm_debugfs_list\n"); | ||
394 | return ret; | ||
395 | } | ||
396 | |||
397 | return ret; | ||
398 | } | ||
399 | |||
400 | static void msm_debugfs_cleanup(struct drm_minor *minor) | ||
401 | { | ||
402 | drm_debugfs_remove_files(msm_debugfs_list, | ||
403 | ARRAY_SIZE(msm_debugfs_list), minor); | ||
404 | } | ||
405 | #endif | ||
406 | |||
407 | static const struct vm_operations_struct vm_ops = { | ||
408 | .fault = msm_gem_fault, | ||
409 | .open = drm_gem_vm_open, | ||
410 | .close = drm_gem_vm_close, | ||
411 | }; | ||
412 | |||
413 | static const struct file_operations fops = { | ||
414 | .owner = THIS_MODULE, | ||
415 | .open = drm_open, | ||
416 | .release = drm_release, | ||
417 | .unlocked_ioctl = drm_ioctl, | ||
418 | #ifdef CONFIG_COMPAT | ||
419 | .compat_ioctl = drm_compat_ioctl, | ||
420 | #endif | ||
421 | .poll = drm_poll, | ||
422 | .read = drm_read, | ||
423 | .llseek = no_llseek, | ||
424 | .mmap = msm_gem_mmap, | ||
425 | }; | ||
426 | |||
427 | static struct drm_driver msm_driver = { | ||
428 | .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET, | ||
429 | .load = msm_load, | ||
430 | .unload = msm_unload, | ||
431 | .preclose = msm_preclose, | ||
432 | .lastclose = msm_lastclose, | ||
433 | .irq_handler = msm_irq, | ||
434 | .irq_preinstall = msm_irq_preinstall, | ||
435 | .irq_postinstall = msm_irq_postinstall, | ||
436 | .irq_uninstall = msm_irq_uninstall, | ||
437 | .get_vblank_counter = drm_vblank_count, | ||
438 | .enable_vblank = msm_enable_vblank, | ||
439 | .disable_vblank = msm_disable_vblank, | ||
440 | .gem_free_object = msm_gem_free_object, | ||
441 | .gem_vm_ops = &vm_ops, | ||
442 | .dumb_create = msm_gem_dumb_create, | ||
443 | .dumb_map_offset = msm_gem_dumb_map_offset, | ||
444 | .dumb_destroy = msm_gem_dumb_destroy, | ||
445 | #ifdef CONFIG_DEBUG_FS | ||
446 | .debugfs_init = msm_debugfs_init, | ||
447 | .debugfs_cleanup = msm_debugfs_cleanup, | ||
448 | #endif | ||
449 | .fops = &fops, | ||
450 | .name = "msm", | ||
451 | .desc = "MSM Snapdragon DRM", | ||
452 | .date = "20130625", | ||
453 | .major = 1, | ||
454 | .minor = 0, | ||
455 | }; | ||
456 | |||
457 | #ifdef CONFIG_PM_SLEEP | ||
458 | static int msm_pm_suspend(struct device *dev) | ||
459 | { | ||
460 | struct drm_device *ddev = dev_get_drvdata(dev); | ||
461 | |||
462 | drm_kms_helper_poll_disable(ddev); | ||
463 | |||
464 | return 0; | ||
465 | } | ||
466 | |||
467 | static int msm_pm_resume(struct device *dev) | ||
468 | { | ||
469 | struct drm_device *ddev = dev_get_drvdata(dev); | ||
470 | |||
471 | drm_kms_helper_poll_enable(ddev); | ||
472 | |||
473 | return 0; | ||
474 | } | ||
475 | #endif | ||
476 | |||
477 | static const struct dev_pm_ops msm_pm_ops = { | ||
478 | SET_SYSTEM_SLEEP_PM_OPS(msm_pm_suspend, msm_pm_resume) | ||
479 | }; | ||
480 | |||
481 | /* | ||
482 | * Platform driver: | ||
483 | */ | ||
484 | |||
485 | static int msm_pdev_probe(struct platform_device *pdev) | ||
486 | { | ||
487 | return drm_platform_init(&msm_driver, pdev); | ||
488 | } | ||
489 | |||
490 | static int msm_pdev_remove(struct platform_device *pdev) | ||
491 | { | ||
492 | drm_platform_exit(&msm_driver, pdev); | ||
493 | |||
494 | return 0; | ||
495 | } | ||
496 | |||
497 | static const struct platform_device_id msm_id[] = { | ||
498 | { "mdp", 0 }, | ||
499 | { } | ||
500 | }; | ||
501 | |||
502 | static struct platform_driver msm_platform_driver = { | ||
503 | .probe = msm_pdev_probe, | ||
504 | .remove = msm_pdev_remove, | ||
505 | .driver = { | ||
506 | .owner = THIS_MODULE, | ||
507 | .name = "msm", | ||
508 | .pm = &msm_pm_ops, | ||
509 | }, | ||
510 | .id_table = msm_id, | ||
511 | }; | ||
512 | |||
513 | static int __init msm_drm_register(void) | ||
514 | { | ||
515 | DBG("init"); | ||
516 | hdmi_register(); | ||
517 | return platform_driver_register(&msm_platform_driver); | ||
518 | } | ||
519 | |||
520 | static void __exit msm_drm_unregister(void) | ||
521 | { | ||
522 | DBG("fini"); | ||
523 | platform_driver_unregister(&msm_platform_driver); | ||
524 | hdmi_unregister(); | ||
525 | } | ||
526 | |||
527 | module_init(msm_drm_register); | ||
528 | module_exit(msm_drm_unregister); | ||
529 | |||
530 | MODULE_AUTHOR("Rob Clark <robdclark@gmail.com"); | ||
531 | MODULE_DESCRIPTION("MSM DRM Driver"); | ||
532 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h new file mode 100644 index 000000000000..36f8ba2f5c84 --- /dev/null +++ b/drivers/gpu/drm/msm/msm_drv.h | |||
@@ -0,0 +1,187 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #ifndef __MSM_DRV_H__ | ||
19 | #define __MSM_DRV_H__ | ||
20 | |||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/clk.h> | ||
23 | #include <linux/cpufreq.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/pm.h> | ||
27 | #include <linux/pm_runtime.h> | ||
28 | #include <linux/slab.h> | ||
29 | #include <linux/list.h> | ||
30 | #include <linux/iommu.h> | ||
31 | #include <linux/types.h> | ||
32 | #include <asm/sizes.h> | ||
33 | |||
34 | #ifndef CONFIG_OF | ||
35 | #include <mach/board.h> | ||
36 | #include <mach/socinfo.h> | ||
37 | #include <mach/iommu_domains.h> | ||
38 | #endif | ||
39 | |||
40 | #include <drm/drmP.h> | ||
41 | #include <drm/drm_crtc_helper.h> | ||
42 | #include <drm/drm_fb_helper.h> | ||
43 | |||
44 | struct msm_kms; | ||
45 | |||
46 | #define NUM_DOMAINS 1 /* one for KMS, then one per gpu core (?) */ | ||
47 | |||
48 | struct msm_drm_private { | ||
49 | |||
50 | struct msm_kms *kms; | ||
51 | |||
52 | struct drm_fb_helper *fbdev; | ||
53 | |||
54 | /* list of GEM objects: */ | ||
55 | struct list_head inactive_list; | ||
56 | |||
57 | struct workqueue_struct *wq; | ||
58 | |||
59 | /* registered IOMMU domains: */ | ||
60 | unsigned int num_iommus; | ||
61 | struct iommu_domain *iommus[NUM_DOMAINS]; | ||
62 | |||
63 | unsigned int num_crtcs; | ||
64 | struct drm_crtc *crtcs[8]; | ||
65 | |||
66 | unsigned int num_encoders; | ||
67 | struct drm_encoder *encoders[8]; | ||
68 | |||
69 | unsigned int num_connectors; | ||
70 | struct drm_connector *connectors[8]; | ||
71 | }; | ||
72 | |||
73 | struct msm_format { | ||
74 | uint32_t pixel_format; | ||
75 | }; | ||
76 | |||
77 | /* As there are different display controller blocks depending on the | ||
78 | * snapdragon version, the kms support is split out and the appropriate | ||
79 | * implementation is loaded at runtime. The kms module is responsible | ||
80 | * for constructing the appropriate planes/crtcs/encoders/connectors. | ||
81 | */ | ||
82 | struct msm_kms_funcs { | ||
83 | /* hw initialization: */ | ||
84 | int (*hw_init)(struct msm_kms *kms); | ||
85 | /* irq handling: */ | ||
86 | void (*irq_preinstall)(struct msm_kms *kms); | ||
87 | int (*irq_postinstall)(struct msm_kms *kms); | ||
88 | void (*irq_uninstall)(struct msm_kms *kms); | ||
89 | irqreturn_t (*irq)(struct msm_kms *kms); | ||
90 | int (*enable_vblank)(struct msm_kms *kms, struct drm_crtc *crtc); | ||
91 | void (*disable_vblank)(struct msm_kms *kms, struct drm_crtc *crtc); | ||
92 | /* misc: */ | ||
93 | const struct msm_format *(*get_format)(struct msm_kms *kms, uint32_t format); | ||
94 | long (*round_pixclk)(struct msm_kms *kms, unsigned long rate, | ||
95 | struct drm_encoder *encoder); | ||
96 | /* cleanup: */ | ||
97 | void (*preclose)(struct msm_kms *kms, struct drm_file *file); | ||
98 | void (*destroy)(struct msm_kms *kms); | ||
99 | }; | ||
100 | |||
101 | struct msm_kms { | ||
102 | const struct msm_kms_funcs *funcs; | ||
103 | }; | ||
104 | |||
105 | struct msm_kms *mdp4_kms_init(struct drm_device *dev); | ||
106 | |||
107 | int msm_register_iommu(struct drm_device *dev, struct iommu_domain *iommu); | ||
108 | int msm_iommu_attach(struct drm_device *dev, struct iommu_domain *iommu, | ||
109 | const char **names, int cnt); | ||
110 | |||
111 | int msm_gem_mmap(struct file *filp, struct vm_area_struct *vma); | ||
112 | int msm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); | ||
113 | uint64_t msm_gem_mmap_offset(struct drm_gem_object *obj); | ||
114 | int msm_gem_get_iova_locked(struct drm_gem_object *obj, int id, | ||
115 | uint32_t *iova); | ||
116 | int msm_gem_get_iova(struct drm_gem_object *obj, int id, uint32_t *iova); | ||
117 | void msm_gem_put_iova(struct drm_gem_object *obj, int id); | ||
118 | int msm_gem_dumb_create(struct drm_file *file, struct drm_device *dev, | ||
119 | struct drm_mode_create_dumb *args); | ||
120 | int msm_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, | ||
121 | uint32_t handle); | ||
122 | int msm_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, | ||
123 | uint32_t handle, uint64_t *offset); | ||
124 | void *msm_gem_vaddr_locked(struct drm_gem_object *obj); | ||
125 | void *msm_gem_vaddr(struct drm_gem_object *obj); | ||
126 | int msm_gem_queue_inactive_work(struct drm_gem_object *obj, | ||
127 | struct work_struct *work); | ||
128 | void msm_gem_free_object(struct drm_gem_object *obj); | ||
129 | int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, | ||
130 | uint32_t size, uint32_t flags, uint32_t *handle); | ||
131 | struct drm_gem_object *msm_gem_new(struct drm_device *dev, | ||
132 | uint32_t size, uint32_t flags); | ||
133 | |||
134 | struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane); | ||
135 | const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb); | ||
136 | struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, | ||
137 | struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos); | ||
138 | struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, | ||
139 | struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd); | ||
140 | |||
141 | struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev); | ||
142 | |||
143 | struct drm_connector *hdmi_connector_init(struct drm_device *dev, | ||
144 | struct drm_encoder *encoder); | ||
145 | void __init hdmi_register(void); | ||
146 | void __exit hdmi_unregister(void); | ||
147 | |||
148 | #ifdef CONFIG_DEBUG_FS | ||
149 | void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m); | ||
150 | void msm_gem_describe_objects(struct list_head *list, struct seq_file *m); | ||
151 | void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m); | ||
152 | #endif | ||
153 | |||
154 | void __iomem *msm_ioremap(struct platform_device *pdev, const char *name, | ||
155 | const char *dbgname); | ||
156 | void msm_writel(u32 data, void __iomem *addr); | ||
157 | u32 msm_readl(const void __iomem *addr); | ||
158 | |||
159 | #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) | ||
160 | #define VERB(fmt, ...) if (0) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) | ||
161 | |||
162 | static inline int align_pitch(int width, int bpp) | ||
163 | { | ||
164 | int bytespp = (bpp + 7) / 8; | ||
165 | /* adreno needs pitch aligned to 32 pixels: */ | ||
166 | return bytespp * ALIGN(width, 32); | ||
167 | } | ||
168 | |||
169 | /* for the generated headers: */ | ||
170 | #define INVALID_IDX(idx) ({BUG(); 0;}) | ||
171 | |||
172 | #define FIELD(val, name) (((val) & name ## __MASK) >> name ## __SHIFT) | ||
173 | |||
174 | /* for conditionally setting boolean flag(s): */ | ||
175 | #define COND(bool, val) ((bool) ? (val) : 0) | ||
176 | |||
177 | /* just put these here until we start adding driver private ioctls: */ | ||
178 | // TODO might shuffle these around.. just need something for now.. | ||
179 | #define MSM_BO_CACHE_MASK 0x0000000f | ||
180 | #define MSM_BO_SCANOUT 0x00010000 /* scanout capable */ | ||
181 | |||
182 | #define MSM_BO_CACHED 0x00000001 /* default */ | ||
183 | #define MSM_BO_WC 0x0000002 | ||
184 | #define MSM_BO_UNCACHED 0x00000004 | ||
185 | |||
186 | |||
187 | #endif /* __MSM_DRV_H__ */ | ||
diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c new file mode 100644 index 000000000000..0286c0eeb10c --- /dev/null +++ b/drivers/gpu/drm/msm/msm_fb.c | |||
@@ -0,0 +1,202 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "msm_drv.h" | ||
19 | |||
20 | #include "drm_crtc.h" | ||
21 | #include "drm_crtc_helper.h" | ||
22 | |||
23 | struct msm_framebuffer { | ||
24 | struct drm_framebuffer base; | ||
25 | const struct msm_format *format; | ||
26 | struct drm_gem_object *planes[2]; | ||
27 | }; | ||
28 | #define to_msm_framebuffer(x) container_of(x, struct msm_framebuffer, base) | ||
29 | |||
30 | |||
31 | static int msm_framebuffer_create_handle(struct drm_framebuffer *fb, | ||
32 | struct drm_file *file_priv, | ||
33 | unsigned int *handle) | ||
34 | { | ||
35 | struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb); | ||
36 | return drm_gem_handle_create(file_priv, | ||
37 | msm_fb->planes[0], handle); | ||
38 | } | ||
39 | |||
40 | static void msm_framebuffer_destroy(struct drm_framebuffer *fb) | ||
41 | { | ||
42 | struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb); | ||
43 | int i, n = drm_format_num_planes(fb->pixel_format); | ||
44 | |||
45 | DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); | ||
46 | |||
47 | drm_framebuffer_cleanup(fb); | ||
48 | |||
49 | for (i = 0; i < n; i++) { | ||
50 | struct drm_gem_object *bo = msm_fb->planes[i]; | ||
51 | if (bo) | ||
52 | drm_gem_object_unreference_unlocked(bo); | ||
53 | } | ||
54 | |||
55 | kfree(msm_fb); | ||
56 | } | ||
57 | |||
58 | static int msm_framebuffer_dirty(struct drm_framebuffer *fb, | ||
59 | struct drm_file *file_priv, unsigned flags, unsigned color, | ||
60 | struct drm_clip_rect *clips, unsigned num_clips) | ||
61 | { | ||
62 | return 0; | ||
63 | } | ||
64 | |||
65 | static const struct drm_framebuffer_funcs msm_framebuffer_funcs = { | ||
66 | .create_handle = msm_framebuffer_create_handle, | ||
67 | .destroy = msm_framebuffer_destroy, | ||
68 | .dirty = msm_framebuffer_dirty, | ||
69 | }; | ||
70 | |||
71 | #ifdef CONFIG_DEBUG_FS | ||
72 | void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m) | ||
73 | { | ||
74 | struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb); | ||
75 | int i, n = drm_format_num_planes(fb->pixel_format); | ||
76 | |||
77 | seq_printf(m, "fb: %dx%d@%4.4s (%2d, ID:%d)\n", | ||
78 | fb->width, fb->height, (char *)&fb->pixel_format, | ||
79 | fb->refcount.refcount.counter, fb->base.id); | ||
80 | |||
81 | for (i = 0; i < n; i++) { | ||
82 | seq_printf(m, " %d: offset=%d pitch=%d, obj: ", | ||
83 | i, fb->offsets[i], fb->pitches[i]); | ||
84 | msm_gem_describe(msm_fb->planes[i], m); | ||
85 | } | ||
86 | } | ||
87 | #endif | ||
88 | |||
89 | struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane) | ||
90 | { | ||
91 | struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb); | ||
92 | return msm_fb->planes[plane]; | ||
93 | } | ||
94 | |||
95 | const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb) | ||
96 | { | ||
97 | struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb); | ||
98 | return msm_fb->format; | ||
99 | } | ||
100 | |||
101 | struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev, | ||
102 | struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd) | ||
103 | { | ||
104 | struct drm_gem_object *bos[4] = {0}; | ||
105 | struct drm_framebuffer *fb; | ||
106 | int ret, i, n = drm_format_num_planes(mode_cmd->pixel_format); | ||
107 | |||
108 | for (i = 0; i < n; i++) { | ||
109 | bos[i] = drm_gem_object_lookup(dev, file, | ||
110 | mode_cmd->handles[i]); | ||
111 | if (!bos[i]) { | ||
112 | ret = -ENXIO; | ||
113 | goto out_unref; | ||
114 | } | ||
115 | } | ||
116 | |||
117 | fb = msm_framebuffer_init(dev, mode_cmd, bos); | ||
118 | if (IS_ERR(fb)) { | ||
119 | ret = PTR_ERR(fb); | ||
120 | goto out_unref; | ||
121 | } | ||
122 | |||
123 | return fb; | ||
124 | |||
125 | out_unref: | ||
126 | for (i = 0; i < n; i++) | ||
127 | drm_gem_object_unreference_unlocked(bos[i]); | ||
128 | return ERR_PTR(ret); | ||
129 | } | ||
130 | |||
131 | struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev, | ||
132 | struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos) | ||
133 | { | ||
134 | struct msm_drm_private *priv = dev->dev_private; | ||
135 | struct msm_kms *kms = priv->kms; | ||
136 | struct msm_framebuffer *msm_fb; | ||
137 | struct drm_framebuffer *fb = NULL; | ||
138 | const struct msm_format *format; | ||
139 | int ret, i, n; | ||
140 | unsigned int hsub, vsub; | ||
141 | |||
142 | DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%4.4s)", | ||
143 | dev, mode_cmd, mode_cmd->width, mode_cmd->height, | ||
144 | (char *)&mode_cmd->pixel_format); | ||
145 | |||
146 | n = drm_format_num_planes(mode_cmd->pixel_format); | ||
147 | hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format); | ||
148 | vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format); | ||
149 | |||
150 | format = kms->funcs->get_format(kms, mode_cmd->pixel_format); | ||
151 | if (!format) { | ||
152 | dev_err(dev->dev, "unsupported pixel format: %4.4s\n", | ||
153 | (char *)&mode_cmd->pixel_format); | ||
154 | ret = -EINVAL; | ||
155 | goto fail; | ||
156 | } | ||
157 | |||
158 | msm_fb = kzalloc(sizeof(*msm_fb), GFP_KERNEL); | ||
159 | if (!msm_fb) { | ||
160 | ret = -ENOMEM; | ||
161 | goto fail; | ||
162 | } | ||
163 | |||
164 | fb = &msm_fb->base; | ||
165 | |||
166 | msm_fb->format = format; | ||
167 | |||
168 | for (i = 0; i < n; i++) { | ||
169 | unsigned int width = mode_cmd->width / (i ? hsub : 1); | ||
170 | unsigned int height = mode_cmd->height / (i ? vsub : 1); | ||
171 | unsigned int min_size; | ||
172 | |||
173 | min_size = (height - 1) * mode_cmd->pitches[i] | ||
174 | + width * drm_format_plane_cpp(mode_cmd->pixel_format, i) | ||
175 | + mode_cmd->offsets[i]; | ||
176 | |||
177 | if (bos[i]->size < min_size) { | ||
178 | ret = -EINVAL; | ||
179 | goto fail; | ||
180 | } | ||
181 | |||
182 | msm_fb->planes[i] = bos[i]; | ||
183 | } | ||
184 | |||
185 | drm_helper_mode_fill_fb_struct(fb, mode_cmd); | ||
186 | |||
187 | ret = drm_framebuffer_init(dev, fb, &msm_framebuffer_funcs); | ||
188 | if (ret) { | ||
189 | dev_err(dev->dev, "framebuffer init failed: %d\n", ret); | ||
190 | goto fail; | ||
191 | } | ||
192 | |||
193 | DBG("create: FB ID: %d (%p)", fb->base.id, fb); | ||
194 | |||
195 | return fb; | ||
196 | |||
197 | fail: | ||
198 | if (fb) | ||
199 | msm_framebuffer_destroy(fb); | ||
200 | |||
201 | return ERR_PTR(ret); | ||
202 | } | ||
diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c new file mode 100644 index 000000000000..6c6d7d4c9b4e --- /dev/null +++ b/drivers/gpu/drm/msm/msm_fbdev.c | |||
@@ -0,0 +1,258 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include "msm_drv.h" | ||
19 | |||
20 | #include "drm_crtc.h" | ||
21 | #include "drm_fb_helper.h" | ||
22 | |||
23 | /* | ||
24 | * fbdev funcs, to implement legacy fbdev interface on top of drm driver | ||
25 | */ | ||
26 | |||
27 | #define to_msm_fbdev(x) container_of(x, struct msm_fbdev, base) | ||
28 | |||
29 | struct msm_fbdev { | ||
30 | struct drm_fb_helper base; | ||
31 | struct drm_framebuffer *fb; | ||
32 | struct drm_gem_object *bo; | ||
33 | }; | ||
34 | |||
35 | static struct fb_ops msm_fb_ops = { | ||
36 | .owner = THIS_MODULE, | ||
37 | |||
38 | /* Note: to properly handle manual update displays, we wrap the | ||
39 | * basic fbdev ops which write to the framebuffer | ||
40 | */ | ||
41 | .fb_read = fb_sys_read, | ||
42 | .fb_write = fb_sys_write, | ||
43 | .fb_fillrect = sys_fillrect, | ||
44 | .fb_copyarea = sys_copyarea, | ||
45 | .fb_imageblit = sys_imageblit, | ||
46 | |||
47 | .fb_check_var = drm_fb_helper_check_var, | ||
48 | .fb_set_par = drm_fb_helper_set_par, | ||
49 | .fb_pan_display = drm_fb_helper_pan_display, | ||
50 | .fb_blank = drm_fb_helper_blank, | ||
51 | .fb_setcmap = drm_fb_helper_setcmap, | ||
52 | }; | ||
53 | |||
54 | static int msm_fbdev_create(struct drm_fb_helper *helper, | ||
55 | struct drm_fb_helper_surface_size *sizes) | ||
56 | { | ||
57 | struct msm_fbdev *fbdev = to_msm_fbdev(helper); | ||
58 | struct drm_device *dev = helper->dev; | ||
59 | struct drm_framebuffer *fb = NULL; | ||
60 | struct fb_info *fbi = NULL; | ||
61 | struct drm_mode_fb_cmd2 mode_cmd = {0}; | ||
62 | dma_addr_t paddr; | ||
63 | int ret, size; | ||
64 | |||
65 | /* only doing ARGB32 since this is what is needed to alpha-blend | ||
66 | * with video overlays: | ||
67 | */ | ||
68 | sizes->surface_bpp = 32; | ||
69 | sizes->surface_depth = 32; | ||
70 | |||
71 | DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, | ||
72 | sizes->surface_height, sizes->surface_bpp, | ||
73 | sizes->fb_width, sizes->fb_height); | ||
74 | |||
75 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, | ||
76 | sizes->surface_depth); | ||
77 | |||
78 | mode_cmd.width = sizes->surface_width; | ||
79 | mode_cmd.height = sizes->surface_height; | ||
80 | |||
81 | mode_cmd.pitches[0] = align_pitch( | ||
82 | mode_cmd.width, sizes->surface_bpp); | ||
83 | |||
84 | /* allocate backing bo */ | ||
85 | size = mode_cmd.pitches[0] * mode_cmd.height; | ||
86 | DBG("allocating %d bytes for fb %d", size, dev->primary->index); | ||
87 | mutex_lock(&dev->struct_mutex); | ||
88 | fbdev->bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC); | ||
89 | mutex_unlock(&dev->struct_mutex); | ||
90 | if (IS_ERR(fbdev->bo)) { | ||
91 | ret = PTR_ERR(fbdev->bo); | ||
92 | fbdev->bo = NULL; | ||
93 | dev_err(dev->dev, "failed to allocate buffer object: %d\n", ret); | ||
94 | goto fail; | ||
95 | } | ||
96 | |||
97 | fb = msm_framebuffer_init(dev, &mode_cmd, &fbdev->bo); | ||
98 | if (IS_ERR(fb)) { | ||
99 | dev_err(dev->dev, "failed to allocate fb\n"); | ||
100 | /* note: if fb creation failed, we can't rely on fb destroy | ||
101 | * to unref the bo: | ||
102 | */ | ||
103 | drm_gem_object_unreference(fbdev->bo); | ||
104 | ret = PTR_ERR(fb); | ||
105 | goto fail; | ||
106 | } | ||
107 | |||
108 | mutex_lock(&dev->struct_mutex); | ||
109 | |||
110 | /* TODO implement our own fb_mmap so we don't need this: */ | ||
111 | msm_gem_get_iova_locked(fbdev->bo, 0, &paddr); | ||
112 | |||
113 | fbi = framebuffer_alloc(0, dev->dev); | ||
114 | if (!fbi) { | ||
115 | dev_err(dev->dev, "failed to allocate fb info\n"); | ||
116 | ret = -ENOMEM; | ||
117 | goto fail_unlock; | ||
118 | } | ||
119 | |||
120 | DBG("fbi=%p, dev=%p", fbi, dev); | ||
121 | |||
122 | fbdev->fb = fb; | ||
123 | helper->fb = fb; | ||
124 | helper->fbdev = fbi; | ||
125 | |||
126 | fbi->par = helper; | ||
127 | fbi->flags = FBINFO_DEFAULT; | ||
128 | fbi->fbops = &msm_fb_ops; | ||
129 | |||
130 | strcpy(fbi->fix.id, "msm"); | ||
131 | |||
132 | ret = fb_alloc_cmap(&fbi->cmap, 256, 0); | ||
133 | if (ret) { | ||
134 | ret = -ENOMEM; | ||
135 | goto fail_unlock; | ||
136 | } | ||
137 | |||
138 | drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); | ||
139 | drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); | ||
140 | |||
141 | dev->mode_config.fb_base = paddr; | ||
142 | |||
143 | fbi->screen_base = msm_gem_vaddr_locked(fbdev->bo); | ||
144 | fbi->screen_size = fbdev->bo->size; | ||
145 | fbi->fix.smem_start = paddr; | ||
146 | fbi->fix.smem_len = fbdev->bo->size; | ||
147 | |||
148 | DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); | ||
149 | DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); | ||
150 | |||
151 | mutex_unlock(&dev->struct_mutex); | ||
152 | |||
153 | return 0; | ||
154 | |||
155 | fail_unlock: | ||
156 | mutex_unlock(&dev->struct_mutex); | ||
157 | fail: | ||
158 | |||
159 | if (ret) { | ||
160 | if (fbi) | ||
161 | framebuffer_release(fbi); | ||
162 | if (fb) { | ||
163 | drm_framebuffer_unregister_private(fb); | ||
164 | drm_framebuffer_remove(fb); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | return ret; | ||
169 | } | ||
170 | |||
171 | static void msm_crtc_fb_gamma_set(struct drm_crtc *crtc, | ||
172 | u16 red, u16 green, u16 blue, int regno) | ||
173 | { | ||
174 | DBG("fbdev: set gamma"); | ||
175 | } | ||
176 | |||
177 | static void msm_crtc_fb_gamma_get(struct drm_crtc *crtc, | ||
178 | u16 *red, u16 *green, u16 *blue, int regno) | ||
179 | { | ||
180 | DBG("fbdev: get gamma"); | ||
181 | } | ||
182 | |||
183 | static struct drm_fb_helper_funcs msm_fb_helper_funcs = { | ||
184 | .gamma_set = msm_crtc_fb_gamma_set, | ||
185 | .gamma_get = msm_crtc_fb_gamma_get, | ||
186 | .fb_probe = msm_fbdev_create, | ||
187 | }; | ||
188 | |||
189 | /* initialize fbdev helper */ | ||
190 | struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev) | ||
191 | { | ||
192 | struct msm_drm_private *priv = dev->dev_private; | ||
193 | struct msm_fbdev *fbdev = NULL; | ||
194 | struct drm_fb_helper *helper; | ||
195 | int ret = 0; | ||
196 | |||
197 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | ||
198 | if (!fbdev) | ||
199 | goto fail; | ||
200 | |||
201 | helper = &fbdev->base; | ||
202 | |||
203 | helper->funcs = &msm_fb_helper_funcs; | ||
204 | |||
205 | ret = drm_fb_helper_init(dev, helper, | ||
206 | priv->num_crtcs, priv->num_connectors); | ||
207 | if (ret) { | ||
208 | dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); | ||
209 | goto fail; | ||
210 | } | ||
211 | |||
212 | drm_fb_helper_single_add_all_connectors(helper); | ||
213 | |||
214 | /* disable all the possible outputs/crtcs before entering KMS mode */ | ||
215 | drm_helper_disable_unused_functions(dev); | ||
216 | |||
217 | drm_fb_helper_initial_config(helper, 32); | ||
218 | |||
219 | priv->fbdev = helper; | ||
220 | |||
221 | return helper; | ||
222 | |||
223 | fail: | ||
224 | kfree(fbdev); | ||
225 | return NULL; | ||
226 | } | ||
227 | |||
228 | void msm_fbdev_free(struct drm_device *dev) | ||
229 | { | ||
230 | struct msm_drm_private *priv = dev->dev_private; | ||
231 | struct drm_fb_helper *helper = priv->fbdev; | ||
232 | struct msm_fbdev *fbdev; | ||
233 | struct fb_info *fbi; | ||
234 | |||
235 | DBG(); | ||
236 | |||
237 | fbi = helper->fbdev; | ||
238 | |||
239 | /* only cleanup framebuffer if it is present */ | ||
240 | if (fbi) { | ||
241 | unregister_framebuffer(fbi); | ||
242 | framebuffer_release(fbi); | ||
243 | } | ||
244 | |||
245 | drm_fb_helper_fini(helper); | ||
246 | |||
247 | fbdev = to_msm_fbdev(priv->fbdev); | ||
248 | |||
249 | /* this will free the backing object */ | ||
250 | if (fbdev->fb) { | ||
251 | drm_framebuffer_unregister_private(fbdev->fb); | ||
252 | drm_framebuffer_remove(fbdev->fb); | ||
253 | } | ||
254 | |||
255 | kfree(fbdev); | ||
256 | |||
257 | priv->fbdev = NULL; | ||
258 | } | ||
diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c new file mode 100644 index 000000000000..a52e6cca8403 --- /dev/null +++ b/drivers/gpu/drm/msm/msm_gem.c | |||
@@ -0,0 +1,521 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include <linux/spinlock.h> | ||
19 | #include <linux/shmem_fs.h> | ||
20 | |||
21 | #include "msm_drv.h" | ||
22 | #include "msm_gem.h" | ||
23 | |||
24 | |||
25 | /* called with dev->struct_mutex held */ | ||
26 | static struct page **get_pages(struct drm_gem_object *obj) | ||
27 | { | ||
28 | struct msm_gem_object *msm_obj = to_msm_bo(obj); | ||
29 | |||
30 | if (!msm_obj->pages) { | ||
31 | struct drm_device *dev = obj->dev; | ||
32 | struct page **p = drm_gem_get_pages(obj, 0); | ||
33 | int npages = obj->size >> PAGE_SHIFT; | ||
34 | |||
35 | if (IS_ERR(p)) { | ||
36 | dev_err(dev->dev, "could not get pages: %ld\n", | ||
37 | PTR_ERR(p)); | ||
38 | return p; | ||
39 | } | ||
40 | |||
41 | msm_obj->sgt = drm_prime_pages_to_sg(p, npages); | ||
42 | if (!msm_obj->sgt) { | ||
43 | dev_err(dev->dev, "failed to allocate sgt\n"); | ||
44 | return ERR_PTR(-ENOMEM); | ||
45 | } | ||
46 | |||
47 | msm_obj->pages = p; | ||
48 | |||
49 | /* For non-cached buffers, ensure the new pages are clean | ||
50 | * because display controller, GPU, etc. are not coherent: | ||
51 | */ | ||
52 | if (msm_obj->flags & (MSM_BO_WC|MSM_BO_UNCACHED)) | ||
53 | dma_map_sg(dev->dev, msm_obj->sgt->sgl, | ||
54 | msm_obj->sgt->nents, DMA_BIDIRECTIONAL); | ||
55 | } | ||
56 | |||
57 | return msm_obj->pages; | ||
58 | } | ||
59 | |||
60 | static void put_pages(struct drm_gem_object *obj) | ||
61 | { | ||
62 | struct msm_gem_object *msm_obj = to_msm_bo(obj); | ||
63 | |||
64 | if (msm_obj->pages) { | ||
65 | /* For non-cached buffers, ensure the new pages are clean | ||
66 | * because display controller, GPU, etc. are not coherent: | ||
67 | */ | ||
68 | if (msm_obj->flags & (MSM_BO_WC|MSM_BO_UNCACHED)) | ||
69 | dma_unmap_sg(obj->dev->dev, msm_obj->sgt->sgl, | ||
70 | msm_obj->sgt->nents, DMA_BIDIRECTIONAL); | ||
71 | sg_free_table(msm_obj->sgt); | ||
72 | kfree(msm_obj->sgt); | ||
73 | |||
74 | drm_gem_put_pages(obj, msm_obj->pages, true, false); | ||
75 | msm_obj->pages = NULL; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | int msm_gem_mmap_obj(struct drm_gem_object *obj, | ||
80 | struct vm_area_struct *vma) | ||
81 | { | ||
82 | struct msm_gem_object *msm_obj = to_msm_bo(obj); | ||
83 | |||
84 | vma->vm_flags &= ~VM_PFNMAP; | ||
85 | vma->vm_flags |= VM_MIXEDMAP; | ||
86 | |||
87 | if (msm_obj->flags & MSM_BO_WC) { | ||
88 | vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); | ||
89 | } else if (msm_obj->flags & MSM_BO_UNCACHED) { | ||
90 | vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); | ||
91 | } else { | ||
92 | /* | ||
93 | * Shunt off cached objs to shmem file so they have their own | ||
94 | * address_space (so unmap_mapping_range does what we want, | ||
95 | * in particular in the case of mmap'd dmabufs) | ||
96 | */ | ||
97 | fput(vma->vm_file); | ||
98 | get_file(obj->filp); | ||
99 | vma->vm_pgoff = 0; | ||
100 | vma->vm_file = obj->filp; | ||
101 | |||
102 | vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); | ||
103 | } | ||
104 | |||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | int msm_gem_mmap(struct file *filp, struct vm_area_struct *vma) | ||
109 | { | ||
110 | int ret; | ||
111 | |||
112 | ret = drm_gem_mmap(filp, vma); | ||
113 | if (ret) { | ||
114 | DBG("mmap failed: %d", ret); | ||
115 | return ret; | ||
116 | } | ||
117 | |||
118 | return msm_gem_mmap_obj(vma->vm_private_data, vma); | ||
119 | } | ||
120 | |||
121 | int msm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) | ||
122 | { | ||
123 | struct drm_gem_object *obj = vma->vm_private_data; | ||
124 | struct msm_gem_object *msm_obj = to_msm_bo(obj); | ||
125 | struct drm_device *dev = obj->dev; | ||
126 | struct page **pages; | ||
127 | unsigned long pfn; | ||
128 | pgoff_t pgoff; | ||
129 | int ret; | ||
130 | |||
131 | /* Make sure we don't parallel update on a fault, nor move or remove | ||
132 | * something from beneath our feet | ||
133 | */ | ||
134 | ret = mutex_lock_interruptible(&dev->struct_mutex); | ||
135 | if (ret) | ||
136 | goto out; | ||
137 | |||
138 | /* make sure we have pages attached now */ | ||
139 | pages = get_pages(obj); | ||
140 | if (IS_ERR(pages)) { | ||
141 | ret = PTR_ERR(pages); | ||
142 | goto out_unlock; | ||
143 | } | ||
144 | |||
145 | /* We don't use vmf->pgoff since that has the fake offset: */ | ||
146 | pgoff = ((unsigned long)vmf->virtual_address - | ||
147 | vma->vm_start) >> PAGE_SHIFT; | ||
148 | |||
149 | pfn = page_to_pfn(msm_obj->pages[pgoff]); | ||
150 | |||
151 | VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address, | ||
152 | pfn, pfn << PAGE_SHIFT); | ||
153 | |||
154 | ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn); | ||
155 | |||
156 | out_unlock: | ||
157 | mutex_unlock(&dev->struct_mutex); | ||
158 | out: | ||
159 | switch (ret) { | ||
160 | case -EAGAIN: | ||
161 | set_need_resched(); | ||
162 | case 0: | ||
163 | case -ERESTARTSYS: | ||
164 | case -EINTR: | ||
165 | return VM_FAULT_NOPAGE; | ||
166 | case -ENOMEM: | ||
167 | return VM_FAULT_OOM; | ||
168 | default: | ||
169 | return VM_FAULT_SIGBUS; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | /** get mmap offset */ | ||
174 | static uint64_t mmap_offset(struct drm_gem_object *obj) | ||
175 | { | ||
176 | struct drm_device *dev = obj->dev; | ||
177 | int ret; | ||
178 | |||
179 | WARN_ON(!mutex_is_locked(&dev->struct_mutex)); | ||
180 | |||
181 | /* Make it mmapable */ | ||
182 | ret = drm_gem_create_mmap_offset(obj); | ||
183 | |||
184 | if (ret) { | ||
185 | dev_err(dev->dev, "could not allocate mmap offset\n"); | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | return drm_vma_node_offset_addr(&obj->vma_node); | ||
190 | } | ||
191 | |||
192 | uint64_t msm_gem_mmap_offset(struct drm_gem_object *obj) | ||
193 | { | ||
194 | uint64_t offset; | ||
195 | mutex_lock(&obj->dev->struct_mutex); | ||
196 | offset = mmap_offset(obj); | ||
197 | mutex_unlock(&obj->dev->struct_mutex); | ||
198 | return offset; | ||
199 | } | ||
200 | |||
201 | /* helpers for dealing w/ iommu: */ | ||
202 | static int map_range(struct iommu_domain *domain, unsigned int iova, | ||
203 | struct sg_table *sgt, unsigned int len, int prot) | ||
204 | { | ||
205 | struct scatterlist *sg; | ||
206 | unsigned int da = iova; | ||
207 | unsigned int i, j; | ||
208 | int ret; | ||
209 | |||
210 | if (!domain || !sgt) | ||
211 | return -EINVAL; | ||
212 | |||
213 | for_each_sg(sgt->sgl, sg, sgt->nents, i) { | ||
214 | u32 pa = sg_phys(sg) - sg->offset; | ||
215 | size_t bytes = sg->length + sg->offset; | ||
216 | |||
217 | VERB("map[%d]: %08x %08x(%x)", i, iova, pa, bytes); | ||
218 | |||
219 | ret = iommu_map(domain, da, pa, bytes, prot); | ||
220 | if (ret) | ||
221 | goto fail; | ||
222 | |||
223 | da += bytes; | ||
224 | } | ||
225 | |||
226 | return 0; | ||
227 | |||
228 | fail: | ||
229 | da = iova; | ||
230 | |||
231 | for_each_sg(sgt->sgl, sg, i, j) { | ||
232 | size_t bytes = sg->length + sg->offset; | ||
233 | iommu_unmap(domain, da, bytes); | ||
234 | da += bytes; | ||
235 | } | ||
236 | return ret; | ||
237 | } | ||
238 | |||
239 | static void unmap_range(struct iommu_domain *domain, unsigned int iova, | ||
240 | struct sg_table *sgt, unsigned int len) | ||
241 | { | ||
242 | struct scatterlist *sg; | ||
243 | unsigned int da = iova; | ||
244 | int i; | ||
245 | |||
246 | for_each_sg(sgt->sgl, sg, sgt->nents, i) { | ||
247 | size_t bytes = sg->length + sg->offset; | ||
248 | size_t unmapped; | ||
249 | |||
250 | unmapped = iommu_unmap(domain, da, bytes); | ||
251 | if (unmapped < bytes) | ||
252 | break; | ||
253 | |||
254 | VERB("unmap[%d]: %08x(%x)", i, iova, bytes); | ||
255 | |||
256 | BUG_ON(!IS_ALIGNED(bytes, PAGE_SIZE)); | ||
257 | |||
258 | da += bytes; | ||
259 | } | ||
260 | } | ||
261 | |||
262 | /* should be called under struct_mutex.. although it can be called | ||
263 | * from atomic context without struct_mutex to acquire an extra | ||
264 | * iova ref if you know one is already held. | ||
265 | * | ||
266 | * That means when I do eventually need to add support for unpinning | ||
267 | * the refcnt counter needs to be atomic_t. | ||
268 | */ | ||
269 | int msm_gem_get_iova_locked(struct drm_gem_object *obj, int id, | ||
270 | uint32_t *iova) | ||
271 | { | ||
272 | struct msm_gem_object *msm_obj = to_msm_bo(obj); | ||
273 | int ret = 0; | ||
274 | |||
275 | if (!msm_obj->domain[id].iova) { | ||
276 | struct msm_drm_private *priv = obj->dev->dev_private; | ||
277 | uint32_t offset = (uint32_t)mmap_offset(obj); | ||
278 | struct page **pages; | ||
279 | pages = get_pages(obj); | ||
280 | if (IS_ERR(pages)) | ||
281 | return PTR_ERR(pages); | ||
282 | // XXX ideally we would not map buffers writable when not needed... | ||
283 | ret = map_range(priv->iommus[id], offset, msm_obj->sgt, | ||
284 | obj->size, IOMMU_READ | IOMMU_WRITE); | ||
285 | msm_obj->domain[id].iova = offset; | ||
286 | } | ||
287 | |||
288 | if (!ret) | ||
289 | *iova = msm_obj->domain[id].iova; | ||
290 | |||
291 | return ret; | ||
292 | } | ||
293 | |||
294 | int msm_gem_get_iova(struct drm_gem_object *obj, int id, uint32_t *iova) | ||
295 | { | ||
296 | int ret; | ||
297 | mutex_lock(&obj->dev->struct_mutex); | ||
298 | ret = msm_gem_get_iova_locked(obj, id, iova); | ||
299 | mutex_unlock(&obj->dev->struct_mutex); | ||
300 | return ret; | ||
301 | } | ||
302 | |||
303 | void msm_gem_put_iova(struct drm_gem_object *obj, int id) | ||
304 | { | ||
305 | // XXX TODO .. | ||
306 | // NOTE: probably don't need a _locked() version.. we wouldn't | ||
307 | // normally unmap here, but instead just mark that it could be | ||
308 | // unmapped (if the iova refcnt drops to zero), but then later | ||
309 | // if another _get_iova_locked() fails we can start unmapping | ||
310 | // things that are no longer needed.. | ||
311 | } | ||
312 | |||
313 | int msm_gem_dumb_create(struct drm_file *file, struct drm_device *dev, | ||
314 | struct drm_mode_create_dumb *args) | ||
315 | { | ||
316 | args->pitch = align_pitch(args->width, args->bpp); | ||
317 | args->size = PAGE_ALIGN(args->pitch * args->height); | ||
318 | return msm_gem_new_handle(dev, file, args->size, | ||
319 | MSM_BO_SCANOUT | MSM_BO_WC, &args->handle); | ||
320 | } | ||
321 | |||
322 | int msm_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, | ||
323 | uint32_t handle) | ||
324 | { | ||
325 | /* No special work needed, drop the reference and see what falls out */ | ||
326 | return drm_gem_handle_delete(file, handle); | ||
327 | } | ||
328 | |||
329 | int msm_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, | ||
330 | uint32_t handle, uint64_t *offset) | ||
331 | { | ||
332 | struct drm_gem_object *obj; | ||
333 | int ret = 0; | ||
334 | |||
335 | /* GEM does all our handle to object mapping */ | ||
336 | obj = drm_gem_object_lookup(dev, file, handle); | ||
337 | if (obj == NULL) { | ||
338 | ret = -ENOENT; | ||
339 | goto fail; | ||
340 | } | ||
341 | |||
342 | *offset = msm_gem_mmap_offset(obj); | ||
343 | |||
344 | drm_gem_object_unreference_unlocked(obj); | ||
345 | |||
346 | fail: | ||
347 | return ret; | ||
348 | } | ||
349 | |||
350 | void *msm_gem_vaddr_locked(struct drm_gem_object *obj) | ||
351 | { | ||
352 | struct msm_gem_object *msm_obj = to_msm_bo(obj); | ||
353 | WARN_ON(!mutex_is_locked(&obj->dev->struct_mutex)); | ||
354 | if (!msm_obj->vaddr) { | ||
355 | struct page **pages = get_pages(obj); | ||
356 | if (IS_ERR(pages)) | ||
357 | return ERR_CAST(pages); | ||
358 | msm_obj->vaddr = vmap(pages, obj->size >> PAGE_SHIFT, | ||
359 | VM_MAP, pgprot_writecombine(PAGE_KERNEL)); | ||
360 | } | ||
361 | return msm_obj->vaddr; | ||
362 | } | ||
363 | |||
364 | void *msm_gem_vaddr(struct drm_gem_object *obj) | ||
365 | { | ||
366 | void *ret; | ||
367 | mutex_lock(&obj->dev->struct_mutex); | ||
368 | ret = msm_gem_vaddr_locked(obj); | ||
369 | mutex_unlock(&obj->dev->struct_mutex); | ||
370 | return ret; | ||
371 | } | ||
372 | |||
373 | int msm_gem_queue_inactive_work(struct drm_gem_object *obj, | ||
374 | struct work_struct *work) | ||
375 | { | ||
376 | struct drm_device *dev = obj->dev; | ||
377 | struct msm_drm_private *priv = dev->dev_private; | ||
378 | |||
379 | /* just a place-holder until we have gpu.. */ | ||
380 | queue_work(priv->wq, work); | ||
381 | |||
382 | return 0; | ||
383 | } | ||
384 | |||
385 | #ifdef CONFIG_DEBUG_FS | ||
386 | void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m) | ||
387 | { | ||
388 | struct drm_device *dev = obj->dev; | ||
389 | struct msm_gem_object *msm_obj = to_msm_bo(obj); | ||
390 | uint64_t off = drm_vma_node_start(&obj->vma_node); | ||
391 | |||
392 | WARN_ON(!mutex_is_locked(&dev->struct_mutex)); | ||
393 | seq_printf(m, "%08x: %2d (%2d) %08llx %p %d\n", | ||
394 | msm_obj->flags, obj->name, obj->refcount.refcount.counter, | ||
395 | off, msm_obj->vaddr, obj->size); | ||
396 | } | ||
397 | |||
398 | void msm_gem_describe_objects(struct list_head *list, struct seq_file *m) | ||
399 | { | ||
400 | struct msm_gem_object *msm_obj; | ||
401 | int count = 0; | ||
402 | size_t size = 0; | ||
403 | |||
404 | list_for_each_entry(msm_obj, list, mm_list) { | ||
405 | struct drm_gem_object *obj = &msm_obj->base; | ||
406 | seq_printf(m, " "); | ||
407 | msm_gem_describe(obj, m); | ||
408 | count++; | ||
409 | size += obj->size; | ||
410 | } | ||
411 | |||
412 | seq_printf(m, "Total %d objects, %zu bytes\n", count, size); | ||
413 | } | ||
414 | #endif | ||
415 | |||
416 | void msm_gem_free_object(struct drm_gem_object *obj) | ||
417 | { | ||
418 | struct drm_device *dev = obj->dev; | ||
419 | struct msm_gem_object *msm_obj = to_msm_bo(obj); | ||
420 | int id; | ||
421 | |||
422 | WARN_ON(!mutex_is_locked(&dev->struct_mutex)); | ||
423 | |||
424 | list_del(&msm_obj->mm_list); | ||
425 | |||
426 | for (id = 0; id < ARRAY_SIZE(msm_obj->domain); id++) { | ||
427 | if (msm_obj->domain[id].iova) { | ||
428 | struct msm_drm_private *priv = obj->dev->dev_private; | ||
429 | uint32_t offset = (uint32_t)mmap_offset(obj); | ||
430 | unmap_range(priv->iommus[id], offset, | ||
431 | msm_obj->sgt, obj->size); | ||
432 | } | ||
433 | } | ||
434 | |||
435 | drm_gem_free_mmap_offset(obj); | ||
436 | |||
437 | if (msm_obj->vaddr) | ||
438 | vunmap(msm_obj->vaddr); | ||
439 | |||
440 | put_pages(obj); | ||
441 | |||
442 | drm_gem_object_release(obj); | ||
443 | |||
444 | kfree(msm_obj); | ||
445 | } | ||
446 | |||
447 | /* convenience method to construct a GEM buffer object, and userspace handle */ | ||
448 | int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file, | ||
449 | uint32_t size, uint32_t flags, uint32_t *handle) | ||
450 | { | ||
451 | struct drm_gem_object *obj; | ||
452 | int ret; | ||
453 | |||
454 | ret = mutex_lock_interruptible(&dev->struct_mutex); | ||
455 | if (ret) | ||
456 | return ret; | ||
457 | |||
458 | obj = msm_gem_new(dev, size, flags); | ||
459 | |||
460 | mutex_unlock(&dev->struct_mutex); | ||
461 | |||
462 | if (IS_ERR(obj)) | ||
463 | return PTR_ERR(obj); | ||
464 | |||
465 | ret = drm_gem_handle_create(file, obj, handle); | ||
466 | |||
467 | /* drop reference from allocate - handle holds it now */ | ||
468 | drm_gem_object_unreference_unlocked(obj); | ||
469 | |||
470 | return ret; | ||
471 | } | ||
472 | |||
473 | struct drm_gem_object *msm_gem_new(struct drm_device *dev, | ||
474 | uint32_t size, uint32_t flags) | ||
475 | { | ||
476 | struct msm_drm_private *priv = dev->dev_private; | ||
477 | struct msm_gem_object *msm_obj; | ||
478 | struct drm_gem_object *obj = NULL; | ||
479 | int ret; | ||
480 | |||
481 | WARN_ON(!mutex_is_locked(&dev->struct_mutex)); | ||
482 | |||
483 | size = PAGE_ALIGN(size); | ||
484 | |||
485 | switch (flags & MSM_BO_CACHE_MASK) { | ||
486 | case MSM_BO_UNCACHED: | ||
487 | case MSM_BO_CACHED: | ||
488 | case MSM_BO_WC: | ||
489 | break; | ||
490 | default: | ||
491 | dev_err(dev->dev, "invalid cache flag: %x\n", | ||
492 | (flags & MSM_BO_CACHE_MASK)); | ||
493 | ret = -EINVAL; | ||
494 | goto fail; | ||
495 | } | ||
496 | |||
497 | msm_obj = kzalloc(sizeof(*msm_obj), GFP_KERNEL); | ||
498 | if (!msm_obj) { | ||
499 | ret = -ENOMEM; | ||
500 | goto fail; | ||
501 | } | ||
502 | |||
503 | obj = &msm_obj->base; | ||
504 | |||
505 | ret = drm_gem_object_init(dev, obj, size); | ||
506 | if (ret) | ||
507 | goto fail; | ||
508 | |||
509 | msm_obj->flags = flags; | ||
510 | |||
511 | |||
512 | list_add_tail(&msm_obj->mm_list, &priv->inactive_list); | ||
513 | |||
514 | return obj; | ||
515 | |||
516 | fail: | ||
517 | if (obj) | ||
518 | drm_gem_object_unreference_unlocked(obj); | ||
519 | |||
520 | return ERR_PTR(ret); | ||
521 | } | ||
diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h new file mode 100644 index 000000000000..fcafd1965151 --- /dev/null +++ b/drivers/gpu/drm/msm/msm_gem.h | |||
@@ -0,0 +1,41 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #ifndef __MSM_GEM_H__ | ||
19 | #define __MSM_GEM_H__ | ||
20 | |||
21 | #include "msm_drv.h" | ||
22 | |||
23 | struct msm_gem_object { | ||
24 | struct drm_gem_object base; | ||
25 | |||
26 | uint32_t flags; | ||
27 | |||
28 | struct list_head mm_list; | ||
29 | |||
30 | struct page **pages; | ||
31 | struct sg_table *sgt; | ||
32 | void *vaddr; | ||
33 | |||
34 | struct { | ||
35 | // XXX | ||
36 | uint32_t iova; | ||
37 | } domain[NUM_DOMAINS]; | ||
38 | }; | ||
39 | #define to_msm_bo(x) container_of(x, struct msm_gem_object, base) | ||
40 | |||
41 | #endif /* __MSM_GEM_H__ */ | ||