1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // Copyright 2018 IBM Corporation |
3 | |
4 | #include <linux/clk.h> |
5 | #include <linux/dma-mapping.h> |
6 | #include <linux/irq.h> |
7 | #include <linux/mfd/syscon.h> |
8 | #include <linux/module.h> |
9 | #include <linux/mod_devicetable.h> |
10 | #include <linux/of_reserved_mem.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/property.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/reset.h> |
15 | |
16 | #include <drm/drm_atomic_helper.h> |
17 | #include <drm/drm_device.h> |
18 | #include <drm/drm_fbdev_dma.h> |
19 | #include <drm/drm_gem_dma_helper.h> |
20 | #include <drm/drm_gem_framebuffer_helper.h> |
21 | #include <drm/drm_module.h> |
22 | #include <drm/drm_probe_helper.h> |
23 | #include <drm/drm_simple_kms_helper.h> |
24 | #include <drm/drm_vblank.h> |
25 | #include <drm/drm_drv.h> |
26 | |
27 | #include "aspeed_gfx.h" |
28 | |
29 | /** |
30 | * DOC: ASPEED GFX Driver |
31 | * |
32 | * This driver is for the ASPEED BMC SoC's 'GFX' display hardware, also called |
33 | * the 'SOC Display Controller' in the datasheet. This driver runs on the ARM |
34 | * based BMC systems, unlike the ast driver which runs on a host CPU and is for |
35 | * a PCIe graphics device. |
36 | * |
37 | * The AST2500 supports a total of 3 output paths: |
38 | * |
39 | * 1. VGA output, the output target can choose either or both to the DAC |
40 | * or DVO interface. |
41 | * |
42 | * 2. Graphics CRT output, the output target can choose either or both to |
43 | * the DAC or DVO interface. |
44 | * |
45 | * 3. Video input from DVO, the video input can be used for video engine |
46 | * capture or DAC display output. |
47 | * |
48 | * Output options are selected in SCU2C. |
49 | * |
50 | * The "VGA mode" device is the PCI attached controller. The "Graphics CRT" |
51 | * is the ARM's internal display controller. |
52 | * |
53 | * The driver only supports a simple configuration consisting of a 40MHz |
54 | * pixel clock, fixed by hardware limitations, and the VGA output path. |
55 | * |
56 | * The driver was written with the 'AST2500 Software Programming Guide' v17, |
57 | * which is available under NDA from ASPEED. |
58 | */ |
59 | |
60 | struct aspeed_gfx_config { |
61 | u32 dac_reg; /* DAC register in SCU */ |
62 | u32 int_clear_reg; /* Interrupt clear register */ |
63 | u32 vga_scratch_reg; /* VGA scratch register in SCU */ |
64 | u32 throd_val; /* Default Threshold Seting */ |
65 | u32 scan_line_max; /* Max memory size of one scan line */ |
66 | }; |
67 | |
68 | static const struct aspeed_gfx_config ast2400_config = { |
69 | .dac_reg = 0x2c, |
70 | .int_clear_reg = 0x60, |
71 | .vga_scratch_reg = 0x50, |
72 | .throd_val = CRT_THROD_LOW(0x1e) | CRT_THROD_HIGH(0x12), |
73 | .scan_line_max = 64, |
74 | }; |
75 | |
76 | static const struct aspeed_gfx_config ast2500_config = { |
77 | .dac_reg = 0x2c, |
78 | .int_clear_reg = 0x60, |
79 | .vga_scratch_reg = 0x50, |
80 | .throd_val = CRT_THROD_LOW(0x24) | CRT_THROD_HIGH(0x3c), |
81 | .scan_line_max = 128, |
82 | }; |
83 | |
84 | static const struct aspeed_gfx_config ast2600_config = { |
85 | .dac_reg = 0xc0, |
86 | .int_clear_reg = 0x68, |
87 | .vga_scratch_reg = 0x50, |
88 | .throd_val = CRT_THROD_LOW(0x50) | CRT_THROD_HIGH(0x70), |
89 | .scan_line_max = 128, |
90 | }; |
91 | |
92 | static const struct of_device_id aspeed_gfx_match[] = { |
93 | { .compatible = "aspeed,ast2400-gfx" , .data = &ast2400_config }, |
94 | { .compatible = "aspeed,ast2500-gfx" , .data = &ast2500_config }, |
95 | { .compatible = "aspeed,ast2600-gfx" , .data = &ast2600_config }, |
96 | { }, |
97 | }; |
98 | MODULE_DEVICE_TABLE(of, aspeed_gfx_match); |
99 | |
100 | static const struct drm_mode_config_funcs aspeed_gfx_mode_config_funcs = { |
101 | .fb_create = drm_gem_fb_create, |
102 | .atomic_check = drm_atomic_helper_check, |
103 | .atomic_commit = drm_atomic_helper_commit, |
104 | }; |
105 | |
106 | static int aspeed_gfx_setup_mode_config(struct drm_device *drm) |
107 | { |
108 | int ret; |
109 | |
110 | ret = drmm_mode_config_init(dev: drm); |
111 | if (ret) |
112 | return ret; |
113 | |
114 | drm->mode_config.min_width = 0; |
115 | drm->mode_config.min_height = 0; |
116 | drm->mode_config.max_width = 800; |
117 | drm->mode_config.max_height = 600; |
118 | drm->mode_config.funcs = &aspeed_gfx_mode_config_funcs; |
119 | |
120 | return ret; |
121 | } |
122 | |
123 | static irqreturn_t aspeed_gfx_irq_handler(int irq, void *data) |
124 | { |
125 | struct drm_device *drm = data; |
126 | struct aspeed_gfx *priv = to_aspeed_gfx(drm); |
127 | u32 reg; |
128 | |
129 | reg = readl(addr: priv->base + CRT_CTRL1); |
130 | |
131 | if (reg & CRT_CTRL_VERTICAL_INTR_STS) { |
132 | drm_crtc_handle_vblank(crtc: &priv->pipe.crtc); |
133 | writel(val: reg, addr: priv->base + priv->int_clr_reg); |
134 | return IRQ_HANDLED; |
135 | } |
136 | |
137 | return IRQ_NONE; |
138 | } |
139 | |
140 | static int aspeed_gfx_load(struct drm_device *drm) |
141 | { |
142 | struct platform_device *pdev = to_platform_device(drm->dev); |
143 | struct aspeed_gfx *priv = to_aspeed_gfx(drm); |
144 | struct device_node *np = pdev->dev.of_node; |
145 | const struct aspeed_gfx_config *config; |
146 | struct resource *res; |
147 | int ret; |
148 | |
149 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
150 | priv->base = devm_ioremap_resource(dev: drm->dev, res); |
151 | if (IS_ERR(ptr: priv->base)) |
152 | return PTR_ERR(ptr: priv->base); |
153 | |
154 | config = device_get_match_data(dev: &pdev->dev); |
155 | if (!config) |
156 | return -EINVAL; |
157 | |
158 | priv->dac_reg = config->dac_reg; |
159 | priv->int_clr_reg = config->int_clear_reg; |
160 | priv->vga_scratch_reg = config->vga_scratch_reg; |
161 | priv->throd_val = config->throd_val; |
162 | priv->scan_line_max = config->scan_line_max; |
163 | |
164 | priv->scu = syscon_regmap_lookup_by_phandle(np, property: "syscon" ); |
165 | if (IS_ERR(ptr: priv->scu)) { |
166 | priv->scu = syscon_regmap_lookup_by_compatible(s: "aspeed,ast2500-scu" ); |
167 | if (IS_ERR(ptr: priv->scu)) { |
168 | dev_err(&pdev->dev, "failed to find SCU regmap\n" ); |
169 | return PTR_ERR(ptr: priv->scu); |
170 | } |
171 | } |
172 | |
173 | ret = of_reserved_mem_device_init(dev: drm->dev); |
174 | if (ret) { |
175 | dev_err(&pdev->dev, |
176 | "failed to initialize reserved mem: %d\n" , ret); |
177 | return ret; |
178 | } |
179 | |
180 | ret = dma_set_mask_and_coherent(dev: drm->dev, DMA_BIT_MASK(32)); |
181 | if (ret) { |
182 | dev_err(&pdev->dev, "failed to set DMA mask: %d\n" , ret); |
183 | return ret; |
184 | } |
185 | |
186 | priv->rst = devm_reset_control_get_exclusive(dev: &pdev->dev, NULL); |
187 | if (IS_ERR(ptr: priv->rst)) { |
188 | dev_err(&pdev->dev, |
189 | "missing or invalid reset controller device tree entry" ); |
190 | return PTR_ERR(ptr: priv->rst); |
191 | } |
192 | reset_control_deassert(rstc: priv->rst); |
193 | |
194 | priv->clk = devm_clk_get(dev: drm->dev, NULL); |
195 | if (IS_ERR(ptr: priv->clk)) { |
196 | dev_err(&pdev->dev, |
197 | "missing or invalid clk device tree entry" ); |
198 | return PTR_ERR(ptr: priv->clk); |
199 | } |
200 | clk_prepare_enable(clk: priv->clk); |
201 | |
202 | /* Sanitize control registers */ |
203 | writel(val: 0, addr: priv->base + CRT_CTRL1); |
204 | writel(val: 0, addr: priv->base + CRT_CTRL2); |
205 | |
206 | ret = aspeed_gfx_setup_mode_config(drm); |
207 | if (ret < 0) |
208 | return ret; |
209 | |
210 | ret = drm_vblank_init(dev: drm, num_crtcs: 1); |
211 | if (ret < 0) { |
212 | dev_err(drm->dev, "Failed to initialise vblank\n" ); |
213 | return ret; |
214 | } |
215 | |
216 | ret = aspeed_gfx_create_output(drm); |
217 | if (ret < 0) { |
218 | dev_err(drm->dev, "Failed to create outputs\n" ); |
219 | return ret; |
220 | } |
221 | |
222 | ret = aspeed_gfx_create_pipe(drm); |
223 | if (ret < 0) { |
224 | dev_err(drm->dev, "Cannot setup simple display pipe\n" ); |
225 | return ret; |
226 | } |
227 | |
228 | ret = devm_request_irq(dev: drm->dev, irq: platform_get_irq(pdev, 0), |
229 | handler: aspeed_gfx_irq_handler, irqflags: 0, devname: "aspeed gfx" , dev_id: drm); |
230 | if (ret < 0) { |
231 | dev_err(drm->dev, "Failed to install IRQ handler\n" ); |
232 | return ret; |
233 | } |
234 | |
235 | drm_mode_config_reset(dev: drm); |
236 | |
237 | return 0; |
238 | } |
239 | |
240 | static void aspeed_gfx_unload(struct drm_device *drm) |
241 | { |
242 | drm_kms_helper_poll_fini(dev: drm); |
243 | } |
244 | |
245 | DEFINE_DRM_GEM_DMA_FOPS(fops); |
246 | |
247 | static const struct drm_driver aspeed_gfx_driver = { |
248 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, |
249 | DRM_GEM_DMA_DRIVER_OPS, |
250 | .fops = &fops, |
251 | .name = "aspeed-gfx-drm" , |
252 | .desc = "ASPEED GFX DRM" , |
253 | .date = "20180319" , |
254 | .major = 1, |
255 | .minor = 0, |
256 | }; |
257 | |
258 | static ssize_t dac_mux_store(struct device *dev, struct device_attribute *attr, |
259 | const char *buf, size_t count) |
260 | { |
261 | struct aspeed_gfx *priv = dev_get_drvdata(dev); |
262 | u32 val; |
263 | int rc; |
264 | |
265 | rc = kstrtou32(s: buf, base: 0, res: &val); |
266 | if (rc) |
267 | return rc; |
268 | |
269 | if (val > 3) |
270 | return -EINVAL; |
271 | |
272 | rc = regmap_update_bits(map: priv->scu, reg: priv->dac_reg, mask: 0x30000, val: val << 16); |
273 | if (rc < 0) |
274 | return 0; |
275 | |
276 | return count; |
277 | } |
278 | |
279 | static ssize_t dac_mux_show(struct device *dev, struct device_attribute *attr, char *buf) |
280 | { |
281 | struct aspeed_gfx *priv = dev_get_drvdata(dev); |
282 | u32 reg; |
283 | int rc; |
284 | |
285 | rc = regmap_read(map: priv->scu, reg: priv->dac_reg, val: ®); |
286 | if (rc) |
287 | return rc; |
288 | |
289 | return sprintf(buf, fmt: "%u\n" , (reg >> 16) & 0x3); |
290 | } |
291 | static DEVICE_ATTR_RW(dac_mux); |
292 | |
293 | static ssize_t |
294 | vga_pw_show(struct device *dev, struct device_attribute *attr, char *buf) |
295 | { |
296 | struct aspeed_gfx *priv = dev_get_drvdata(dev); |
297 | u32 reg; |
298 | int rc; |
299 | |
300 | rc = regmap_read(map: priv->scu, reg: priv->vga_scratch_reg, val: ®); |
301 | if (rc) |
302 | return rc; |
303 | |
304 | return sprintf(buf, fmt: "%u\n" , reg); |
305 | } |
306 | static DEVICE_ATTR_RO(vga_pw); |
307 | |
308 | static struct attribute *aspeed_sysfs_entries[] = { |
309 | &dev_attr_vga_pw.attr, |
310 | &dev_attr_dac_mux.attr, |
311 | NULL, |
312 | }; |
313 | |
314 | static struct attribute_group aspeed_sysfs_attr_group = { |
315 | .attrs = aspeed_sysfs_entries, |
316 | }; |
317 | |
318 | static int aspeed_gfx_probe(struct platform_device *pdev) |
319 | { |
320 | struct aspeed_gfx *priv; |
321 | int ret; |
322 | |
323 | priv = devm_drm_dev_alloc(&pdev->dev, &aspeed_gfx_driver, |
324 | struct aspeed_gfx, drm); |
325 | if (IS_ERR(ptr: priv)) |
326 | return PTR_ERR(ptr: priv); |
327 | |
328 | ret = aspeed_gfx_load(drm: &priv->drm); |
329 | if (ret) |
330 | return ret; |
331 | |
332 | platform_set_drvdata(pdev, data: priv); |
333 | |
334 | ret = sysfs_create_group(kobj: &pdev->dev.kobj, grp: &aspeed_sysfs_attr_group); |
335 | if (ret) |
336 | return ret; |
337 | |
338 | ret = drm_dev_register(dev: &priv->drm, flags: 0); |
339 | if (ret) |
340 | goto err_unload; |
341 | |
342 | drm_fbdev_dma_setup(dev: &priv->drm, preferred_bpp: 32); |
343 | return 0; |
344 | |
345 | err_unload: |
346 | sysfs_remove_group(kobj: &pdev->dev.kobj, grp: &aspeed_sysfs_attr_group); |
347 | aspeed_gfx_unload(drm: &priv->drm); |
348 | |
349 | return ret; |
350 | } |
351 | |
352 | static void aspeed_gfx_remove(struct platform_device *pdev) |
353 | { |
354 | struct drm_device *drm = platform_get_drvdata(pdev); |
355 | |
356 | sysfs_remove_group(kobj: &pdev->dev.kobj, grp: &aspeed_sysfs_attr_group); |
357 | drm_dev_unregister(dev: drm); |
358 | aspeed_gfx_unload(drm); |
359 | drm_atomic_helper_shutdown(dev: drm); |
360 | } |
361 | |
362 | static void aspeed_gfx_shutdown(struct platform_device *pdev) |
363 | { |
364 | drm_atomic_helper_shutdown(dev: platform_get_drvdata(pdev)); |
365 | } |
366 | |
367 | static struct platform_driver aspeed_gfx_platform_driver = { |
368 | .probe = aspeed_gfx_probe, |
369 | .remove_new = aspeed_gfx_remove, |
370 | .shutdown = aspeed_gfx_shutdown, |
371 | .driver = { |
372 | .name = "aspeed_gfx" , |
373 | .of_match_table = aspeed_gfx_match, |
374 | }, |
375 | }; |
376 | |
377 | drm_module_platform_driver(aspeed_gfx_platform_driver); |
378 | |
379 | MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>" ); |
380 | MODULE_DESCRIPTION("ASPEED BMC DRM/KMS driver" ); |
381 | MODULE_LICENSE("GPL" ); |
382 | |