forked from Beckhoff/CCAT
-
Notifications
You must be signed in to change notification settings - Fork 0
/
update.c
345 lines (309 loc) · 10.3 KB
/
update.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// SPDX-License-Identifier: MIT
/**
Network Driver for Beckhoff CCAT communication controller
Copyright (C) 2014 - 2018 Beckhoff Automation GmbH & Co. KG
Author: Patrick Bruenn <[email protected]>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include "module.h"
MODULE_DESCRIPTION(DRV_DESCRIPTION);
MODULE_AUTHOR("Patrick Bruenn <[email protected]>");
MODULE_LICENSE("GPL and additional rights");
MODULE_VERSION(DRV_VERSION);
#define CCAT_DEVICES_MAX 5
#define CCAT_DATA_IN_4 0x038
#define CCAT_DATA_IN_N 0x7F0
#define CCAT_DATA_OUT_4 0x030
#define CCAT_DATA_BLOCK_SIZE (size_t)((CCAT_DATA_IN_N - CCAT_DATA_IN_4)/8)
#define CCAT_WRITE_BLOCK_SIZE 128
#define CCAT_FLASH_SIZE (size_t)0xE0000
/** FUNCTION_NAME CMD, CLOCKS */
#define CCAT_BULK_ERASE 0xE3, 8
#define CCAT_GET_PROM_ID 0xD5, 40
#define CCAT_READ_FLASH 0xC0, 32
#define CCAT_READ_STATUS 0xA0, 16
#define CCAT_WRITE_ENABLE 0x60, 8
#define CCAT_WRITE_FLASH 0x40, 32
/* from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits */
#define SWAP_BITS(B) \
((((B) * 0x0802LU & 0x22110LU) | ((B) * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16)
/**
* wait_until_busy_reset() - wait until the busy flag was reset
* @ioaddr: address of the CCAT Update function in PCI config space
*/
static inline void wait_until_busy_reset(void __iomem * const ioaddr)
{
wmb();
while (ioread8(ioaddr + 1)) {
schedule();
}
}
/**
* __ccat_update_cmd() - Helper to issue a FPGA flash command
* @ioaddr: address of the CCAT Update function in PCI config space
* @cmd: the command identifier
* @clocks: the number of clocks associated with the specified command
*
* no write memory barrier is called and the busy flag is not evaluated
*/
static inline void __ccat_update_cmd(void __iomem * const ioaddr, u8 cmd,
u16 clocks)
{
iowrite8((0xff00 & clocks) >> 8, ioaddr);
iowrite8(0x00ff & clocks, ioaddr + 0x8);
iowrite8(cmd, ioaddr + 0x10);
}
/**
* ccat_update_cmd() - Helper to issue a FPGA flash command
* @ioaddr: address of the CCAT Update function in PCI config space
* @cmd: the command identifier
* @clocks: the number of clocks associated with the specified command
*
* Triggers a full flash command cycle with write memory barrier and
* command activate. This call blocks until the busy flag is reset.
*/
static inline void ccat_update_cmd(void __iomem * const ioaddr, u8 cmd,
u16 clocks)
{
__ccat_update_cmd(ioaddr, cmd, clocks);
wmb();
iowrite8(0xff, ioaddr + 0x7f8);
wait_until_busy_reset(ioaddr);
}
/**
* ccat_update_cmd_addr() - Helper to issue a FPGA flash command with address parameter
* @ioaddr: address of the CCAT Update function in PCI config space
* @cmd: the command identifier
* @clocks: the number of clocks associated with the specified command
* @addr: 24 bit address associated with the specified command
*
* Triggers a full flash command cycle with write memory barrier and
* command activate. This call blocks until the busy flag is reset.
*/
static inline void ccat_update_cmd_addr(void __iomem * const ioaddr,
u8 cmd, u16 clocks, u32 addr)
{
const u8 addr_0 = SWAP_BITS(addr & 0xff);
const u8 addr_1 = SWAP_BITS((addr & 0xff00) >> 8);
const u8 addr_2 = SWAP_BITS((addr & 0xff0000) >> 16);
__ccat_update_cmd(ioaddr, cmd, clocks);
iowrite8(addr_2, ioaddr + 0x18);
iowrite8(addr_1, ioaddr + 0x20);
iowrite8(addr_0, ioaddr + 0x28);
wmb();
iowrite8(0xff, ioaddr + 0x7f8);
wait_until_busy_reset(ioaddr);
}
/**
* ccat_get_status() - Read CCAT Update status
* @ioaddr: address of the CCAT Update function in PCI config space
*
* Return: the current status of the CCAT Update function
*/
static u8 ccat_get_status(void __iomem * const ioaddr)
{
ccat_update_cmd(ioaddr, CCAT_READ_STATUS);
return ioread8(ioaddr + 0x20);
}
/**
* ccat_read_flash_block() - Read a block of CCAT configuration data from flash
* @ioaddr: address of the CCAT Update function in PCI config space
* @addr: 24 bit address of the block to read
* @len: number of bytes to read from this block, len <= CCAT_DATA_BLOCK_SIZE
* @buf: output buffer in user space
*
* Copies one block of configuration data from the CCAT FPGA's flash to
* the user space buffer.
* Note that the size of the FPGA's firmware is not known exactly so it
* is very possible that the overall buffer ends with a lot of 0xff.
*
* Return: the number of bytes copied
*/
static int ccat_read_flash_block(void __iomem * const ioaddr,
const u32 addr, const u16 len,
char __user * const buf)
{
u16 i;
const u16 clocks = 8 * len;
ccat_update_cmd_addr(ioaddr, CCAT_READ_FLASH + clocks, addr);
for (i = 0; i < len; i++) {
put_user(ioread8(ioaddr + CCAT_DATA_IN_4 + 8 * i), buf + i);
}
return len;
}
/**
* ccat_read_flash() - Read a chunk of CCAT configuration data from flash
* @ioaddr: address of the CCAT Update function in PCI config space
* @buf: output buffer in user space
* @len: number of bytes to read
* @off: offset in the configuration data
*
* Copies multiple blocks of configuration data from the CCAT FPGA's
* flash to the user space buffer.
*
* Return: the number of bytes copied
*/
static int ccat_read_flash(void __iomem * const ioaddr, char __user * buf,
u32 len, loff_t * off)
{
const loff_t start = *off;
while (len > CCAT_DATA_BLOCK_SIZE) {
*off +=
ccat_read_flash_block(ioaddr, *off, CCAT_DATA_BLOCK_SIZE,
buf);
buf += CCAT_DATA_BLOCK_SIZE;
len -= CCAT_DATA_BLOCK_SIZE;
}
*off += ccat_read_flash_block(ioaddr, *off, len, buf);
return *off - start;
}
/**
* ccat_wait_status_cleared() - wait until CCAT status is cleared
* @ioaddr: address of the CCAT Update function in PCI config space
*
* Blocks until bit 7 of the CCAT Update status is reset
*/
static void ccat_wait_status_cleared(void __iomem * const ioaddr)
{
u8 status;
do {
status = ccat_get_status(ioaddr);
} while (status & (1 << 7));
}
/**
* ccat_write_flash_block() - Write a block of CCAT configuration data to flash
* @ioaddr: address of the CCAT Update function in PCI config space
* @addr: 24 bit start address in the CCAT FPGA's flash
* @len: number of bytes to write in this block, len <= CCAT_WRITE_BLOCK_SIZE
* @buf: input buffer
*
* Copies one block of configuration data to the CCAT FPGA's flash
*
* Return: the number of bytes copied
*/
static int ccat_write_flash_block(void __iomem * const ioaddr,
const u32 addr, const u16 len,
const char *const buf)
{
const u16 clocks = 8 * len;
u16 i;
ccat_update_cmd(ioaddr, CCAT_WRITE_ENABLE);
for (i = 0; i < len; i++) {
iowrite8(buf[i], ioaddr + CCAT_DATA_OUT_4 + 8 * i);
}
ccat_update_cmd_addr(ioaddr, CCAT_WRITE_FLASH + clocks, addr);
ccat_wait_status_cleared(ioaddr);
return len;
}
/**
* ccat_write_flash() - Write a new CCAT configuration to FPGA's flash
* @update: a CCAT Update buffer containing the new FPGA configuration
*/
static void ccat_write_flash(const struct cdev_buffer *const buffer)
{
const char *buf = buffer->data;
u32 off = 0;
size_t len = buffer->size;
while (len > CCAT_WRITE_BLOCK_SIZE) {
ccat_write_flash_block(buffer->ccdev->ioaddr, off,
(u16) CCAT_WRITE_BLOCK_SIZE, buf);
off += CCAT_WRITE_BLOCK_SIZE;
buf += CCAT_WRITE_BLOCK_SIZE;
len -= CCAT_WRITE_BLOCK_SIZE;
}
ccat_write_flash_block(buffer->ccdev->ioaddr, off, (u16) len, buf);
}
static int ccat_update_release(struct inode *const i, struct file *const f)
{
const struct cdev_buffer *const buf = f->private_data;
void __iomem *ioaddr = buf->ccdev->ioaddr;
if (buf->size > 0) {
ccat_update_cmd(ioaddr, CCAT_WRITE_ENABLE);
ccat_update_cmd(ioaddr, CCAT_BULK_ERASE);
ccat_wait_status_cleared(ioaddr);
ccat_write_flash(buf);
}
return ccat_cdev_release(i, f);
}
/**
* ccat_update_read() - Read CCAT configuration data from flash
* @f: file handle previously initialized with ccat_update_open()
* @buf: buffer in user space provided for our data
* @len: length of the user space buffer
* @off: current offset of our file operation
*
* Copies data from the CCAT FPGA's configuration flash to user space.
* Note that the size of the FPGA's firmware is not known exactly so it
* is very possible that the overall buffer ends with a lot of 0xff.
*
* Return: the number of bytes written, or 0 if EOF reached
*/
static ssize_t ccat_update_read(struct file *const f, char __user * buf,
size_t len, loff_t * off)
{
struct cdev_buffer *buffer = f->private_data;
const size_t iosize = buffer->ccdev->iosize;
if (*off >= iosize) {
return 0;
}
len = min(len, (size_t) (iosize - *off));
return ccat_read_flash(buffer->ccdev->ioaddr, buf, len, off);
}
/**
* ccat_update_write() - Write data to the CCAT FPGA's configuration flash
* @f: file handle previously initialized with ccat_update_open()
* @buf: buffer in user space providing the new configuration data (from *.rbf)
* @len: length of the user space buffer
* @off: current offset in the configuration data
*
* Copies data from user space (possibly a *.rbf) to the CCAT FPGA's
* configuration flash.
*
* Return: the number of bytes written, or 0 if flash end is reached
*/
static ssize_t ccat_update_write(struct file *const f, const char __user * buf,
size_t len, loff_t * off)
{
struct cdev_buffer *const buffer = f->private_data;
if (*off + len > buffer->ccdev->iosize) {
return 0;
}
if (copy_from_user(buffer->data + *off, buf, len)) {
return -EFAULT;
}
*off += len;
buffer->size = *off;
return len;
}
static struct ccat_cdev dev_table[CCAT_DEVICES_MAX];
static struct ccat_class cdev_class = {
.count = CCAT_DEVICES_MAX,
.devices = dev_table,
.name = "ccat_update",
.fops = {
.owner = THIS_MODULE,
.open = ccat_cdev_open,
.release = ccat_update_release,
.read = ccat_update_read,
.write = ccat_update_write,
},
};
static int ccat_update_probe(struct platform_device *pdev)
{
struct ccat_function *const func = pdev->dev.platform_data;
static const u16 SUPPORTED_REVISION = 0x00;
if (SUPPORTED_REVISION != func->info.rev) {
pr_warn("CCAT Update rev. %d not supported\n", func->info.rev);
return -ENODEV;
}
return ccat_cdev_probe(func, &cdev_class, CCAT_FLASH_SIZE, NULL);
}
static struct platform_driver update_driver = {
.driver = {.name = "ccat_update"},
.probe = ccat_update_probe,
.remove = ccat_cdev_remove,
};
module_platform_driver(update_driver);