1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * TW5864 driver - core functions |
4 | * |
5 | * Copyright (C) 2016 Bluecherry, LLC <maintainers@bluecherrydvr.com> |
6 | */ |
7 | |
8 | #include <linux/init.h> |
9 | #include <linux/list.h> |
10 | #include <linux/module.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/kmod.h> |
14 | #include <linux/sound.h> |
15 | #include <linux/interrupt.h> |
16 | #include <linux/delay.h> |
17 | #include <linux/dma-mapping.h> |
18 | #include <linux/pm.h> |
19 | #include <linux/pci_ids.h> |
20 | #include <linux/jiffies.h> |
21 | #include <asm/dma.h> |
22 | #include <media/v4l2-dev.h> |
23 | |
24 | #include "tw5864.h" |
25 | #include "tw5864-reg.h" |
26 | |
27 | MODULE_DESCRIPTION("V4L2 driver module for tw5864-based multimedia capture & encoding devices" ); |
28 | MODULE_AUTHOR("Bluecherry Maintainers <maintainers@bluecherrydvr.com>" ); |
29 | MODULE_AUTHOR("Andrey Utkin <andrey.utkin@corp.bluecherry.net>" ); |
30 | MODULE_LICENSE("GPL" ); |
31 | |
32 | /* |
33 | * BEWARE OF KNOWN ISSUES WITH VIDEO QUALITY |
34 | * |
35 | * This driver was developed by Bluecherry LLC by deducing behaviour of |
36 | * original manufacturer's driver, from both source code and execution traces. |
37 | * It is known that there are some artifacts on output video with this driver: |
38 | * - on all known hardware samples: random pixels of wrong color (mostly |
39 | * white, red or blue) appearing and disappearing on sequences of P-frames; |
40 | * - on some hardware samples (known with H.264 core version e006:2800): |
41 | * total madness on P-frames: blocks of wrong luminance; blocks of wrong |
42 | * colors "creeping" across the picture. |
43 | * There is a workaround for both issues: avoid P-frames by setting GOP size |
44 | * to 1. To do that, run this command on device files created by this driver: |
45 | * |
46 | * v4l2-ctl --device /dev/videoX --set-ctrl=video_gop_size=1 |
47 | * |
48 | * These issues are not decoding errors; all produced H.264 streams are decoded |
49 | * properly. Streams without P-frames don't have these artifacts so it's not |
50 | * analog-to-digital conversion issues nor internal memory errors; we conclude |
51 | * it's internal H.264 encoder issues. |
52 | * We cannot even check the original driver's behaviour because it has never |
53 | * worked properly at all in our development environment. So these issues may |
54 | * be actually related to firmware or hardware. However it may be that there's |
55 | * just some more register settings missing in the driver which would please |
56 | * the hardware. |
57 | * Manufacturer didn't help much on our inquiries, but feel free to disturb |
58 | * again the support of Intersil (owner of former Techwell). |
59 | */ |
60 | |
61 | /* take first free /dev/videoX indexes by default */ |
62 | static unsigned int video_nr[] = {[0 ... (TW5864_INPUTS - 1)] = -1 }; |
63 | |
64 | module_param_array(video_nr, int, NULL, 0444); |
65 | MODULE_PARM_DESC(video_nr, "video devices numbers array" ); |
66 | |
67 | /* |
68 | * Please add any new PCI IDs to: https://pci-ids.ucw.cz. This keeps |
69 | * the PCI ID database up to date. Note that the entries must be |
70 | * added under vendor 0x1797 (Techwell Inc.) as subsystem IDs. |
71 | */ |
72 | static const struct pci_device_id tw5864_pci_tbl[] = { |
73 | {PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_TECHWELL_5864)}, |
74 | {0,} |
75 | }; |
76 | |
77 | void tw5864_irqmask_apply(struct tw5864_dev *dev) |
78 | { |
79 | tw_writel(TW5864_INTR_ENABLE_L, dev->irqmask & 0xffff); |
80 | tw_writel(TW5864_INTR_ENABLE_H, (dev->irqmask >> 16)); |
81 | } |
82 | |
83 | static void tw5864_interrupts_disable(struct tw5864_dev *dev) |
84 | { |
85 | unsigned long flags; |
86 | |
87 | spin_lock_irqsave(&dev->slock, flags); |
88 | dev->irqmask = 0; |
89 | tw5864_irqmask_apply(dev); |
90 | spin_unlock_irqrestore(lock: &dev->slock, flags); |
91 | } |
92 | |
93 | static void tw5864_timer_isr(struct tw5864_dev *dev); |
94 | static void tw5864_h264_isr(struct tw5864_dev *dev); |
95 | |
96 | static irqreturn_t tw5864_isr(int irq, void *dev_id) |
97 | { |
98 | struct tw5864_dev *dev = dev_id; |
99 | u32 status; |
100 | |
101 | status = tw_readl(TW5864_INTR_STATUS_L) | |
102 | tw_readl(TW5864_INTR_STATUS_H) << 16; |
103 | if (!status) |
104 | return IRQ_NONE; |
105 | |
106 | tw_writel(TW5864_INTR_CLR_L, 0xffff); |
107 | tw_writel(TW5864_INTR_CLR_H, 0xffff); |
108 | |
109 | if (status & TW5864_INTR_VLC_DONE) |
110 | tw5864_h264_isr(dev); |
111 | |
112 | if (status & TW5864_INTR_TIMER) |
113 | tw5864_timer_isr(dev); |
114 | |
115 | if (!(status & (TW5864_INTR_TIMER | TW5864_INTR_VLC_DONE))) { |
116 | dev_dbg(&dev->pci->dev, "Unknown interrupt, status 0x%08X\n" , |
117 | status); |
118 | } |
119 | |
120 | return IRQ_HANDLED; |
121 | } |
122 | |
123 | static void tw5864_h264_isr(struct tw5864_dev *dev) |
124 | { |
125 | int channel = tw_readl(TW5864_DSP) & TW5864_DSP_ENC_CHN; |
126 | struct tw5864_input *input = &dev->inputs[channel]; |
127 | int cur_frame_index, next_frame_index; |
128 | struct tw5864_h264_frame *cur_frame, *next_frame; |
129 | unsigned long flags; |
130 | |
131 | spin_lock_irqsave(&dev->slock, flags); |
132 | |
133 | cur_frame_index = dev->h264_buf_w_index; |
134 | next_frame_index = (cur_frame_index + 1) % H264_BUF_CNT; |
135 | cur_frame = &dev->h264_buf[cur_frame_index]; |
136 | next_frame = &dev->h264_buf[next_frame_index]; |
137 | |
138 | if (next_frame_index != dev->h264_buf_r_index) { |
139 | cur_frame->vlc_len = tw_readl(TW5864_VLC_LENGTH) << 2; |
140 | cur_frame->checksum = tw_readl(TW5864_VLC_CRC_REG); |
141 | cur_frame->input = input; |
142 | cur_frame->timestamp = ktime_get_ns(); |
143 | cur_frame->seqno = input->frame_seqno; |
144 | cur_frame->gop_seqno = input->frame_gop_seqno; |
145 | |
146 | dev->h264_buf_w_index = next_frame_index; |
147 | tasklet_schedule(t: &dev->tasklet); |
148 | |
149 | cur_frame = next_frame; |
150 | |
151 | spin_lock(lock: &input->slock); |
152 | input->frame_seqno++; |
153 | input->frame_gop_seqno++; |
154 | if (input->frame_gop_seqno >= input->gop) |
155 | input->frame_gop_seqno = 0; |
156 | spin_unlock(lock: &input->slock); |
157 | } else { |
158 | dev_err(&dev->pci->dev, |
159 | "Skipped frame on input %d because all buffers busy\n" , |
160 | channel); |
161 | } |
162 | |
163 | dev->encoder_busy = 0; |
164 | |
165 | spin_unlock_irqrestore(lock: &dev->slock, flags); |
166 | |
167 | tw_writel(TW5864_VLC_STREAM_BASE_ADDR, cur_frame->vlc.dma_addr); |
168 | tw_writel(TW5864_MV_STREAM_BASE_ADDR, cur_frame->mv.dma_addr); |
169 | |
170 | /* Additional ack for this interrupt */ |
171 | tw_writel(TW5864_VLC_DSP_INTR, 0x00000001); |
172 | tw_writel(TW5864_PCI_INTR_STATUS, TW5864_VLC_DONE_INTR); |
173 | } |
174 | |
175 | static void tw5864_input_deadline_update(struct tw5864_input *input) |
176 | { |
177 | input->new_frame_deadline = jiffies + msecs_to_jiffies(m: 1000); |
178 | } |
179 | |
180 | static void tw5864_timer_isr(struct tw5864_dev *dev) |
181 | { |
182 | unsigned long flags; |
183 | int i; |
184 | int encoder_busy; |
185 | |
186 | /* Additional ack for this interrupt */ |
187 | tw_writel(TW5864_PCI_INTR_STATUS, TW5864_TIMER_INTR); |
188 | |
189 | spin_lock_irqsave(&dev->slock, flags); |
190 | encoder_busy = dev->encoder_busy; |
191 | spin_unlock_irqrestore(lock: &dev->slock, flags); |
192 | |
193 | if (encoder_busy) |
194 | return; |
195 | |
196 | /* |
197 | * Traversing inputs in round-robin fashion, starting from next to the |
198 | * last processed one |
199 | */ |
200 | for (i = 0; i < TW5864_INPUTS; i++) { |
201 | int next_input = (i + dev->next_input) % TW5864_INPUTS; |
202 | struct tw5864_input *input = &dev->inputs[next_input]; |
203 | int raw_buf_id; /* id of internal buf with last raw frame */ |
204 | |
205 | spin_lock_irqsave(&input->slock, flags); |
206 | if (!input->enabled) |
207 | goto next; |
208 | |
209 | /* Check if new raw frame is available */ |
210 | raw_buf_id = tw_mask_shift_readl(TW5864_SENIF_ORG_FRM_PTR1, 0x3, |
211 | 2 * input->nr); |
212 | |
213 | if (input->buf_id != raw_buf_id) { |
214 | input->buf_id = raw_buf_id; |
215 | tw5864_input_deadline_update(input); |
216 | spin_unlock_irqrestore(lock: &input->slock, flags); |
217 | |
218 | spin_lock_irqsave(&dev->slock, flags); |
219 | dev->encoder_busy = 1; |
220 | dev->next_input = (next_input + 1) % TW5864_INPUTS; |
221 | spin_unlock_irqrestore(lock: &dev->slock, flags); |
222 | |
223 | tw5864_request_encoded_frame(input); |
224 | break; |
225 | } |
226 | |
227 | /* No new raw frame; check if channel is stuck */ |
228 | if (time_is_after_jiffies(input->new_frame_deadline)) { |
229 | /* If stuck, request new raw frames again */ |
230 | tw_mask_shift_writel(TW5864_ENC_BUF_PTR_REC1, 0x3, |
231 | 2 * input->nr, input->buf_id + 3); |
232 | tw5864_input_deadline_update(input); |
233 | } |
234 | next: |
235 | spin_unlock_irqrestore(lock: &input->slock, flags); |
236 | } |
237 | } |
238 | |
239 | static int tw5864_initdev(struct pci_dev *pci_dev, |
240 | const struct pci_device_id *pci_id) |
241 | { |
242 | struct tw5864_dev *dev; |
243 | int err; |
244 | |
245 | dev = devm_kzalloc(dev: &pci_dev->dev, size: sizeof(*dev), GFP_KERNEL); |
246 | if (!dev) |
247 | return -ENOMEM; |
248 | |
249 | snprintf(buf: dev->name, size: sizeof(dev->name), fmt: "tw5864:%s" , pci_name(pdev: pci_dev)); |
250 | |
251 | err = v4l2_device_register(dev: &pci_dev->dev, v4l2_dev: &dev->v4l2_dev); |
252 | if (err) |
253 | return err; |
254 | |
255 | /* pci init */ |
256 | dev->pci = pci_dev; |
257 | err = pcim_enable_device(pdev: pci_dev); |
258 | if (err) { |
259 | dev_err(&dev->pci->dev, "pcim_enable_device() failed\n" ); |
260 | goto unreg_v4l2; |
261 | } |
262 | |
263 | pci_set_master(dev: pci_dev); |
264 | |
265 | err = dma_set_mask(dev: &pci_dev->dev, DMA_BIT_MASK(32)); |
266 | if (err) { |
267 | dev_err(&dev->pci->dev, "32 bit PCI DMA is not supported\n" ); |
268 | goto unreg_v4l2; |
269 | } |
270 | |
271 | /* get mmio */ |
272 | err = pcim_iomap_regions(pdev: pci_dev, BIT(0), name: dev->name); |
273 | if (err) { |
274 | dev_err(&dev->pci->dev, "Cannot request regions for MMIO\n" ); |
275 | goto unreg_v4l2; |
276 | } |
277 | dev->mmio = pcim_iomap_table(pdev: pci_dev)[0]; |
278 | |
279 | spin_lock_init(&dev->slock); |
280 | |
281 | dev_info(&pci_dev->dev, "TW5864 hardware version: %04x\n" , |
282 | tw_readl(TW5864_HW_VERSION)); |
283 | dev_info(&pci_dev->dev, "TW5864 H.264 core version: %04x:%04x\n" , |
284 | tw_readl(TW5864_H264REV), |
285 | tw_readl(TW5864_UNDECLARED_H264REV_PART2)); |
286 | |
287 | err = tw5864_video_init(dev, video_nr); |
288 | if (err) |
289 | goto unreg_v4l2; |
290 | |
291 | /* get irq */ |
292 | err = devm_request_irq(dev: &pci_dev->dev, irq: pci_dev->irq, handler: tw5864_isr, |
293 | IRQF_SHARED, devname: "tw5864" , dev_id: dev); |
294 | if (err < 0) { |
295 | dev_err(&dev->pci->dev, "can't get IRQ %d\n" , pci_dev->irq); |
296 | goto fini_video; |
297 | } |
298 | |
299 | dev_info(&pci_dev->dev, "Note: there are known video quality issues. For details\n" ); |
300 | dev_info(&pci_dev->dev, "see the comment in drivers/media/pci/tw5864/tw5864-core.c.\n" ); |
301 | |
302 | return 0; |
303 | |
304 | fini_video: |
305 | tw5864_video_fini(dev); |
306 | unreg_v4l2: |
307 | v4l2_device_unregister(v4l2_dev: &dev->v4l2_dev); |
308 | return err; |
309 | } |
310 | |
311 | static void tw5864_finidev(struct pci_dev *pci_dev) |
312 | { |
313 | struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev: pci_dev); |
314 | struct tw5864_dev *dev = |
315 | container_of(v4l2_dev, struct tw5864_dev, v4l2_dev); |
316 | |
317 | /* shutdown subsystems */ |
318 | tw5864_interrupts_disable(dev); |
319 | |
320 | /* unregister */ |
321 | tw5864_video_fini(dev); |
322 | |
323 | v4l2_device_unregister(v4l2_dev: &dev->v4l2_dev); |
324 | } |
325 | |
326 | static struct pci_driver tw5864_pci_driver = { |
327 | .name = "tw5864" , |
328 | .id_table = tw5864_pci_tbl, |
329 | .probe = tw5864_initdev, |
330 | .remove = tw5864_finidev, |
331 | }; |
332 | |
333 | module_pci_driver(tw5864_pci_driver); |
334 | |