1 | /* |
2 | * Copyright (C) 2013-2015 ARM Limited |
3 | * Author: Liviu Dudau <Liviu.Dudau@arm.com> |
4 | * |
5 | * This file is subject to the terms and conditions of the GNU General Public |
6 | * License. See the file COPYING in the main directory of this archive |
7 | * for more details. |
8 | * |
9 | * ARM HDLCD Driver |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/spinlock.h> |
14 | #include <linux/clk.h> |
15 | #include <linux/component.h> |
16 | #include <linux/console.h> |
17 | #include <linux/dma-mapping.h> |
18 | #include <linux/list.h> |
19 | #include <linux/of_graph.h> |
20 | #include <linux/of_reserved_mem.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/pm_runtime.h> |
23 | |
24 | #include <drm/drm_aperture.h> |
25 | #include <drm/drm_atomic_helper.h> |
26 | #include <drm/drm_crtc.h> |
27 | #include <drm/drm_debugfs.h> |
28 | #include <drm/drm_drv.h> |
29 | #include <drm/drm_fbdev_dma.h> |
30 | #include <drm/drm_gem_dma_helper.h> |
31 | #include <drm/drm_gem_framebuffer_helper.h> |
32 | #include <drm/drm_modeset_helper.h> |
33 | #include <drm/drm_module.h> |
34 | #include <drm/drm_of.h> |
35 | #include <drm/drm_probe_helper.h> |
36 | #include <drm/drm_vblank.h> |
37 | |
38 | #include "hdlcd_drv.h" |
39 | #include "hdlcd_regs.h" |
40 | |
41 | static irqreturn_t hdlcd_irq(int irq, void *arg) |
42 | { |
43 | struct hdlcd_drm_private *hdlcd = arg; |
44 | unsigned long irq_status; |
45 | |
46 | irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS); |
47 | |
48 | #ifdef CONFIG_DEBUG_FS |
49 | if (irq_status & HDLCD_INTERRUPT_UNDERRUN) |
50 | atomic_inc(v: &hdlcd->buffer_underrun_count); |
51 | |
52 | if (irq_status & HDLCD_INTERRUPT_DMA_END) |
53 | atomic_inc(v: &hdlcd->dma_end_count); |
54 | |
55 | if (irq_status & HDLCD_INTERRUPT_BUS_ERROR) |
56 | atomic_inc(v: &hdlcd->bus_error_count); |
57 | |
58 | if (irq_status & HDLCD_INTERRUPT_VSYNC) |
59 | atomic_inc(v: &hdlcd->vsync_count); |
60 | |
61 | #endif |
62 | if (irq_status & HDLCD_INTERRUPT_VSYNC) |
63 | drm_crtc_handle_vblank(crtc: &hdlcd->crtc); |
64 | |
65 | /* acknowledge interrupt(s) */ |
66 | hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, value: irq_status); |
67 | |
68 | return IRQ_HANDLED; |
69 | } |
70 | |
71 | static int hdlcd_irq_install(struct hdlcd_drm_private *hdlcd) |
72 | { |
73 | int ret; |
74 | |
75 | /* Ensure interrupts are disabled */ |
76 | hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, value: 0); |
77 | hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, value: ~0); |
78 | |
79 | ret = request_irq(irq: hdlcd->irq, handler: hdlcd_irq, flags: 0, name: "hdlcd" , dev: hdlcd); |
80 | if (ret) |
81 | return ret; |
82 | |
83 | #ifdef CONFIG_DEBUG_FS |
84 | /* enable debug interrupts */ |
85 | hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, HDLCD_DEBUG_INT_MASK); |
86 | #endif |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static void hdlcd_irq_uninstall(struct hdlcd_drm_private *hdlcd) |
92 | { |
93 | /* disable all the interrupts that we might have enabled */ |
94 | hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, value: 0); |
95 | |
96 | free_irq(hdlcd->irq, hdlcd); |
97 | } |
98 | |
99 | static int hdlcd_load(struct drm_device *drm, unsigned long flags) |
100 | { |
101 | struct hdlcd_drm_private *hdlcd = drm_to_hdlcd_priv(drm); |
102 | struct platform_device *pdev = to_platform_device(drm->dev); |
103 | u32 version; |
104 | int ret; |
105 | |
106 | hdlcd->clk = devm_clk_get(dev: drm->dev, id: "pxlclk" ); |
107 | if (IS_ERR(ptr: hdlcd->clk)) |
108 | return PTR_ERR(ptr: hdlcd->clk); |
109 | |
110 | #ifdef CONFIG_DEBUG_FS |
111 | atomic_set(v: &hdlcd->buffer_underrun_count, i: 0); |
112 | atomic_set(v: &hdlcd->bus_error_count, i: 0); |
113 | atomic_set(v: &hdlcd->vsync_count, i: 0); |
114 | atomic_set(v: &hdlcd->dma_end_count, i: 0); |
115 | #endif |
116 | |
117 | hdlcd->mmio = devm_platform_ioremap_resource(pdev, index: 0); |
118 | if (IS_ERR(ptr: hdlcd->mmio)) { |
119 | DRM_ERROR("failed to map control registers area\n" ); |
120 | ret = PTR_ERR(ptr: hdlcd->mmio); |
121 | hdlcd->mmio = NULL; |
122 | return ret; |
123 | } |
124 | |
125 | version = hdlcd_read(hdlcd, HDLCD_REG_VERSION); |
126 | if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) { |
127 | DRM_ERROR("unknown product id: 0x%x\n" , version); |
128 | return -EINVAL; |
129 | } |
130 | DRM_INFO("found ARM HDLCD version r%dp%d\n" , |
131 | (version & HDLCD_VERSION_MAJOR_MASK) >> 8, |
132 | version & HDLCD_VERSION_MINOR_MASK); |
133 | |
134 | /* Get the optional framebuffer memory resource */ |
135 | ret = of_reserved_mem_device_init(dev: drm->dev); |
136 | if (ret && ret != -ENODEV) |
137 | return ret; |
138 | |
139 | ret = dma_set_mask_and_coherent(dev: drm->dev, DMA_BIT_MASK(32)); |
140 | if (ret) |
141 | goto setup_fail; |
142 | |
143 | ret = hdlcd_setup_crtc(dev: drm); |
144 | if (ret < 0) { |
145 | DRM_ERROR("failed to create crtc\n" ); |
146 | goto setup_fail; |
147 | } |
148 | |
149 | ret = platform_get_irq(pdev, 0); |
150 | if (ret < 0) |
151 | goto irq_fail; |
152 | hdlcd->irq = ret; |
153 | |
154 | ret = hdlcd_irq_install(hdlcd); |
155 | if (ret < 0) { |
156 | DRM_ERROR("failed to install IRQ handler\n" ); |
157 | goto irq_fail; |
158 | } |
159 | |
160 | return 0; |
161 | |
162 | irq_fail: |
163 | drm_crtc_cleanup(crtc: &hdlcd->crtc); |
164 | setup_fail: |
165 | of_reserved_mem_device_release(dev: drm->dev); |
166 | |
167 | return ret; |
168 | } |
169 | |
170 | static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { |
171 | .fb_create = drm_gem_fb_create, |
172 | .atomic_check = drm_atomic_helper_check, |
173 | .atomic_commit = drm_atomic_helper_commit, |
174 | }; |
175 | |
176 | static int hdlcd_setup_mode_config(struct drm_device *drm) |
177 | { |
178 | int ret; |
179 | |
180 | ret = drmm_mode_config_init(dev: drm); |
181 | if (ret) |
182 | return ret; |
183 | |
184 | drm->mode_config.min_width = 0; |
185 | drm->mode_config.min_height = 0; |
186 | drm->mode_config.max_width = HDLCD_MAX_XRES; |
187 | drm->mode_config.max_height = HDLCD_MAX_YRES; |
188 | drm->mode_config.funcs = &hdlcd_mode_config_funcs; |
189 | |
190 | return 0; |
191 | } |
192 | |
193 | #ifdef CONFIG_DEBUG_FS |
194 | static int hdlcd_show_underrun_count(struct seq_file *m, void *arg) |
195 | { |
196 | struct drm_debugfs_entry *entry = m->private; |
197 | struct drm_device *drm = entry->dev; |
198 | struct hdlcd_drm_private *hdlcd = drm_to_hdlcd_priv(drm); |
199 | |
200 | seq_printf(m, fmt: "underrun : %d\n" , atomic_read(v: &hdlcd->buffer_underrun_count)); |
201 | seq_printf(m, fmt: "dma_end : %d\n" , atomic_read(v: &hdlcd->dma_end_count)); |
202 | seq_printf(m, fmt: "bus_error: %d\n" , atomic_read(v: &hdlcd->bus_error_count)); |
203 | seq_printf(m, fmt: "vsync : %d\n" , atomic_read(v: &hdlcd->vsync_count)); |
204 | return 0; |
205 | } |
206 | |
207 | static int hdlcd_show_pxlclock(struct seq_file *m, void *arg) |
208 | { |
209 | struct drm_debugfs_entry *entry = m->private; |
210 | struct drm_device *drm = entry->dev; |
211 | struct hdlcd_drm_private *hdlcd = drm_to_hdlcd_priv(drm); |
212 | unsigned long clkrate = clk_get_rate(clk: hdlcd->clk); |
213 | unsigned long mode_clock = hdlcd->crtc.mode.crtc_clock * 1000; |
214 | |
215 | seq_printf(m, fmt: "hw : %lu\n" , clkrate); |
216 | seq_printf(m, fmt: "mode: %lu\n" , mode_clock); |
217 | return 0; |
218 | } |
219 | |
220 | static struct drm_debugfs_info hdlcd_debugfs_list[] = { |
221 | { "interrupt_count" , hdlcd_show_underrun_count, 0 }, |
222 | { "clocks" , hdlcd_show_pxlclock, 0 }, |
223 | }; |
224 | #endif |
225 | |
226 | DEFINE_DRM_GEM_DMA_FOPS(fops); |
227 | |
228 | static const struct drm_driver hdlcd_driver = { |
229 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, |
230 | DRM_GEM_DMA_DRIVER_OPS, |
231 | .fops = &fops, |
232 | .name = "hdlcd" , |
233 | .desc = "ARM HDLCD Controller DRM" , |
234 | .date = "20151021" , |
235 | .major = 1, |
236 | .minor = 0, |
237 | }; |
238 | |
239 | static int hdlcd_drm_bind(struct device *dev) |
240 | { |
241 | struct drm_device *drm; |
242 | struct hdlcd_drm_private *hdlcd; |
243 | int ret; |
244 | |
245 | hdlcd = devm_drm_dev_alloc(dev, &hdlcd_driver, typeof(*hdlcd), base); |
246 | if (IS_ERR(ptr: hdlcd)) |
247 | return PTR_ERR(ptr: hdlcd); |
248 | |
249 | drm = &hdlcd->base; |
250 | |
251 | dev_set_drvdata(dev, data: drm); |
252 | |
253 | ret = hdlcd_setup_mode_config(drm); |
254 | if (ret) |
255 | goto err_free; |
256 | |
257 | ret = hdlcd_load(drm, flags: 0); |
258 | if (ret) |
259 | goto err_free; |
260 | |
261 | /* Set the CRTC's port so that the encoder component can find it */ |
262 | hdlcd->crtc.port = of_graph_get_port_by_id(node: dev->of_node, id: 0); |
263 | |
264 | ret = component_bind_all(parent: dev, data: drm); |
265 | if (ret) { |
266 | DRM_ERROR("Failed to bind all components\n" ); |
267 | goto err_unload; |
268 | } |
269 | |
270 | ret = pm_runtime_set_active(dev); |
271 | if (ret) |
272 | goto err_pm_active; |
273 | |
274 | pm_runtime_enable(dev); |
275 | |
276 | ret = drm_vblank_init(dev: drm, num_crtcs: drm->mode_config.num_crtc); |
277 | if (ret < 0) { |
278 | DRM_ERROR("failed to initialise vblank\n" ); |
279 | goto err_vblank; |
280 | } |
281 | |
282 | /* |
283 | * If EFI left us running, take over from simple framebuffer |
284 | * drivers. Read HDLCD_REG_COMMAND to see if we are enabled. |
285 | */ |
286 | if (hdlcd_read(hdlcd, HDLCD_REG_COMMAND)) { |
287 | hdlcd_write(hdlcd, HDLCD_REG_COMMAND, value: 0); |
288 | drm_aperture_remove_framebuffers(req_driver: &hdlcd_driver); |
289 | } |
290 | |
291 | drm_mode_config_reset(dev: drm); |
292 | drm_kms_helper_poll_init(dev: drm); |
293 | |
294 | #ifdef CONFIG_DEBUG_FS |
295 | drm_debugfs_add_files(dev: drm, files: hdlcd_debugfs_list, ARRAY_SIZE(hdlcd_debugfs_list)); |
296 | #endif |
297 | |
298 | ret = drm_dev_register(dev: drm, flags: 0); |
299 | if (ret) |
300 | goto err_register; |
301 | |
302 | drm_fbdev_dma_setup(dev: drm, preferred_bpp: 32); |
303 | |
304 | return 0; |
305 | |
306 | err_register: |
307 | drm_kms_helper_poll_fini(dev: drm); |
308 | err_vblank: |
309 | pm_runtime_disable(dev: drm->dev); |
310 | err_pm_active: |
311 | drm_atomic_helper_shutdown(dev: drm); |
312 | component_unbind_all(parent: dev, data: drm); |
313 | err_unload: |
314 | of_node_put(node: hdlcd->crtc.port); |
315 | hdlcd->crtc.port = NULL; |
316 | hdlcd_irq_uninstall(hdlcd); |
317 | of_reserved_mem_device_release(dev: drm->dev); |
318 | err_free: |
319 | dev_set_drvdata(dev, NULL); |
320 | return ret; |
321 | } |
322 | |
323 | static void hdlcd_drm_unbind(struct device *dev) |
324 | { |
325 | struct drm_device *drm = dev_get_drvdata(dev); |
326 | struct hdlcd_drm_private *hdlcd = drm_to_hdlcd_priv(drm); |
327 | |
328 | drm_dev_unregister(dev: drm); |
329 | drm_kms_helper_poll_fini(dev: drm); |
330 | component_unbind_all(parent: dev, data: drm); |
331 | of_node_put(node: hdlcd->crtc.port); |
332 | hdlcd->crtc.port = NULL; |
333 | pm_runtime_get_sync(dev); |
334 | drm_atomic_helper_shutdown(dev: drm); |
335 | hdlcd_irq_uninstall(hdlcd); |
336 | pm_runtime_put(dev); |
337 | if (pm_runtime_enabled(dev)) |
338 | pm_runtime_disable(dev); |
339 | of_reserved_mem_device_release(dev); |
340 | dev_set_drvdata(dev, NULL); |
341 | } |
342 | |
343 | static const struct component_master_ops hdlcd_master_ops = { |
344 | .bind = hdlcd_drm_bind, |
345 | .unbind = hdlcd_drm_unbind, |
346 | }; |
347 | |
348 | static int compare_dev(struct device *dev, void *data) |
349 | { |
350 | return dev->of_node == data; |
351 | } |
352 | |
353 | static int hdlcd_probe(struct platform_device *pdev) |
354 | { |
355 | struct device_node *port; |
356 | struct component_match *match = NULL; |
357 | |
358 | /* there is only one output port inside each device, find it */ |
359 | port = of_graph_get_remote_node(node: pdev->dev.of_node, port: 0, endpoint: 0); |
360 | if (!port) |
361 | return -ENODEV; |
362 | |
363 | drm_of_component_match_add(master: &pdev->dev, matchptr: &match, compare: compare_dev, node: port); |
364 | of_node_put(node: port); |
365 | |
366 | return component_master_add_with_match(&pdev->dev, &hdlcd_master_ops, |
367 | match); |
368 | } |
369 | |
370 | static void hdlcd_remove(struct platform_device *pdev) |
371 | { |
372 | component_master_del(&pdev->dev, &hdlcd_master_ops); |
373 | } |
374 | |
375 | static void hdlcd_shutdown(struct platform_device *pdev) |
376 | { |
377 | drm_atomic_helper_shutdown(dev: platform_get_drvdata(pdev)); |
378 | } |
379 | |
380 | static const struct of_device_id hdlcd_of_match[] = { |
381 | { .compatible = "arm,hdlcd" }, |
382 | {}, |
383 | }; |
384 | MODULE_DEVICE_TABLE(of, hdlcd_of_match); |
385 | |
386 | static int __maybe_unused hdlcd_pm_suspend(struct device *dev) |
387 | { |
388 | struct drm_device *drm = dev_get_drvdata(dev); |
389 | |
390 | return drm_mode_config_helper_suspend(dev: drm); |
391 | } |
392 | |
393 | static int __maybe_unused hdlcd_pm_resume(struct device *dev) |
394 | { |
395 | struct drm_device *drm = dev_get_drvdata(dev); |
396 | |
397 | drm_mode_config_helper_resume(dev: drm); |
398 | |
399 | return 0; |
400 | } |
401 | |
402 | static SIMPLE_DEV_PM_OPS(hdlcd_pm_ops, hdlcd_pm_suspend, hdlcd_pm_resume); |
403 | |
404 | static struct platform_driver hdlcd_platform_driver = { |
405 | .probe = hdlcd_probe, |
406 | .remove_new = hdlcd_remove, |
407 | .shutdown = hdlcd_shutdown, |
408 | .driver = { |
409 | .name = "hdlcd" , |
410 | .pm = &hdlcd_pm_ops, |
411 | .of_match_table = hdlcd_of_match, |
412 | }, |
413 | }; |
414 | |
415 | drm_module_platform_driver(hdlcd_platform_driver); |
416 | |
417 | MODULE_AUTHOR("Liviu Dudau" ); |
418 | MODULE_DESCRIPTION("ARM HDLCD DRM driver" ); |
419 | MODULE_LICENSE("GPL v2" ); |
420 | |