Blackfin arch: Use DTEST rather than DMA to poke at L1 SRAM during exception context
[linux-2.6.git] / arch / blackfin / mm / isram-driver.c
1 /*
2  * Description: Instruction SRAM accessor functions for the Blackfin
3  *
4  * Copyright 2008 Analog Devices Inc.
5  *
6  * Bugs: Enter bugs at http://blackfin.uclinux.org/
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  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, see the file COPYING, or write
15  * to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include <linux/module.h>
20 #include <linux/kernel.h>
21 #include <linux/types.h>
22 #include <linux/spinlock.h>
23 #include <linux/sched.h>
24
25 #include <asm/blackfin.h>
26
27 /*
28  * IMPORTANT WARNING ABOUT THESE FUNCTIONS
29  *
30  * The emulator will not function correctly if a write command is left in
31  * ITEST_COMMAND or DTEST_COMMAND AND access to cache memory is needed by
32  * the emulator. To avoid such problems, ensure that both ITEST_COMMAND
33  * and DTEST_COMMAND are zero when exiting these functions.
34  */
35
36
37 /*
38  * On the Blackfin, L1 instruction sram (which operates at core speeds) can not
39  * be accessed by a normal core load, so we need to go through a few hoops to
40  * read/write it.
41  * To try to make it easier - we export a memcpy interface, where either src or
42  * dest can be in this special L1 memory area.
43  * The low level read/write functions should not be exposed to the rest of the
44  * kernel, since they operate on 64-bit data, and need specific address alignment
45  */
46
47 static DEFINE_SPINLOCK(dtest_lock);
48
49 /* Takes a void pointer */
50 #define IADDR2DTEST(x) \
51         ({ unsigned long __addr = (unsigned long)(x); \
52                 (__addr & 0x47F8)        | /* address bits 14 & 10:3 */ \
53                 (__addr & 0x0800) << 15  | /* address bit  11        */ \
54                 (__addr  & 0x3000) << 4  | /* address bits 13:12     */ \
55                 (__addr  & 0x8000) << 8  | /* address bit  15        */ \
56                 (0x1000004);               /* isram access           */ \
57         })
58
59 /* Takes a pointer, and returns the offset (in bits) which things should be shifted */
60 #define ADDR2OFFSET(x) ((((unsigned long)(x)) & 0x7) * 8)
61
62 /* Takes a pointer, determines if it is the last byte in the isram 64-bit data type */
63 #define ADDR2LAST(x) ((((unsigned long)x) & 0x7) == 0x7)
64
65 static void isram_write(const void *addr, uint64_t data)
66 {
67         uint32_t cmd;
68         unsigned long flags;
69
70         if (addr >= (void *)(L1_CODE_START + L1_CODE_LENGTH))
71                 return;
72
73         cmd = IADDR2DTEST(addr) | 1;             /* write */
74
75         /*
76          * Writes to DTEST_DATA[0:1] need to be atomic with write to DTEST_COMMAND
77          * While in exception context - atomicity is guaranteed or double fault
78          */
79         spin_lock_irqsave(&dtest_lock, flags);
80
81         bfin_write_DTEST_DATA0(data & 0xFFFFFFFF);
82         bfin_write_DTEST_DATA1(data >> 32);
83
84         /* use the builtin, since interrupts are already turned off */
85         __builtin_bfin_csync();
86         bfin_write_DTEST_COMMAND(cmd);
87         __builtin_bfin_csync();
88
89         bfin_write_DTEST_COMMAND(0);
90         __builtin_bfin_csync();
91
92         spin_unlock_irqrestore(&dtest_lock, flags);
93 }
94
95 static uint64_t isram_read(const void *addr)
96 {
97         uint32_t cmd;
98         unsigned long flags;
99         uint64_t ret;
100
101         if (addr > (void *)(L1_CODE_START + L1_CODE_LENGTH))
102                 return 0;
103
104         cmd = IADDR2DTEST(addr) | 0;              /* read */
105
106         /*
107          * Reads of DTEST_DATA[0:1] need to be atomic with write to DTEST_COMMAND
108          * While in exception context - atomicity is guaranteed or double fault
109          */
110         spin_lock_irqsave(&dtest_lock, flags);
111         /* use the builtin, since interrupts are already turned off */
112         __builtin_bfin_csync();
113         bfin_write_DTEST_COMMAND(cmd);
114         __builtin_bfin_csync();
115         ret = bfin_read_DTEST_DATA0() | ((uint64_t)bfin_read_DTEST_DATA1() << 32);
116
117         bfin_write_DTEST_COMMAND(0);
118         __builtin_bfin_csync();
119         spin_unlock_irqrestore(&dtest_lock, flags);
120
121         return ret;
122 }
123
124 static bool isram_check_addr(const void *addr, size_t n)
125 {
126         if ((addr >= (void *)L1_CODE_START) &&
127             (addr < (void *)(L1_CODE_START + L1_CODE_LENGTH))) {
128                 if ((addr + n) >= (void *)(L1_CODE_START + L1_CODE_LENGTH)) {
129                         show_stack(NULL, NULL);
130                         printk(KERN_ERR "isram_memcpy: copy involving %p length "
131                                         "(%zu) too long\n", addr, n);
132                 }
133                 return true;
134         }
135         return false;
136 }
137
138 /*
139  * The isram_memcpy() function copies n bytes from memory area src to memory area dest.
140  * The isram_memcpy() function returns a pointer to dest.
141  * Either dest or src can be in L1 instruction sram.
142  */
143 void *isram_memcpy(void *dest, const void *src, size_t n)
144 {
145         uint64_t data_in = 0, data_out = 0;
146         size_t count;
147         bool dest_in_l1, src_in_l1, need_data, put_data;
148         unsigned char byte, *src_byte, *dest_byte;
149
150         src_byte = (unsigned char *)src;
151         dest_byte = (unsigned char *)dest;
152
153         dest_in_l1 = isram_check_addr(dest, n);
154         src_in_l1 = isram_check_addr(src, n);
155
156         need_data = true;
157         put_data = true;
158         for (count = 0; count < n; count++) {
159                 if (src_in_l1) {
160                         if (need_data) {
161                                 data_in = isram_read(src + count);
162                                 need_data = false;
163                         }
164
165                         if (ADDR2LAST(src + count))
166                                 need_data = true;
167
168                         byte = (unsigned char)((data_in >> ADDR2OFFSET(src + count)) & 0xff);
169
170                 } else {
171                         /* src is in L2 or L3 - so just dereference*/
172                         byte = src_byte[count];
173                 }
174
175                 if (dest_in_l1) {
176                         if (put_data) {
177                                 data_out = isram_read(dest + count);
178                                 put_data = false;
179                         }
180
181                         data_out &= ~((uint64_t)0xff << ADDR2OFFSET(dest + count));
182                         data_out |= ((uint64_t)byte << ADDR2OFFSET(dest + count));
183
184                         if (ADDR2LAST(dest + count)) {
185                                 put_data = true;
186                                 isram_write(dest + count, data_out);
187                         }
188                 } else {
189                         /* dest in L2 or L3 - so just dereference */
190                         dest_byte[count] = byte;
191                 }
192         }
193
194         /* make sure we dump the last byte if necessary */
195         if (dest_in_l1 && !put_data)
196                 isram_write(dest + count, data_out);
197
198         return dest;
199 }
200 EXPORT_SYMBOL(isram_memcpy);
201