diff options
Diffstat (limited to 'drivers/media/video/ivtv/ivtv-udma.c')
-rw-r--r-- | drivers/media/video/ivtv/ivtv-udma.c | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/drivers/media/video/ivtv/ivtv-udma.c b/drivers/media/video/ivtv/ivtv-udma.c new file mode 100644 index 000000000000..bd642e1aafc3 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-udma.c | |||
@@ -0,0 +1,200 @@ | |||
1 | /* | ||
2 | User DMA | ||
3 | |||
4 | Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> | ||
5 | Copyright (C) 2004 Chris Kennedy <c@groovy.org> | ||
6 | Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> | ||
7 | |||
8 | This program is free software; you can redistribute it and/or modify | ||
9 | it under the terms of the GNU General Public License as published by | ||
10 | the Free Software Foundation; either version 2 of the License, or | ||
11 | (at your option) any later version. | ||
12 | |||
13 | This program is distributed in the hope that it will be useful, | ||
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | GNU General Public License for more details. | ||
17 | |||
18 | You should have received a copy of the GNU General Public License | ||
19 | along with this program; if not, write to the Free Software | ||
20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
21 | */ | ||
22 | |||
23 | #include "ivtv-driver.h" | ||
24 | #include "ivtv-streams.h" | ||
25 | #include "ivtv-udma.h" | ||
26 | |||
27 | void ivtv_udma_get_page_info(struct ivtv_dma_page_info *dma_page, unsigned long first, unsigned long size) | ||
28 | { | ||
29 | dma_page->uaddr = first & PAGE_MASK; | ||
30 | dma_page->offset = first & ~PAGE_MASK; | ||
31 | dma_page->tail = 1 + ((first+size-1) & ~PAGE_MASK); | ||
32 | dma_page->first = (first & PAGE_MASK) >> PAGE_SHIFT; | ||
33 | dma_page->last = ((first+size-1) & PAGE_MASK) >> PAGE_SHIFT; | ||
34 | dma_page->page_count = dma_page->last - dma_page->first + 1; | ||
35 | if (dma_page->page_count == 1) dma_page->tail -= dma_page->offset; | ||
36 | } | ||
37 | |||
38 | int ivtv_udma_fill_sg_list (struct ivtv_user_dma *dma, struct ivtv_dma_page_info *dma_page, int map_offset) | ||
39 | { | ||
40 | int i, offset; | ||
41 | |||
42 | offset = dma_page->offset; | ||
43 | |||
44 | /* Fill SG Array with new values */ | ||
45 | for (i = 0; i < dma_page->page_count; i++) { | ||
46 | if (i == dma_page->page_count - 1) { | ||
47 | dma->SGlist[map_offset].length = dma_page->tail; | ||
48 | } | ||
49 | else { | ||
50 | dma->SGlist[map_offset].length = PAGE_SIZE - offset; | ||
51 | } | ||
52 | dma->SGlist[map_offset].offset = offset; | ||
53 | dma->SGlist[map_offset].page = dma->map[map_offset]; | ||
54 | offset = 0; | ||
55 | map_offset++; | ||
56 | } | ||
57 | return map_offset; | ||
58 | } | ||
59 | |||
60 | void ivtv_udma_fill_sg_array (struct ivtv_user_dma *dma, u32 buffer_offset, u32 buffer_offset_2, u32 split) { | ||
61 | int i; | ||
62 | struct scatterlist *sg; | ||
63 | |||
64 | for (i = 0, sg = dma->SGlist; i < dma->SG_length; i++, sg++) { | ||
65 | dma->SGarray[i].size = cpu_to_le32(sg_dma_len(sg)); | ||
66 | dma->SGarray[i].src = cpu_to_le32(sg_dma_address(sg)); | ||
67 | dma->SGarray[i].dst = cpu_to_le32(buffer_offset); | ||
68 | buffer_offset += sg_dma_len(sg); | ||
69 | |||
70 | split -= sg_dma_len(sg); | ||
71 | if (split == 0) | ||
72 | buffer_offset = buffer_offset_2; | ||
73 | } | ||
74 | } | ||
75 | |||
76 | /* User DMA Buffers */ | ||
77 | void ivtv_udma_alloc(struct ivtv *itv) | ||
78 | { | ||
79 | if (itv->udma.SG_handle == 0) { | ||
80 | /* Map DMA Page Array Buffer */ | ||
81 | itv->udma.SG_handle = pci_map_single(itv->dev, itv->udma.SGarray, | ||
82 | sizeof(itv->udma.SGarray), PCI_DMA_TODEVICE); | ||
83 | ivtv_udma_sync_for_cpu(itv); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | int ivtv_udma_setup(struct ivtv *itv, unsigned long ivtv_dest_addr, | ||
88 | void __user *userbuf, int size_in_bytes) | ||
89 | { | ||
90 | struct ivtv_dma_page_info user_dma; | ||
91 | struct ivtv_user_dma *dma = &itv->udma; | ||
92 | int err; | ||
93 | |||
94 | IVTV_DEBUG_DMA("ivtv_udma_setup, dst: 0x%08x\n", (unsigned int)ivtv_dest_addr); | ||
95 | |||
96 | /* Still in USE */ | ||
97 | if (dma->SG_length || dma->page_count) { | ||
98 | IVTV_DEBUG_WARN("ivtv_udma_setup: SG_length %d page_count %d still full?\n", | ||
99 | dma->SG_length, dma->page_count); | ||
100 | return -EBUSY; | ||
101 | } | ||
102 | |||
103 | ivtv_udma_get_page_info(&user_dma, (unsigned long)userbuf, size_in_bytes); | ||
104 | |||
105 | if (user_dma.page_count <= 0) { | ||
106 | IVTV_DEBUG_WARN("ivtv_udma_setup: Error %d page_count from %d bytes %d offset\n", | ||
107 | user_dma.page_count, size_in_bytes, user_dma.offset); | ||
108 | return -EINVAL; | ||
109 | } | ||
110 | |||
111 | /* Get user pages for DMA Xfer */ | ||
112 | down_read(¤t->mm->mmap_sem); | ||
113 | err = get_user_pages(current, current->mm, | ||
114 | user_dma.uaddr, user_dma.page_count, 0, 1, dma->map, NULL); | ||
115 | up_read(¤t->mm->mmap_sem); | ||
116 | |||
117 | if (user_dma.page_count != err) { | ||
118 | IVTV_DEBUG_WARN("failed to map user pages, returned %d instead of %d\n", | ||
119 | err, user_dma.page_count); | ||
120 | return -EINVAL; | ||
121 | } | ||
122 | |||
123 | dma->page_count = user_dma.page_count; | ||
124 | |||
125 | /* Fill SG List with new values */ | ||
126 | ivtv_udma_fill_sg_list(dma, &user_dma, 0); | ||
127 | |||
128 | /* Map SG List */ | ||
129 | dma->SG_length = pci_map_sg(itv->dev, dma->SGlist, dma->page_count, PCI_DMA_TODEVICE); | ||
130 | |||
131 | /* Fill SG Array with new values */ | ||
132 | ivtv_udma_fill_sg_array (dma, ivtv_dest_addr, 0, -1); | ||
133 | |||
134 | /* Tag SG Array with Interrupt Bit */ | ||
135 | dma->SGarray[dma->SG_length - 1].size |= cpu_to_le32(0x80000000); | ||
136 | |||
137 | ivtv_udma_sync_for_device(itv); | ||
138 | return dma->page_count; | ||
139 | } | ||
140 | |||
141 | void ivtv_udma_unmap(struct ivtv *itv) | ||
142 | { | ||
143 | struct ivtv_user_dma *dma = &itv->udma; | ||
144 | int i; | ||
145 | |||
146 | IVTV_DEBUG_INFO("ivtv_unmap_user_dma\n"); | ||
147 | |||
148 | /* Nothing to free */ | ||
149 | if (dma->page_count == 0) | ||
150 | return; | ||
151 | |||
152 | /* Unmap Scatterlist */ | ||
153 | if (dma->SG_length) { | ||
154 | pci_unmap_sg(itv->dev, dma->SGlist, dma->page_count, PCI_DMA_TODEVICE); | ||
155 | dma->SG_length = 0; | ||
156 | } | ||
157 | /* sync DMA */ | ||
158 | ivtv_udma_sync_for_cpu(itv); | ||
159 | |||
160 | /* Release User Pages */ | ||
161 | for (i = 0; i < dma->page_count; i++) { | ||
162 | put_page(dma->map[i]); | ||
163 | } | ||
164 | dma->page_count = 0; | ||
165 | } | ||
166 | |||
167 | void ivtv_udma_free(struct ivtv *itv) | ||
168 | { | ||
169 | /* Unmap SG Array */ | ||
170 | if (itv->udma.SG_handle) { | ||
171 | pci_unmap_single(itv->dev, itv->udma.SG_handle, | ||
172 | sizeof(itv->udma.SGarray), PCI_DMA_TODEVICE); | ||
173 | } | ||
174 | |||
175 | /* Unmap Scatterlist */ | ||
176 | if (itv->udma.SG_length) { | ||
177 | pci_unmap_sg(itv->dev, itv->udma.SGlist, itv->udma.page_count, PCI_DMA_TODEVICE); | ||
178 | } | ||
179 | } | ||
180 | |||
181 | void ivtv_udma_start(struct ivtv *itv) | ||
182 | { | ||
183 | IVTV_DEBUG_DMA("start UDMA\n"); | ||
184 | write_reg(itv->udma.SG_handle, IVTV_REG_DECDMAADDR); | ||
185 | write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x01, IVTV_REG_DMAXFER); | ||
186 | set_bit(IVTV_F_I_DMA, &itv->i_flags); | ||
187 | set_bit(IVTV_F_I_UDMA, &itv->i_flags); | ||
188 | } | ||
189 | |||
190 | void ivtv_udma_prepare(struct ivtv *itv) | ||
191 | { | ||
192 | unsigned long flags; | ||
193 | |||
194 | spin_lock_irqsave(&itv->dma_reg_lock, flags); | ||
195 | if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) | ||
196 | ivtv_udma_start(itv); | ||
197 | else | ||
198 | set_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags); | ||
199 | spin_unlock_irqrestore(&itv->dma_reg_lock, flags); | ||
200 | } | ||