1 | /* |
2 | * Copyright (C) 2012 CERN (www.cern.ch) |
3 | * Author: Alessandro Rubini <rubini@gnudd.com> |
4 | * |
5 | * Released according to the GNU GPL, version 2 or any later version. |
6 | * |
7 | * This work is part of the White Rabbit project, a research effort led |
8 | * by CERN, the European Institute for Nuclear Research. |
9 | */ |
10 | #include <linux/module.h> |
11 | #include <linux/string.h> |
12 | #include <linux/firmware.h> |
13 | #include <linux/init.h> |
14 | #include <linux/fmc.h> |
15 | #include <asm/unaligned.h> |
16 | |
17 | /* |
18 | * This module uses the firmware loader to program the whole or part |
19 | * of the FMC eeprom. The meat is in the _run functions. However, no |
20 | * default file name is provided, to avoid accidental mishaps. Also, |
21 | * you must pass the busid argument |
22 | */ |
23 | static struct fmc_driver fwe_drv; |
24 | |
25 | FMC_PARAM_BUSID(fwe_drv); |
26 | |
27 | /* The "file=" is like the generic "gateware=" used elsewhere */ |
28 | static char *fwe_file[FMC_MAX_CARDS]; |
29 | static int fwe_file_n; |
30 | module_param_array_named(file, fwe_file, charp, &fwe_file_n, 0444); |
31 | |
32 | static int fwe_run_tlv(struct fmc_device *fmc, const struct firmware *fw, |
33 | int write) |
34 | { |
35 | const uint8_t *p = fw->data; |
36 | int len = fw->size; |
37 | uint16_t thislen, thisaddr; |
38 | int err; |
39 | |
40 | /* format is: 'w' addr16 len16 data... */ |
41 | while (len > 5) { |
42 | thisaddr = get_unaligned_le16(p+1); |
43 | thislen = get_unaligned_le16(p+3); |
44 | if (p[0] != 'w' || thislen + 5 > len) { |
45 | dev_err(&fmc->dev, "invalid tlv at offset %ti\n" , |
46 | p - fw->data); |
47 | return -EINVAL; |
48 | } |
49 | err = 0; |
50 | if (write) { |
51 | dev_info(&fmc->dev, "write %i bytes at 0x%04x\n" , |
52 | thislen, thisaddr); |
53 | err = fmc_write_ee(fmc, thisaddr, p + 5, thislen); |
54 | } |
55 | if (err < 0) { |
56 | dev_err(&fmc->dev, "write failure @0x%04x\n" , |
57 | thisaddr); |
58 | return err; |
59 | } |
60 | p += 5 + thislen; |
61 | len -= 5 + thislen; |
62 | } |
63 | if (write) |
64 | dev_info(&fmc->dev, "write_eeprom: success\n" ); |
65 | return 0; |
66 | } |
67 | |
68 | static int fwe_run_bin(struct fmc_device *fmc, const struct firmware *fw) |
69 | { |
70 | int ret; |
71 | |
72 | dev_info(&fmc->dev, "programming %zi bytes\n" , fw->size); |
73 | ret = fmc_write_ee(fmc, 0, (void *)fw->data, fw->size); |
74 | if (ret < 0) { |
75 | dev_info(&fmc->dev, "write_eeprom: error %i\n" , ret); |
76 | return ret; |
77 | } |
78 | dev_info(&fmc->dev, "write_eeprom: success\n" ); |
79 | return 0; |
80 | } |
81 | |
82 | static int fwe_run(struct fmc_device *fmc, const struct firmware *fw, char *s) |
83 | { |
84 | char *last4 = s + strlen(s) - 4; |
85 | int err; |
86 | |
87 | if (!strcmp(last4, ".bin" )) |
88 | return fwe_run_bin(fmc, fw); |
89 | if (!strcmp(last4, ".tlv" )) { |
90 | err = fwe_run_tlv(fmc, fw, 0); |
91 | if (!err) |
92 | err = fwe_run_tlv(fmc, fw, 1); |
93 | return err; |
94 | } |
95 | dev_err(&fmc->dev, "invalid file name \"%s\"\n" , s); |
96 | return -EINVAL; |
97 | } |
98 | |
99 | /* |
100 | * Programming is done at probe time. Morever, only those listed with |
101 | * busid= are programmed. |
102 | * card is probed for, only one is programmed. Unfortunately, it's |
103 | * difficult to know in advance when probing the first card if others |
104 | * are there. |
105 | */ |
106 | static int fwe_probe(struct fmc_device *fmc) |
107 | { |
108 | int err, index = 0; |
109 | const struct firmware *fw; |
110 | struct device *dev = &fmc->dev; |
111 | char *s; |
112 | |
113 | if (!fwe_drv.busid_n) { |
114 | dev_err(dev, "%s: no busid passed, refusing all cards\n" , |
115 | KBUILD_MODNAME); |
116 | return -ENODEV; |
117 | } |
118 | |
119 | index = fmc_validate(fmc, &fwe_drv); |
120 | if (index < 0) { |
121 | pr_err("%s: refusing device \"%s\"\n" , KBUILD_MODNAME, |
122 | dev_name(dev)); |
123 | return -ENODEV; |
124 | } |
125 | if (index >= fwe_file_n) { |
126 | pr_err("%s: no filename for device index %i\n" , |
127 | KBUILD_MODNAME, index); |
128 | return -ENODEV; |
129 | } |
130 | s = fwe_file[index]; |
131 | if (!s) { |
132 | pr_err("%s: no filename for \"%s\" not programming\n" , |
133 | KBUILD_MODNAME, dev_name(dev)); |
134 | return -ENOENT; |
135 | } |
136 | err = request_firmware(&fw, s, dev); |
137 | if (err < 0) { |
138 | dev_err(&fmc->dev, "request firmware \"%s\": error %i\n" , |
139 | s, err); |
140 | return err; |
141 | } |
142 | fwe_run(fmc, fw, s); |
143 | release_firmware(fw); |
144 | return 0; |
145 | } |
146 | |
147 | static int fwe_remove(struct fmc_device *fmc) |
148 | { |
149 | return 0; |
150 | } |
151 | |
152 | static struct fmc_driver fwe_drv = { |
153 | .version = FMC_VERSION, |
154 | .driver.name = KBUILD_MODNAME, |
155 | .probe = fwe_probe, |
156 | .remove = fwe_remove, |
157 | /* no table, as the current match just matches everything */ |
158 | }; |
159 | |
160 | static int fwe_init(void) |
161 | { |
162 | int ret; |
163 | |
164 | ret = fmc_driver_register(&fwe_drv); |
165 | return ret; |
166 | } |
167 | |
168 | static void fwe_exit(void) |
169 | { |
170 | fmc_driver_unregister(&fwe_drv); |
171 | } |
172 | |
173 | module_init(fwe_init); |
174 | module_exit(fwe_exit); |
175 | |
176 | MODULE_LICENSE("GPL" ); |
177 | |