[ARM/tegra] Add Tegra3 support
[linux-3.10.git] / arch / arm / mach-tegra / sysfs-dcc.c
1 /*
2  * Copyright (c) 2010 NVIDIA Corporation.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  *
11  * Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  *
15  * Neither the name of the NVIDIA Corporation nor the names of its contributors
16  * may be used to endorse or promote products derived from this software
17  * without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  *
31  */
32
33 #include <linux/module.h>
34 #include <linux/kernel.h>
35 #include <linux/spinlock.h>
36 #include <linux/sysfs.h>
37 #include <linux/workqueue.h>
38 #include <linux/kobject.h>
39 #include "nvos.h"
40
41 #define DCC_TIMEOUT_US      100000      /* Delay time for DCC timeout (in US) */
42 #define CP14_DSCR_WDTRFULL  0x20000000  /* Write Data Transfer Register Full */
43 #define SYSFS_DCC_DEBUG_PRINTS 0        /* Set non-zero to enable debug prints */
44
45 #if SYSFS_DCC_DEBUG_PRINTS
46 #define DEBUG_DCC(x) printk x
47 #else
48 #define DEBUG_DCC(x)
49 #endif
50
51 static int DebuggerConnected = 0;  /* -1=not connected, 0=unknown, 1=connected */
52 static struct kobject *nvdcc_kobj;
53 static spinlock_t dcc_lock;
54 static struct list_head dcc_list;
55
56 static ssize_t sysfsdcc_show(struct kobject *kobj,
57                 struct kobj_attribute *attr, char *buf);
58
59 static ssize_t sysfsdcc_store(struct kobject *kobj,
60                 struct kobj_attribute *attr, const char *buf, size_t count);
61
62
63 static struct kobj_attribute nvdcc_attr =
64                 __ATTR(dcc0, 0222, sysfsdcc_show, sysfsdcc_store);
65
66 static int write_to_dcc(u32 c)
67 {
68         volatile NvU32 dscr;
69
70         /* Have we already determined that there is no debugger connected? */
71         if (DebuggerConnected < 0)
72         {
73                 return -ENXIO;
74         }
75
76         /* Read the DSCR. */
77         asm volatile ("mrc p14, 0, %0, c0, c1, 0" : "=r" (dscr) : : "cc");
78
79         /* If DSCR Bit 29 (wDTRFull) is set there is data in the write
80          * register. If it stays there for than the DCC_TIMEOUT_US
81          * period, ignore this write and disable further DCC accesses. */
82         if (dscr & CP14_DSCR_WDTRFULL)
83         {
84                 NvU64 start  = NvOsGetTimeUS();
85                 NvU64 end    = start + DCC_TIMEOUT_US;
86                 NvU64 offset = (end > start) ? 0 : 0 - start;
87                 NvU64 now;
88
89                 for (;;)
90                 {
91                         /* Re-read the DSCR. */
92                         asm volatile ("mrc p14, 0, %0, c0, c1, 0" : "=r" (dscr) : : "cc");
93
94                         /* Previous data still there? */
95                         if (dscr & CP14_DSCR_WDTRFULL)
96                         {
97                                 if (end > start)
98                                 {
99                                         now = NvOsGetTimeUS();
100
101                                         if ((now >= end) || (now < start))
102                                         {
103                                                 goto fail;
104                                         }
105                                 }
106                                 else
107                                 {
108                                         now = offset + NvOsGetTimeUS();
109
110                                         if (now >= (end + offset))
111                                         {
112                                                 goto fail;
113                                         }
114                                 }
115                         }
116                         else
117                         {
118                                 if (DebuggerConnected == 0) {
119                                         /* Debugger connected */
120                                         spin_lock(&dcc_lock);
121                                         DebuggerConnected = 1;
122                                         spin_unlock(&dcc_lock);
123                                 }
124                                 break;
125                         }
126                 }
127         }
128
129         // Write the data into the DCC output register
130         asm volatile ("mcr p14, 0, %0, c0, c5, 0" : : "r" (c) : "cc");
131         return 0;
132
133 fail:
134         /* No debugged connected -- disable DCC */
135         spin_lock(&dcc_lock);
136         DebuggerConnected = -1;
137         spin_unlock(&dcc_lock);
138         return -ENXIO;
139 }
140
141
142 struct tegra_dcc_req {
143         struct list_head node;
144
145         const char *pBuf;
146         unsigned int size;
147 };
148
149 struct dcc_action {
150         struct tegra_dcc_req req;
151         struct work_struct work;
152         struct list_head node;
153 };
154
155
156 static void dcc_writer(struct work_struct *work)
157 {
158         struct dcc_action *action = container_of(work, struct dcc_action, work);
159         const char *p;
160
161         DEBUG_DCC(("+dcc_writer\n"));
162
163         spin_lock(&dcc_lock);
164         list_del(&action->req.node);
165         spin_unlock(&dcc_lock);
166
167         p = action->req.pBuf;
168         if (p)
169                 while ((p < &(action->req.pBuf[action->req.size])) && (*p))
170                         if (write_to_dcc(*p++))
171                                 break;
172
173         kfree(action->req.pBuf);
174         kfree(action);
175
176         DEBUG_DCC(("-dcc_writer\n"));
177 }
178
179 static ssize_t sysfsdcc_show(struct kobject *kobj,
180                 struct kobj_attribute *attr, char *buf)
181 {
182         DEBUG_DCC(("!sysfsdcc_show\n"));
183         return -EACCES;
184 }
185
186 static ssize_t sysfsdcc_store(struct kobject *kobj,
187         struct kobj_attribute *attr, const char *buf, size_t count)
188 {
189         struct dcc_action *action;
190         char *pBuf;
191         ssize_t ret = count;
192
193         DEBUG_DCC(("+sysfsdcc_store: %p, %d\n", buf, count));
194
195         if (!buf || !count) {
196                 ret = -EINVAL;
197                 goto fail;
198         }
199
200         pBuf = kmalloc(count+1, GFP_KERNEL);
201         if (!pBuf) {
202                 pr_debug("%s: insufficient memory\n", __func__);
203                 ret = -ENOMEM;
204                 goto fail;
205         }
206
207         action = kzalloc(sizeof(*action), GFP_KERNEL);
208         if (!action) {
209                 kfree(pBuf);
210                 pr_debug("%s: insufficient memory\n", __func__);
211                 ret = -ENOMEM;
212                 goto fail;
213         }
214
215         strncpy(pBuf, buf, count);
216         pBuf[count] = '\0';
217         action->req.pBuf = pBuf;
218         action->req.size = count;
219
220         INIT_WORK(&action->work, dcc_writer);
221
222         spin_lock(&dcc_lock);
223         list_add_tail(&action->req.node, &dcc_list);
224         spin_unlock(&dcc_lock);
225
226         /* DCC writes can only be performed from CPU0 */
227         schedule_work_on(0, &action->work);
228
229 fail:
230         DEBUG_DCC(("-sysfsdcc_store: %d\n", count));
231         return ret;
232 }
233
234 static int __init sysfsdcc_init(void)
235 {
236         spin_lock_init(&dcc_lock);
237         INIT_LIST_HEAD(&dcc_list);
238
239         DEBUG_DCC(("+sysfsdcc_init\n"));
240         nvdcc_kobj = kobject_create_and_add("dcc", kernel_kobj);
241
242         if (sysfs_create_file(nvdcc_kobj, &nvdcc_attr.attr))
243         {
244                 DEBUG_DCC(("DCC: sysfs_create_file failed!\n"));
245                 return -ENXIO;
246         }
247
248         DEBUG_DCC(("-sysfsdcc_init\n"));
249         return 0;
250 }
251
252 static void __exit sysfsdcc_exit(void)
253 {
254         DEBUG_DCC(("+sysfsdcc_exit\n"));
255         sysfs_remove_file(nvdcc_kobj, &nvdcc_attr.attr);
256         kobject_del(nvdcc_kobj);
257         DEBUG_DCC(("-sysfsdcc_exit\n"));
258 }
259
260 module_init(sysfsdcc_init);
261 module_exit(sysfsdcc_exit);
262 MODULE_LICENSE("GPL");