1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2017 Lucas Stach, Pengutronix |
4 | */ |
5 | |
6 | #include <drm/drm_fourcc.h> |
7 | #include <linux/clk.h> |
8 | #include <linux/err.h> |
9 | #include <linux/genalloc.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/platform_device.h> |
13 | #include <video/imx-ipu-v3.h> |
14 | |
15 | #include "ipu-prv.h" |
16 | |
17 | #define IPU_PRE_MAX_WIDTH 2048 |
18 | #define IPU_PRE_NUM_SCANLINES 8 |
19 | |
20 | #define IPU_PRE_CTRL 0x000 |
21 | #define IPU_PRE_CTRL_SET 0x004 |
22 | #define IPU_PRE_CTRL_ENABLE (1 << 0) |
23 | #define IPU_PRE_CTRL_BLOCK_EN (1 << 1) |
24 | #define IPU_PRE_CTRL_BLOCK_16 (1 << 2) |
25 | #define IPU_PRE_CTRL_SDW_UPDATE (1 << 4) |
26 | #define IPU_PRE_CTRL_VFLIP (1 << 5) |
27 | #define IPU_PRE_CTRL_SO (1 << 6) |
28 | #define IPU_PRE_CTRL_INTERLACED_FIELD (1 << 7) |
29 | #define IPU_PRE_CTRL_HANDSHAKE_EN (1 << 8) |
30 | #define IPU_PRE_CTRL_HANDSHAKE_LINE_NUM(v) ((v & 0x3) << 9) |
31 | #define IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN (1 << 11) |
32 | #define IPU_PRE_CTRL_EN_REPEAT (1 << 28) |
33 | #define IPU_PRE_CTRL_TPR_REST_SEL (1 << 29) |
34 | #define IPU_PRE_CTRL_CLKGATE (1 << 30) |
35 | #define IPU_PRE_CTRL_SFTRST (1 << 31) |
36 | |
37 | #define IPU_PRE_CUR_BUF 0x030 |
38 | |
39 | #define IPU_PRE_NEXT_BUF 0x040 |
40 | |
41 | #define IPU_PRE_TPR_CTRL 0x070 |
42 | #define IPU_PRE_TPR_CTRL_TILE_FORMAT(v) ((v & 0xff) << 0) |
43 | #define IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK 0xff |
44 | #define IPU_PRE_TPR_CTRL_TILE_FORMAT_16_BIT (1 << 0) |
45 | #define IPU_PRE_TPR_CTRL_TILE_FORMAT_SPLIT_BUF (1 << 4) |
46 | #define IPU_PRE_TPR_CTRL_TILE_FORMAT_SINGLE_BUF (1 << 5) |
47 | #define IPU_PRE_TPR_CTRL_TILE_FORMAT_SUPER_TILED (1 << 6) |
48 | |
49 | #define IPU_PRE_PREFETCH_ENG_CTRL 0x080 |
50 | #define IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN (1 << 0) |
51 | #define IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(v) ((v & 0x7) << 1) |
52 | #define IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(v) ((v & 0x3) << 4) |
53 | #define IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(v) ((v & 0x7) << 8) |
54 | #define IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS (1 << 11) |
55 | #define IPU_PRE_PREF_ENG_CTRL_FIELD_INVERSE (1 << 12) |
56 | #define IPU_PRE_PREF_ENG_CTRL_PARTIAL_UV_SWAP (1 << 14) |
57 | #define IPU_PRE_PREF_ENG_CTRL_TPR_COOR_OFFSET_EN (1 << 15) |
58 | |
59 | #define IPU_PRE_PREFETCH_ENG_INPUT_SIZE 0x0a0 |
60 | #define IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(v) ((v & 0xffff) << 0) |
61 | #define IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(v) ((v & 0xffff) << 16) |
62 | |
63 | #define IPU_PRE_PREFETCH_ENG_PITCH 0x0d0 |
64 | #define IPU_PRE_PREFETCH_ENG_PITCH_Y(v) ((v & 0xffff) << 0) |
65 | #define IPU_PRE_PREFETCH_ENG_PITCH_UV(v) ((v & 0xffff) << 16) |
66 | |
67 | #define IPU_PRE_STORE_ENG_CTRL 0x110 |
68 | #define IPU_PRE_STORE_ENG_CTRL_STORE_EN (1 << 0) |
69 | #define IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(v) ((v & 0x7) << 1) |
70 | #define IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(v) ((v & 0x3) << 4) |
71 | |
72 | #define IPU_PRE_STORE_ENG_STATUS 0x120 |
73 | #define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_X_MASK 0xffff |
74 | #define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_X_SHIFT 0 |
75 | #define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_MASK 0x3fff |
76 | #define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_SHIFT 16 |
77 | #define IPU_PRE_STORE_ENG_STATUS_STORE_FIFO_FULL (1 << 30) |
78 | #define IPU_PRE_STORE_ENG_STATUS_STORE_FIELD (1 << 31) |
79 | |
80 | #define IPU_PRE_STORE_ENG_SIZE 0x130 |
81 | #define IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(v) ((v & 0xffff) << 0) |
82 | #define IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(v) ((v & 0xffff) << 16) |
83 | |
84 | #define IPU_PRE_STORE_ENG_PITCH 0x140 |
85 | #define IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(v) ((v & 0xffff) << 0) |
86 | |
87 | #define IPU_PRE_STORE_ENG_ADDR 0x150 |
88 | |
89 | struct ipu_pre { |
90 | struct list_head list; |
91 | struct device *dev; |
92 | |
93 | void __iomem *regs; |
94 | struct clk *clk_axi; |
95 | struct gen_pool *iram; |
96 | |
97 | dma_addr_t buffer_paddr; |
98 | void *buffer_virt; |
99 | bool in_use; |
100 | unsigned int safe_window_end; |
101 | unsigned int last_bufaddr; |
102 | }; |
103 | |
104 | static DEFINE_MUTEX(ipu_pre_list_mutex); |
105 | static LIST_HEAD(ipu_pre_list); |
106 | static int available_pres; |
107 | |
108 | int ipu_pre_get_available_count(void) |
109 | { |
110 | return available_pres; |
111 | } |
112 | |
113 | struct ipu_pre * |
114 | ipu_pre_lookup_by_phandle(struct device *dev, const char *name, int index) |
115 | { |
116 | struct device_node *pre_node = of_parse_phandle(np: dev->of_node, |
117 | phandle_name: name, index); |
118 | struct ipu_pre *pre; |
119 | |
120 | mutex_lock(&ipu_pre_list_mutex); |
121 | list_for_each_entry(pre, &ipu_pre_list, list) { |
122 | if (pre_node == pre->dev->of_node) { |
123 | mutex_unlock(lock: &ipu_pre_list_mutex); |
124 | device_link_add(consumer: dev, supplier: pre->dev, |
125 | DL_FLAG_AUTOREMOVE_CONSUMER); |
126 | of_node_put(node: pre_node); |
127 | return pre; |
128 | } |
129 | } |
130 | mutex_unlock(lock: &ipu_pre_list_mutex); |
131 | |
132 | of_node_put(node: pre_node); |
133 | |
134 | return NULL; |
135 | } |
136 | |
137 | int ipu_pre_get(struct ipu_pre *pre) |
138 | { |
139 | u32 val; |
140 | |
141 | if (pre->in_use) |
142 | return -EBUSY; |
143 | |
144 | /* first get the engine out of reset and remove clock gating */ |
145 | writel(val: 0, addr: pre->regs + IPU_PRE_CTRL); |
146 | |
147 | /* init defaults that should be applied to all streams */ |
148 | val = IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN | |
149 | IPU_PRE_CTRL_HANDSHAKE_EN | |
150 | IPU_PRE_CTRL_TPR_REST_SEL | |
151 | IPU_PRE_CTRL_SDW_UPDATE; |
152 | writel(val, addr: pre->regs + IPU_PRE_CTRL); |
153 | |
154 | pre->in_use = true; |
155 | return 0; |
156 | } |
157 | |
158 | void ipu_pre_put(struct ipu_pre *pre) |
159 | { |
160 | writel(IPU_PRE_CTRL_SFTRST, addr: pre->regs + IPU_PRE_CTRL); |
161 | |
162 | pre->in_use = false; |
163 | } |
164 | |
165 | void ipu_pre_configure(struct ipu_pre *pre, unsigned int width, |
166 | unsigned int height, unsigned int stride, u32 format, |
167 | uint64_t modifier, unsigned int bufaddr) |
168 | { |
169 | const struct drm_format_info *info = drm_format_info(format); |
170 | u32 active_bpp = info->cpp[0] >> 1; |
171 | u32 val; |
172 | |
173 | /* calculate safe window for ctrl register updates */ |
174 | if (modifier == DRM_FORMAT_MOD_LINEAR) |
175 | pre->safe_window_end = height - 2; |
176 | else |
177 | pre->safe_window_end = DIV_ROUND_UP(height, 4) - 1; |
178 | |
179 | writel(val: bufaddr, addr: pre->regs + IPU_PRE_CUR_BUF); |
180 | writel(val: bufaddr, addr: pre->regs + IPU_PRE_NEXT_BUF); |
181 | pre->last_bufaddr = bufaddr; |
182 | |
183 | val = IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(0) | |
184 | IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(active_bpp) | |
185 | IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(4) | |
186 | IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS | |
187 | IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN; |
188 | writel(val, addr: pre->regs + IPU_PRE_PREFETCH_ENG_CTRL); |
189 | |
190 | val = IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(width) | |
191 | IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(height); |
192 | writel(val, addr: pre->regs + IPU_PRE_PREFETCH_ENG_INPUT_SIZE); |
193 | |
194 | val = IPU_PRE_PREFETCH_ENG_PITCH_Y(stride); |
195 | writel(val, addr: pre->regs + IPU_PRE_PREFETCH_ENG_PITCH); |
196 | |
197 | val = IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(active_bpp) | |
198 | IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(4) | |
199 | IPU_PRE_STORE_ENG_CTRL_STORE_EN; |
200 | writel(val, addr: pre->regs + IPU_PRE_STORE_ENG_CTRL); |
201 | |
202 | val = IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(width) | |
203 | IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(height); |
204 | writel(val, addr: pre->regs + IPU_PRE_STORE_ENG_SIZE); |
205 | |
206 | val = IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(stride); |
207 | writel(val, addr: pre->regs + IPU_PRE_STORE_ENG_PITCH); |
208 | |
209 | writel(val: pre->buffer_paddr, addr: pre->regs + IPU_PRE_STORE_ENG_ADDR); |
210 | |
211 | val = readl(addr: pre->regs + IPU_PRE_TPR_CTRL); |
212 | val &= ~IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK; |
213 | if (modifier != DRM_FORMAT_MOD_LINEAR) { |
214 | /* only support single buffer formats for now */ |
215 | val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_SINGLE_BUF; |
216 | if (modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED) |
217 | val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_SUPER_TILED; |
218 | if (info->cpp[0] == 2) |
219 | val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_16_BIT; |
220 | } |
221 | writel(val, addr: pre->regs + IPU_PRE_TPR_CTRL); |
222 | |
223 | val = readl(addr: pre->regs + IPU_PRE_CTRL); |
224 | val |= IPU_PRE_CTRL_EN_REPEAT | IPU_PRE_CTRL_ENABLE | |
225 | IPU_PRE_CTRL_SDW_UPDATE; |
226 | if (modifier == DRM_FORMAT_MOD_LINEAR) |
227 | val &= ~IPU_PRE_CTRL_BLOCK_EN; |
228 | else |
229 | val |= IPU_PRE_CTRL_BLOCK_EN; |
230 | writel(val, addr: pre->regs + IPU_PRE_CTRL); |
231 | } |
232 | |
233 | void ipu_pre_update(struct ipu_pre *pre, unsigned int bufaddr) |
234 | { |
235 | unsigned long timeout = jiffies + msecs_to_jiffies(m: 5); |
236 | unsigned short current_yblock; |
237 | u32 val; |
238 | |
239 | if (bufaddr == pre->last_bufaddr) |
240 | return; |
241 | |
242 | writel(val: bufaddr, addr: pre->regs + IPU_PRE_NEXT_BUF); |
243 | pre->last_bufaddr = bufaddr; |
244 | |
245 | do { |
246 | if (time_after(jiffies, timeout)) { |
247 | dev_warn(pre->dev, "timeout waiting for PRE safe window\n" ); |
248 | return; |
249 | } |
250 | |
251 | val = readl(addr: pre->regs + IPU_PRE_STORE_ENG_STATUS); |
252 | current_yblock = |
253 | (val >> IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_SHIFT) & |
254 | IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_MASK; |
255 | } while (current_yblock == 0 || current_yblock >= pre->safe_window_end); |
256 | |
257 | writel(IPU_PRE_CTRL_SDW_UPDATE, addr: pre->regs + IPU_PRE_CTRL_SET); |
258 | } |
259 | |
260 | bool ipu_pre_update_pending(struct ipu_pre *pre) |
261 | { |
262 | return !!(readl_relaxed(pre->regs + IPU_PRE_CTRL) & |
263 | IPU_PRE_CTRL_SDW_UPDATE); |
264 | } |
265 | |
266 | u32 ipu_pre_get_baddr(struct ipu_pre *pre) |
267 | { |
268 | return (u32)pre->buffer_paddr; |
269 | } |
270 | |
271 | static int ipu_pre_probe(struct platform_device *pdev) |
272 | { |
273 | struct device *dev = &pdev->dev; |
274 | struct ipu_pre *pre; |
275 | |
276 | pre = devm_kzalloc(dev, size: sizeof(*pre), GFP_KERNEL); |
277 | if (!pre) |
278 | return -ENOMEM; |
279 | |
280 | pre->regs = devm_platform_ioremap_resource(pdev, index: 0); |
281 | if (IS_ERR(ptr: pre->regs)) |
282 | return PTR_ERR(ptr: pre->regs); |
283 | |
284 | pre->clk_axi = devm_clk_get(dev, id: "axi" ); |
285 | if (IS_ERR(ptr: pre->clk_axi)) |
286 | return PTR_ERR(ptr: pre->clk_axi); |
287 | |
288 | pre->iram = of_gen_pool_get(np: dev->of_node, propname: "fsl,iram" , index: 0); |
289 | if (!pre->iram) |
290 | return -EPROBE_DEFER; |
291 | |
292 | /* |
293 | * Allocate IRAM buffer with maximum size. This could be made dynamic, |
294 | * but as there is no other user of this IRAM region and we can fit all |
295 | * max sized buffers into it, there is no need yet. |
296 | */ |
297 | pre->buffer_virt = gen_pool_dma_alloc(pool: pre->iram, IPU_PRE_MAX_WIDTH * |
298 | IPU_PRE_NUM_SCANLINES * 4, |
299 | dma: &pre->buffer_paddr); |
300 | if (!pre->buffer_virt) |
301 | return -ENOMEM; |
302 | |
303 | clk_prepare_enable(clk: pre->clk_axi); |
304 | |
305 | pre->dev = dev; |
306 | platform_set_drvdata(pdev, data: pre); |
307 | mutex_lock(&ipu_pre_list_mutex); |
308 | list_add(new: &pre->list, head: &ipu_pre_list); |
309 | available_pres++; |
310 | mutex_unlock(lock: &ipu_pre_list_mutex); |
311 | |
312 | return 0; |
313 | } |
314 | |
315 | static int ipu_pre_remove(struct platform_device *pdev) |
316 | { |
317 | struct ipu_pre *pre = platform_get_drvdata(pdev); |
318 | |
319 | mutex_lock(&ipu_pre_list_mutex); |
320 | list_del(entry: &pre->list); |
321 | available_pres--; |
322 | mutex_unlock(lock: &ipu_pre_list_mutex); |
323 | |
324 | clk_disable_unprepare(clk: pre->clk_axi); |
325 | |
326 | if (pre->buffer_virt) |
327 | gen_pool_free(pool: pre->iram, addr: (unsigned long)pre->buffer_virt, |
328 | IPU_PRE_MAX_WIDTH * IPU_PRE_NUM_SCANLINES * 4); |
329 | return 0; |
330 | } |
331 | |
332 | static const struct of_device_id ipu_pre_dt_ids[] = { |
333 | { .compatible = "fsl,imx6qp-pre" , }, |
334 | { /* sentinel */ }, |
335 | }; |
336 | |
337 | struct platform_driver ipu_pre_drv = { |
338 | .probe = ipu_pre_probe, |
339 | .remove = ipu_pre_remove, |
340 | .driver = { |
341 | .name = "imx-ipu-pre" , |
342 | .of_match_table = ipu_pre_dt_ids, |
343 | }, |
344 | }; |
345 | |