]>
Commit | Line | Data |
---|---|---|
b0444f18 PZ |
1 | /* |
2 | * i.MX6 Video Data Order Adapter (VDOA) | |
3 | * | |
4 | * Copyright (C) 2014 Philipp Zabel | |
5 | * Copyright (C) 2016 Pengutronix, Michael Tretter <kernel@pengutronix.de> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * version 2, as published by the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | */ | |
16 | ||
17 | #include <linux/clk.h> | |
18 | #include <linux/device.h> | |
19 | #include <linux/interrupt.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/dma-mapping.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/videodev2.h> | |
24 | #include <linux/slab.h> | |
25 | ||
26 | #include "imx-vdoa.h" | |
27 | ||
28 | #define VDOA_NAME "imx-vdoa" | |
29 | ||
30 | #define VDOAC 0x00 | |
31 | #define VDOASRR 0x04 | |
32 | #define VDOAIE 0x08 | |
33 | #define VDOAIST 0x0c | |
34 | #define VDOAFP 0x10 | |
35 | #define VDOAIEBA00 0x14 | |
36 | #define VDOAIEBA01 0x18 | |
37 | #define VDOAIEBA02 0x1c | |
38 | #define VDOAIEBA10 0x20 | |
39 | #define VDOAIEBA11 0x24 | |
40 | #define VDOAIEBA12 0x28 | |
41 | #define VDOASL 0x2c | |
42 | #define VDOAIUBO 0x30 | |
43 | #define VDOAVEBA0 0x34 | |
44 | #define VDOAVEBA1 0x38 | |
45 | #define VDOAVEBA2 0x3c | |
46 | #define VDOAVUBO 0x40 | |
47 | #define VDOASR 0x44 | |
48 | ||
49 | #define VDOAC_ISEL BIT(6) | |
50 | #define VDOAC_PFS BIT(5) | |
51 | #define VDOAC_SO BIT(4) | |
52 | #define VDOAC_SYNC BIT(3) | |
53 | #define VDOAC_NF BIT(2) | |
54 | #define VDOAC_BNDM_MASK 0x3 | |
55 | #define VDOAC_BAND_HEIGHT_8 0x0 | |
56 | #define VDOAC_BAND_HEIGHT_16 0x1 | |
57 | #define VDOAC_BAND_HEIGHT_32 0x2 | |
58 | ||
59 | #define VDOASRR_START BIT(1) | |
60 | #define VDOASRR_SWRST BIT(0) | |
61 | ||
62 | #define VDOAIE_EITERR BIT(1) | |
63 | #define VDOAIE_EIEOT BIT(0) | |
64 | ||
65 | #define VDOAIST_TERR BIT(1) | |
66 | #define VDOAIST_EOT BIT(0) | |
67 | ||
68 | #define VDOAFP_FH_MASK (0x1fff << 16) | |
69 | #define VDOAFP_FW_MASK (0x3fff) | |
70 | ||
71 | #define VDOASL_VSLY_MASK (0x3fff << 16) | |
72 | #define VDOASL_ISLY_MASK (0x7fff) | |
73 | ||
74 | #define VDOASR_ERRW BIT(4) | |
75 | #define VDOASR_EOB BIT(3) | |
76 | #define VDOASR_CURRENT_FRAME (0x3 << 1) | |
77 | #define VDOASR_CURRENT_BUFFER BIT(1) | |
78 | ||
79 | enum { | |
80 | V4L2_M2M_SRC = 0, | |
81 | V4L2_M2M_DST = 1, | |
82 | }; | |
83 | ||
84 | struct vdoa_data { | |
85 | struct vdoa_ctx *curr_ctx; | |
86 | struct device *dev; | |
87 | struct clk *vdoa_clk; | |
88 | void __iomem *regs; | |
89 | int irq; | |
90 | }; | |
91 | ||
92 | struct vdoa_q_data { | |
93 | unsigned int width; | |
94 | unsigned int height; | |
95 | unsigned int bytesperline; | |
96 | unsigned int sizeimage; | |
97 | u32 pixelformat; | |
98 | }; | |
99 | ||
100 | struct vdoa_ctx { | |
101 | struct vdoa_data *vdoa; | |
102 | struct completion completion; | |
103 | struct vdoa_q_data q_data[2]; | |
104 | }; | |
105 | ||
106 | static irqreturn_t vdoa_irq_handler(int irq, void *data) | |
107 | { | |
108 | struct vdoa_data *vdoa = data; | |
109 | struct vdoa_ctx *curr_ctx; | |
110 | u32 val; | |
111 | ||
112 | /* Disable interrupts */ | |
113 | writel(0, vdoa->regs + VDOAIE); | |
114 | ||
115 | curr_ctx = vdoa->curr_ctx; | |
116 | if (!curr_ctx) { | |
117 | dev_dbg(vdoa->dev, | |
118 | "Instance released before the end of transaction\n"); | |
119 | return IRQ_HANDLED; | |
120 | } | |
121 | ||
122 | val = readl(vdoa->regs + VDOAIST); | |
123 | writel(val, vdoa->regs + VDOAIST); | |
124 | if (val & VDOAIST_TERR) { | |
125 | val = readl(vdoa->regs + VDOASR) & VDOASR_ERRW; | |
126 | dev_err(vdoa->dev, "AXI %s error\n", val ? "write" : "read"); | |
127 | } else if (!(val & VDOAIST_EOT)) { | |
128 | dev_warn(vdoa->dev, "Spurious interrupt\n"); | |
129 | } | |
130 | complete(&curr_ctx->completion); | |
131 | ||
132 | return IRQ_HANDLED; | |
133 | } | |
134 | ||
135 | void vdoa_device_run(struct vdoa_ctx *ctx, dma_addr_t dst, dma_addr_t src) | |
136 | { | |
137 | struct vdoa_q_data *src_q_data, *dst_q_data; | |
138 | struct vdoa_data *vdoa = ctx->vdoa; | |
139 | u32 val; | |
140 | ||
141 | vdoa->curr_ctx = ctx; | |
142 | ||
143 | src_q_data = &ctx->q_data[V4L2_M2M_SRC]; | |
144 | dst_q_data = &ctx->q_data[V4L2_M2M_DST]; | |
145 | ||
146 | /* Progressive, no sync, 1 frame per run */ | |
147 | if (dst_q_data->pixelformat == V4L2_PIX_FMT_YUYV) | |
148 | val = VDOAC_PFS; | |
149 | else | |
150 | val = 0; | |
151 | writel(val, vdoa->regs + VDOAC); | |
152 | ||
153 | writel(dst_q_data->height << 16 | dst_q_data->width, | |
154 | vdoa->regs + VDOAFP); | |
155 | ||
156 | val = dst; | |
157 | writel(val, vdoa->regs + VDOAIEBA00); | |
158 | ||
159 | writel(src_q_data->bytesperline << 16 | dst_q_data->bytesperline, | |
160 | vdoa->regs + VDOASL); | |
161 | ||
162 | if (dst_q_data->pixelformat == V4L2_PIX_FMT_NV12 || | |
163 | dst_q_data->pixelformat == V4L2_PIX_FMT_NV21) | |
164 | val = dst_q_data->bytesperline * dst_q_data->height; | |
165 | else | |
166 | val = 0; | |
167 | writel(val, vdoa->regs + VDOAIUBO); | |
168 | ||
169 | val = src; | |
170 | writel(val, vdoa->regs + VDOAVEBA0); | |
171 | val = round_up(src_q_data->bytesperline * src_q_data->height, 4096); | |
172 | writel(val, vdoa->regs + VDOAVUBO); | |
173 | ||
174 | /* Enable interrupts and start transfer */ | |
175 | writel(VDOAIE_EITERR | VDOAIE_EIEOT, vdoa->regs + VDOAIE); | |
176 | writel(VDOASRR_START, vdoa->regs + VDOASRR); | |
177 | } | |
178 | EXPORT_SYMBOL(vdoa_device_run); | |
179 | ||
180 | int vdoa_wait_for_completion(struct vdoa_ctx *ctx) | |
181 | { | |
182 | struct vdoa_data *vdoa = ctx->vdoa; | |
183 | ||
184 | if (!wait_for_completion_timeout(&ctx->completion, | |
185 | msecs_to_jiffies(300))) { | |
186 | dev_err(vdoa->dev, | |
187 | "Timeout waiting for transfer result\n"); | |
188 | return -ETIMEDOUT; | |
189 | } | |
190 | ||
191 | return 0; | |
192 | } | |
193 | EXPORT_SYMBOL(vdoa_wait_for_completion); | |
194 | ||
195 | struct vdoa_ctx *vdoa_context_create(struct vdoa_data *vdoa) | |
196 | { | |
197 | struct vdoa_ctx *ctx; | |
198 | int err; | |
199 | ||
200 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | |
201 | if (!ctx) | |
202 | return NULL; | |
203 | ||
204 | err = clk_prepare_enable(vdoa->vdoa_clk); | |
205 | if (err) { | |
206 | kfree(ctx); | |
207 | return NULL; | |
208 | } | |
209 | ||
210 | init_completion(&ctx->completion); | |
211 | ctx->vdoa = vdoa; | |
212 | ||
213 | return ctx; | |
214 | } | |
215 | EXPORT_SYMBOL(vdoa_context_create); | |
216 | ||
217 | void vdoa_context_destroy(struct vdoa_ctx *ctx) | |
218 | { | |
219 | struct vdoa_data *vdoa = ctx->vdoa; | |
220 | ||
221 | clk_disable_unprepare(vdoa->vdoa_clk); | |
222 | kfree(ctx); | |
223 | } | |
224 | EXPORT_SYMBOL(vdoa_context_destroy); | |
225 | ||
226 | int vdoa_context_configure(struct vdoa_ctx *ctx, | |
227 | unsigned int width, unsigned int height, | |
228 | u32 pixelformat) | |
229 | { | |
230 | struct vdoa_q_data *src_q_data; | |
231 | struct vdoa_q_data *dst_q_data; | |
232 | ||
233 | if (width < 16 || width > 8192 || width % 16 != 0 || | |
234 | height < 16 || height > 4096 || height % 16 != 0) | |
235 | return -EINVAL; | |
236 | ||
237 | if (pixelformat != V4L2_PIX_FMT_YUYV && | |
238 | pixelformat != V4L2_PIX_FMT_NV12) | |
239 | return -EINVAL; | |
240 | ||
241 | /* If no context is passed, only check if the format is valid */ | |
242 | if (!ctx) | |
243 | return 0; | |
244 | ||
245 | src_q_data = &ctx->q_data[V4L2_M2M_SRC]; | |
246 | dst_q_data = &ctx->q_data[V4L2_M2M_DST]; | |
247 | ||
248 | src_q_data->width = width; | |
249 | src_q_data->height = height; | |
250 | src_q_data->bytesperline = width; | |
251 | src_q_data->sizeimage = | |
252 | round_up(src_q_data->bytesperline * height, 4096) + | |
253 | src_q_data->bytesperline * height / 2; | |
254 | ||
255 | dst_q_data->width = width; | |
256 | dst_q_data->height = height; | |
257 | dst_q_data->pixelformat = pixelformat; | |
258 | switch (pixelformat) { | |
259 | case V4L2_PIX_FMT_YUYV: | |
260 | dst_q_data->bytesperline = width * 2; | |
261 | dst_q_data->sizeimage = dst_q_data->bytesperline * height; | |
262 | break; | |
263 | case V4L2_PIX_FMT_NV12: | |
264 | default: | |
265 | dst_q_data->bytesperline = width; | |
266 | dst_q_data->sizeimage = | |
267 | dst_q_data->bytesperline * height * 3 / 2; | |
268 | break; | |
269 | } | |
270 | ||
271 | return 0; | |
272 | } | |
273 | EXPORT_SYMBOL(vdoa_context_configure); | |
274 | ||
275 | static int vdoa_probe(struct platform_device *pdev) | |
276 | { | |
277 | struct vdoa_data *vdoa; | |
278 | struct resource *res; | |
279 | ||
280 | dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); | |
281 | ||
282 | vdoa = devm_kzalloc(&pdev->dev, sizeof(*vdoa), GFP_KERNEL); | |
283 | if (!vdoa) | |
284 | return -ENOMEM; | |
285 | ||
286 | vdoa->dev = &pdev->dev; | |
287 | ||
288 | vdoa->vdoa_clk = devm_clk_get(vdoa->dev, NULL); | |
289 | if (IS_ERR(vdoa->vdoa_clk)) { | |
290 | dev_err(vdoa->dev, "Failed to get clock\n"); | |
291 | return PTR_ERR(vdoa->vdoa_clk); | |
292 | } | |
293 | ||
294 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
295 | vdoa->regs = devm_ioremap_resource(vdoa->dev, res); | |
296 | if (IS_ERR(vdoa->regs)) | |
297 | return PTR_ERR(vdoa->regs); | |
298 | ||
299 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
300 | vdoa->irq = devm_request_threaded_irq(&pdev->dev, res->start, NULL, | |
301 | vdoa_irq_handler, IRQF_ONESHOT, | |
302 | "vdoa", vdoa); | |
303 | if (vdoa->irq < 0) { | |
304 | dev_err(vdoa->dev, "Failed to get irq\n"); | |
305 | return vdoa->irq; | |
306 | } | |
307 | ||
308 | platform_set_drvdata(pdev, vdoa); | |
309 | ||
310 | return 0; | |
311 | } | |
312 | ||
313 | static int vdoa_remove(struct platform_device *pdev) | |
314 | { | |
315 | return 0; | |
316 | } | |
317 | ||
d2fe28fe | 318 | static const struct of_device_id vdoa_dt_ids[] = { |
b0444f18 PZ |
319 | { .compatible = "fsl,imx6q-vdoa" }, |
320 | {} | |
321 | }; | |
322 | MODULE_DEVICE_TABLE(of, vdoa_dt_ids); | |
323 | ||
6e2e0eea | 324 | static struct platform_driver vdoa_driver = { |
b0444f18 PZ |
325 | .probe = vdoa_probe, |
326 | .remove = vdoa_remove, | |
327 | .driver = { | |
328 | .name = VDOA_NAME, | |
329 | .of_match_table = vdoa_dt_ids, | |
330 | }, | |
331 | }; | |
332 | ||
333 | module_platform_driver(vdoa_driver); | |
334 | ||
335 | MODULE_DESCRIPTION("Video Data Order Adapter"); | |
336 | MODULE_AUTHOR("Philipp Zabel <philipp.zabel@gmail.com>"); | |
337 | MODULE_ALIAS("platform:imx-vdoa"); | |
338 | MODULE_LICENSE("GPL"); |