1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. |
4 | * Author: James.Qian.Wang <james.qian.wang@arm.com> |
5 | * |
6 | */ |
7 | #include <linux/io.h> |
8 | #include <linux/iommu.h> |
9 | #include <linux/of.h> |
10 | #include <linux/of_graph.h> |
11 | #include <linux/of_reserved_mem.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/pm_runtime.h> |
14 | #include <linux/dma-mapping.h> |
15 | #ifdef CONFIG_DEBUG_FS |
16 | #include <linux/debugfs.h> |
17 | #include <linux/seq_file.h> |
18 | #endif |
19 | |
20 | #include <drm/drm_print.h> |
21 | |
22 | #include "komeda_dev.h" |
23 | |
24 | static int komeda_register_show(struct seq_file *sf, void *x) |
25 | { |
26 | struct komeda_dev *mdev = sf->private; |
27 | int i; |
28 | |
29 | seq_puts(m: sf, s: "\n====== Komeda register dump =========\n" ); |
30 | |
31 | pm_runtime_get_sync(dev: mdev->dev); |
32 | |
33 | if (mdev->funcs->dump_register) |
34 | mdev->funcs->dump_register(mdev, sf); |
35 | |
36 | for (i = 0; i < mdev->n_pipelines; i++) |
37 | komeda_pipeline_dump_register(pipe: mdev->pipelines[i], sf); |
38 | |
39 | pm_runtime_put(dev: mdev->dev); |
40 | |
41 | return 0; |
42 | } |
43 | |
44 | DEFINE_SHOW_ATTRIBUTE(komeda_register); |
45 | |
46 | #ifdef CONFIG_DEBUG_FS |
47 | static void komeda_debugfs_init(struct komeda_dev *mdev) |
48 | { |
49 | if (!debugfs_initialized()) |
50 | return; |
51 | |
52 | mdev->debugfs_root = debugfs_create_dir(name: "komeda" , NULL); |
53 | debugfs_create_file(name: "register" , mode: 0444, parent: mdev->debugfs_root, |
54 | data: mdev, fops: &komeda_register_fops); |
55 | debugfs_create_x16(name: "err_verbosity" , mode: 0664, parent: mdev->debugfs_root, |
56 | value: &mdev->err_verbosity); |
57 | } |
58 | #endif |
59 | |
60 | static ssize_t |
61 | core_id_show(struct device *dev, struct device_attribute *attr, char *buf) |
62 | { |
63 | struct komeda_dev *mdev = dev_to_mdev(dev); |
64 | |
65 | return sysfs_emit(buf, fmt: "0x%08x\n" , mdev->chip.core_id); |
66 | } |
67 | static DEVICE_ATTR_RO(core_id); |
68 | |
69 | static ssize_t |
70 | config_id_show(struct device *dev, struct device_attribute *attr, char *buf) |
71 | { |
72 | struct komeda_dev *mdev = dev_to_mdev(dev); |
73 | struct komeda_pipeline *pipe = mdev->pipelines[0]; |
74 | union komeda_config_id config_id; |
75 | int i; |
76 | |
77 | memset(&config_id, 0, sizeof(config_id)); |
78 | |
79 | config_id.max_line_sz = pipe->layers[0]->hsize_in.end; |
80 | config_id.n_pipelines = mdev->n_pipelines; |
81 | config_id.n_scalers = pipe->n_scalers; |
82 | config_id.n_layers = pipe->n_layers; |
83 | config_id.n_richs = 0; |
84 | for (i = 0; i < pipe->n_layers; i++) { |
85 | if (pipe->layers[i]->layer_type == KOMEDA_FMT_RICH_LAYER) |
86 | config_id.n_richs++; |
87 | } |
88 | return sysfs_emit(buf, fmt: "0x%08x\n" , config_id.value); |
89 | } |
90 | static DEVICE_ATTR_RO(config_id); |
91 | |
92 | static ssize_t |
93 | aclk_hz_show(struct device *dev, struct device_attribute *attr, char *buf) |
94 | { |
95 | struct komeda_dev *mdev = dev_to_mdev(dev); |
96 | |
97 | return sysfs_emit(buf, fmt: "%lu\n" , clk_get_rate(clk: mdev->aclk)); |
98 | } |
99 | static DEVICE_ATTR_RO(aclk_hz); |
100 | |
101 | static struct attribute *komeda_sysfs_entries[] = { |
102 | &dev_attr_core_id.attr, |
103 | &dev_attr_config_id.attr, |
104 | &dev_attr_aclk_hz.attr, |
105 | NULL, |
106 | }; |
107 | |
108 | static struct attribute_group komeda_sysfs_attr_group = { |
109 | .attrs = komeda_sysfs_entries, |
110 | }; |
111 | |
112 | static int komeda_parse_pipe_dt(struct komeda_pipeline *pipe) |
113 | { |
114 | struct device_node *np = pipe->of_node; |
115 | struct clk *clk; |
116 | |
117 | clk = of_clk_get_by_name(np, name: "pxclk" ); |
118 | if (IS_ERR(ptr: clk)) { |
119 | DRM_ERROR("get pxclk for pipeline %d failed!\n" , pipe->id); |
120 | return PTR_ERR(ptr: clk); |
121 | } |
122 | pipe->pxlclk = clk; |
123 | |
124 | /* enum ports */ |
125 | pipe->of_output_links[0] = |
126 | of_graph_get_remote_node(node: np, port: KOMEDA_OF_PORT_OUTPUT, endpoint: 0); |
127 | pipe->of_output_links[1] = |
128 | of_graph_get_remote_node(node: np, port: KOMEDA_OF_PORT_OUTPUT, endpoint: 1); |
129 | pipe->of_output_port = |
130 | of_graph_get_port_by_id(node: np, id: KOMEDA_OF_PORT_OUTPUT); |
131 | |
132 | pipe->dual_link = pipe->of_output_links[0] && pipe->of_output_links[1]; |
133 | |
134 | return 0; |
135 | } |
136 | |
137 | static int komeda_parse_dt(struct device *dev, struct komeda_dev *mdev) |
138 | { |
139 | struct platform_device *pdev = to_platform_device(dev); |
140 | struct device_node *child, *np = dev->of_node; |
141 | struct komeda_pipeline *pipe; |
142 | u32 pipe_id = U32_MAX; |
143 | int ret = -1; |
144 | |
145 | mdev->irq = platform_get_irq(pdev, 0); |
146 | if (mdev->irq < 0) { |
147 | DRM_ERROR("could not get IRQ number.\n" ); |
148 | return mdev->irq; |
149 | } |
150 | |
151 | /* Get the optional framebuffer memory resource */ |
152 | ret = of_reserved_mem_device_init(dev); |
153 | if (ret && ret != -ENODEV) |
154 | return ret; |
155 | |
156 | for_each_available_child_of_node(np, child) { |
157 | if (of_node_name_eq(np: child, name: "pipeline" )) { |
158 | of_property_read_u32(np: child, propname: "reg" , out_value: &pipe_id); |
159 | if (pipe_id >= mdev->n_pipelines) { |
160 | DRM_WARN("Skip the redundant DT node: pipeline-%u.\n" , |
161 | pipe_id); |
162 | continue; |
163 | } |
164 | mdev->pipelines[pipe_id]->of_node = of_node_get(node: child); |
165 | } |
166 | } |
167 | |
168 | for (pipe_id = 0; pipe_id < mdev->n_pipelines; pipe_id++) { |
169 | pipe = mdev->pipelines[pipe_id]; |
170 | |
171 | if (!pipe->of_node) { |
172 | DRM_ERROR("Pipeline-%d doesn't have a DT node.\n" , |
173 | pipe->id); |
174 | return -EINVAL; |
175 | } |
176 | ret = komeda_parse_pipe_dt(pipe); |
177 | if (ret) |
178 | return ret; |
179 | } |
180 | |
181 | return 0; |
182 | } |
183 | |
184 | struct komeda_dev *komeda_dev_create(struct device *dev) |
185 | { |
186 | struct platform_device *pdev = to_platform_device(dev); |
187 | komeda_identify_func komeda_identify; |
188 | struct komeda_dev *mdev; |
189 | int err = 0; |
190 | |
191 | komeda_identify = of_device_get_match_data(dev); |
192 | if (!komeda_identify) |
193 | return ERR_PTR(error: -ENODEV); |
194 | |
195 | mdev = devm_kzalloc(dev, size: sizeof(*mdev), GFP_KERNEL); |
196 | if (!mdev) |
197 | return ERR_PTR(error: -ENOMEM); |
198 | |
199 | mutex_init(&mdev->lock); |
200 | |
201 | mdev->dev = dev; |
202 | mdev->reg_base = devm_platform_ioremap_resource(pdev, index: 0); |
203 | if (IS_ERR(ptr: mdev->reg_base)) { |
204 | DRM_ERROR("Map register space failed.\n" ); |
205 | err = PTR_ERR(ptr: mdev->reg_base); |
206 | mdev->reg_base = NULL; |
207 | goto err_cleanup; |
208 | } |
209 | |
210 | mdev->aclk = devm_clk_get(dev, id: "aclk" ); |
211 | if (IS_ERR(ptr: mdev->aclk)) { |
212 | DRM_ERROR("Get engine clk failed.\n" ); |
213 | err = PTR_ERR(ptr: mdev->aclk); |
214 | mdev->aclk = NULL; |
215 | goto err_cleanup; |
216 | } |
217 | |
218 | clk_prepare_enable(clk: mdev->aclk); |
219 | |
220 | mdev->funcs = komeda_identify(mdev->reg_base, &mdev->chip); |
221 | if (!mdev->funcs) { |
222 | DRM_ERROR("Failed to identify the HW.\n" ); |
223 | err = -ENODEV; |
224 | goto disable_clk; |
225 | } |
226 | |
227 | DRM_INFO("Found ARM Mali-D%x version r%dp%d\n" , |
228 | MALIDP_CORE_ID_PRODUCT_ID(mdev->chip.core_id), |
229 | MALIDP_CORE_ID_MAJOR(mdev->chip.core_id), |
230 | MALIDP_CORE_ID_MINOR(mdev->chip.core_id)); |
231 | |
232 | mdev->funcs->init_format_table(mdev); |
233 | |
234 | err = mdev->funcs->enum_resources(mdev); |
235 | if (err) { |
236 | DRM_ERROR("enumerate display resource failed.\n" ); |
237 | goto disable_clk; |
238 | } |
239 | |
240 | err = komeda_parse_dt(dev, mdev); |
241 | if (err) { |
242 | DRM_ERROR("parse device tree failed.\n" ); |
243 | goto disable_clk; |
244 | } |
245 | |
246 | err = komeda_assemble_pipelines(mdev); |
247 | if (err) { |
248 | DRM_ERROR("assemble display pipelines failed.\n" ); |
249 | goto disable_clk; |
250 | } |
251 | |
252 | dma_set_max_seg_size(dev, U32_MAX); |
253 | |
254 | mdev->iommu = iommu_get_domain_for_dev(dev: mdev->dev); |
255 | if (!mdev->iommu) |
256 | DRM_INFO("continue without IOMMU support!\n" ); |
257 | |
258 | clk_disable_unprepare(clk: mdev->aclk); |
259 | |
260 | err = sysfs_create_group(kobj: &dev->kobj, grp: &komeda_sysfs_attr_group); |
261 | if (err) { |
262 | DRM_ERROR("create sysfs group failed.\n" ); |
263 | goto err_cleanup; |
264 | } |
265 | |
266 | mdev->err_verbosity = KOMEDA_DEV_PRINT_ERR_EVENTS; |
267 | |
268 | #ifdef CONFIG_DEBUG_FS |
269 | komeda_debugfs_init(mdev); |
270 | #endif |
271 | |
272 | return mdev; |
273 | |
274 | disable_clk: |
275 | clk_disable_unprepare(clk: mdev->aclk); |
276 | err_cleanup: |
277 | komeda_dev_destroy(mdev); |
278 | return ERR_PTR(error: err); |
279 | } |
280 | |
281 | void komeda_dev_destroy(struct komeda_dev *mdev) |
282 | { |
283 | struct device *dev = mdev->dev; |
284 | const struct komeda_dev_funcs *funcs = mdev->funcs; |
285 | int i; |
286 | |
287 | sysfs_remove_group(kobj: &dev->kobj, grp: &komeda_sysfs_attr_group); |
288 | |
289 | #ifdef CONFIG_DEBUG_FS |
290 | debugfs_remove_recursive(dentry: mdev->debugfs_root); |
291 | #endif |
292 | |
293 | if (mdev->aclk) |
294 | clk_prepare_enable(clk: mdev->aclk); |
295 | |
296 | for (i = 0; i < mdev->n_pipelines; i++) { |
297 | komeda_pipeline_destroy(mdev, pipe: mdev->pipelines[i]); |
298 | mdev->pipelines[i] = NULL; |
299 | } |
300 | |
301 | mdev->n_pipelines = 0; |
302 | |
303 | of_reserved_mem_device_release(dev); |
304 | |
305 | if (funcs && funcs->cleanup) |
306 | funcs->cleanup(mdev); |
307 | |
308 | if (mdev->reg_base) { |
309 | devm_iounmap(dev, addr: mdev->reg_base); |
310 | mdev->reg_base = NULL; |
311 | } |
312 | |
313 | if (mdev->aclk) { |
314 | clk_disable_unprepare(clk: mdev->aclk); |
315 | devm_clk_put(dev, clk: mdev->aclk); |
316 | mdev->aclk = NULL; |
317 | } |
318 | |
319 | devm_kfree(dev, p: mdev); |
320 | } |
321 | |
322 | int komeda_dev_resume(struct komeda_dev *mdev) |
323 | { |
324 | clk_prepare_enable(clk: mdev->aclk); |
325 | |
326 | mdev->funcs->enable_irq(mdev); |
327 | |
328 | if (mdev->iommu && mdev->funcs->connect_iommu) |
329 | if (mdev->funcs->connect_iommu(mdev)) |
330 | DRM_ERROR("connect iommu failed.\n" ); |
331 | |
332 | return 0; |
333 | } |
334 | |
335 | int komeda_dev_suspend(struct komeda_dev *mdev) |
336 | { |
337 | if (mdev->iommu && mdev->funcs->disconnect_iommu) |
338 | if (mdev->funcs->disconnect_iommu(mdev)) |
339 | DRM_ERROR("disconnect iommu failed.\n" ); |
340 | |
341 | mdev->funcs->disable_irq(mdev); |
342 | |
343 | clk_disable_unprepare(clk: mdev->aclk); |
344 | |
345 | return 0; |
346 | } |
347 | |