aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/fmc
diff options
context:
space:
mode:
authorAlessandro Rubini <rubini@gnudd.com>2013-06-18 17:47:35 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-06-18 18:41:04 -0400
commit6c62a895e572145f8aa24f2040d1bb8eff473313 (patch)
tree4e6d1186c8491347c6c561c6ff65a5a738cdf3fb /drivers/fmc
parent022c674728f45ad22ce2bb5eb628ac9d3dbc3aea (diff)
FMC: add a software carrier driver
This fake carrier is designed to help FMC users understand how a carrier driver works, and to experiment the behaviour with EEPROM reprogramming (with a mezzanine driver commited later). This carrier can register up to 4 (fake) mezzanines. We have real carriers (both on PCI-E and VME), but they are bigger things and are not part of this submission. Signed-off-by: Alessandro Rubini <rubini@gnudd.com> Acked-by: Juan David Gonzalez Cobas <dcobas@cern.ch> Acked-by: Emilio G. Cota <cota@braap.org> Acked-by: Samuel Iglesias Gonsalvez <siglesias@igalia.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/fmc')
-rw-r--r--drivers/fmc/Kconfig11
-rw-r--r--drivers/fmc/Makefile2
-rw-r--r--drivers/fmc/fmc-fakedev.c355
3 files changed, 368 insertions, 0 deletions
diff --git a/drivers/fmc/Kconfig b/drivers/fmc/Kconfig
index e28790267c69..505e96b02a93 100644
--- a/drivers/fmc/Kconfig
+++ b/drivers/fmc/Kconfig
@@ -15,3 +15,14 @@ menuconfig FMC
15 The framework was born outside of the kernel and at this time 15 The framework was born outside of the kernel and at this time
16 the off-tree code base is more complete. Code and documentation 16 the off-tree code base is more complete. Code and documentation
17 is at git://ohwr.org/fmc-projects/fmc-bus.git . 17 is at git://ohwr.org/fmc-projects/fmc-bus.git .
18
19if FMC
20
21config FMC_FAKEDEV
22 tristate "FMC fake device (software testing)"
23 help
24 This is a fake carrier, bringing a default EEPROM content
25 that can be rewritten at run time and usef for matching
26 mezzanines.
27
28endif # FMC
diff --git a/drivers/fmc/Makefile b/drivers/fmc/Makefile
index df9893972a62..9832b79976b1 100644
--- a/drivers/fmc/Makefile
+++ b/drivers/fmc/Makefile
@@ -6,3 +6,5 @@ fmc-y += fmc-match.o
6fmc-y += fmc-sdb.o 6fmc-y += fmc-sdb.o
7fmc-y += fru-parse.o 7fmc-y += fru-parse.o
8fmc-y += fmc-dump.o 8fmc-y += fmc-dump.o
9
10obj-$(CONFIG_FMC_FAKEDEV) += fmc-fakedev.o
diff --git a/drivers/fmc/fmc-fakedev.c b/drivers/fmc/fmc-fakedev.c
new file mode 100644
index 000000000000..bec94ac0764c
--- /dev/null
+++ b/drivers/fmc/fmc-fakedev.c
@@ -0,0 +1,355 @@
1/*
2 * Copyright (C) 2012 CERN (www.cern.ch)
3 * Author: Alessandro Rubini <rubini@gnudd.com>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * The software is provided "as is"; the copyright holders disclaim
10 * all warranties and liabilities, to the extent permitted by
11 * applicable law.
12 */
13#include <linux/module.h>
14#include <linux/init.h>
15#include <linux/string.h>
16#include <linux/device.h>
17#include <linux/slab.h>
18#include <linux/firmware.h>
19#include <linux/workqueue.h>
20#include <linux/err.h>
21#include <linux/fmc.h>
22
23#define FF_EEPROM_SIZE 8192 /* The standard eeprom size */
24#define FF_MAX_MEZZANINES 4 /* Fakes a multi-mezzanine carrier */
25
26/* The user can pass up to 4 names of eeprom images to load */
27static char *ff_eeprom[FF_MAX_MEZZANINES];
28static int ff_nr_eeprom;
29module_param_array_named(eeprom, ff_eeprom, charp, &ff_nr_eeprom, 0444);
30
31/* The user can ask for a multi-mezzanine carrier, with the default eeprom */
32static int ff_nr_dev = 1;
33module_param_named(ndev, ff_nr_dev, int, 0444);
34
35
36/* Lazily, don't support the "standard" module parameters */
37
38/*
39 * Eeprom built from these commands:
40
41 ../fru-generator -v fake-vendor -n fake-design-for-testing \
42 -s 01234 -p none > IPMI-FRU
43
44 gensdbfs . ../fake-eeprom.bin
45*/
46static char ff_eeimg[FF_MAX_MEZZANINES][FF_EEPROM_SIZE] = {
47 {
48 0x01, 0x00, 0x00, 0x01, 0x00, 0x0c, 0x00, 0xf2, 0x01, 0x0b, 0x00, 0xb2,
49 0x86, 0x87, 0xcb, 0x66, 0x61, 0x6b, 0x65, 0x2d, 0x76, 0x65, 0x6e, 0x64,
50 0x6f, 0x72, 0xd7, 0x66, 0x61, 0x6b, 0x65, 0x2d, 0x64, 0x65, 0x73, 0x69,
51 0x67, 0x6e, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x69,
52 0x6e, 0x67, 0xc5, 0x30, 0x31, 0x32, 0x33, 0x34, 0xc4, 0x6e, 0x6f, 0x6e,
53 0x65, 0xda, 0x32, 0x30, 0x31, 0x32, 0x2d, 0x31, 0x31, 0x2d, 0x31, 0x39,
54 0x20, 0x32, 0x32, 0x3a, 0x34, 0x32, 0x3a, 0x33, 0x30, 0x2e, 0x30, 0x37,
55 0x34, 0x30, 0x35, 0x35, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87,
56 0x02, 0x02, 0x0d, 0xf7, 0xf8, 0x02, 0xb0, 0x04, 0x74, 0x04, 0xec, 0x04,
57 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x02, 0x02, 0x0d, 0x5c, 0x93, 0x01,
58 0x4a, 0x01, 0x39, 0x01, 0x5a, 0x01, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x0b,
59 0x02, 0x02, 0x0d, 0x63, 0x8c, 0x00, 0xfa, 0x00, 0xed, 0x00, 0x06, 0x01,
60 0x00, 0x00, 0x00, 0x00, 0xa0, 0x0f, 0x01, 0x02, 0x0d, 0xfb, 0xf5, 0x05,
61 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
62 0x01, 0x02, 0x0d, 0xfc, 0xf4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
63 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x0d, 0xfd, 0xf3, 0x03,
64 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
65 0xfa, 0x82, 0x0b, 0xea, 0x8f, 0xa2, 0x12, 0x00, 0x00, 0x1e, 0x44, 0x00,
66 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
67 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
68 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
69 0x00, 0x00, 0x00, 0x00, 0x53, 0x44, 0x42, 0x2d, 0x00, 0x03, 0x01, 0x01,
70 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
71 0x00, 0x00, 0x01, 0xc4, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61,
72 0x2e, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
73 0x2e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
74 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
75 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0,
76 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc4, 0x46, 0x69, 0x6c, 0x65,
77 0x44, 0x61, 0x74, 0x61, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, 0x00, 0x01,
78 0x00, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x20,
79 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01,
80 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
81 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf,
82 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x49, 0x50, 0x4d, 0x49,
83 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x49, 0x50, 0x4d, 0x49,
84 0x2d, 0x46, 0x52, 0x55, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
85 0x20, 0x20, 0x20, 0x01, 0x66, 0x61, 0x6b, 0x65, 0x0a,
86 },
87};
88
89struct ff_dev {
90 struct fmc_device *fmc[FF_MAX_MEZZANINES];
91 struct device dev;
92};
93
94static struct ff_dev *ff_current_dev; /* We have 1 carrier, 1 slot */
95
96static int ff_reprogram(struct fmc_device *fmc, struct fmc_driver *drv,
97 char *gw)
98{
99 const struct firmware *fw;
100 int ret;
101
102 if (!gw) {
103 /* program golden: success */
104 fmc->flags &= ~FMC_DEVICE_HAS_CUSTOM;
105 fmc->flags |= FMC_DEVICE_HAS_GOLDEN;
106 return 0;
107 }
108
109 dev_info(&fmc->dev, "reprogramming with %s\n", gw);
110 ret = request_firmware(&fw, gw, &fmc->dev);
111 if (ret < 0) {
112 dev_warn(&fmc->dev, "request firmware \"%s\": error %i\n",
113 gw, ret);
114 goto out;
115 }
116 fmc->flags &= ~FMC_DEVICE_HAS_GOLDEN;
117 fmc->flags |= FMC_DEVICE_HAS_CUSTOM;
118
119out:
120 release_firmware(fw);
121 return ret;
122}
123
124static int ff_irq_request(struct fmc_device *fmc, irq_handler_t handler,
125 char *name, int flags)
126{
127 return -EOPNOTSUPP;
128}
129
130/* FIXME: should also have some fake FMC GPIO mapping */
131
132
133/*
134 * This work function is called when we changed the eeprom. It removes the
135 * current fmc device and registers a new one, with different identifiers.
136 */
137static struct ff_dev *ff_dev_create(void); /* defined later */
138
139static void ff_work_fn(struct work_struct *work)
140{
141 struct ff_dev *ff = ff_current_dev;
142 int ret;
143
144 fmc_device_unregister_n(ff->fmc, ff_nr_dev);
145 device_unregister(&ff->dev);
146 ff_current_dev = NULL;
147
148 ff = ff_dev_create();
149 if (IS_ERR(ff)) {
150 pr_warning("%s: can't re-create FMC devices\n", __func__);
151 return;
152 }
153 ret = fmc_device_register_n(ff->fmc, ff_nr_dev);
154 if (ret < 0) {
155 dev_warn(&ff->dev, "can't re-register FMC devices\n");
156 device_unregister(&ff->dev);
157 return;
158 }
159
160 ff_current_dev = ff;
161}
162
163static DECLARE_DELAYED_WORK(ff_work, ff_work_fn);
164
165
166/* low-level i2c */
167static int ff_eeprom_read(struct fmc_device *fmc, uint32_t offset,
168 void *buf, size_t size)
169{
170 if (offset > FF_EEPROM_SIZE)
171 return -EINVAL;
172 if (offset + size > FF_EEPROM_SIZE)
173 size = FF_EEPROM_SIZE - offset;
174 memcpy(buf, fmc->eeprom + offset, size);
175 return size;
176}
177
178static int ff_eeprom_write(struct fmc_device *fmc, uint32_t offset,
179 const void *buf, size_t size)
180{
181 if (offset > FF_EEPROM_SIZE)
182 return -EINVAL;
183 if (offset + size > FF_EEPROM_SIZE)
184 size = FF_EEPROM_SIZE - offset;
185 dev_info(&fmc->dev, "write_eeprom: offset %i, size %zi\n",
186 (int)offset, size);
187 memcpy(fmc->eeprom + offset, buf, size);
188 schedule_delayed_work(&ff_work, HZ * 2); /* remove, replug, in 2s */
189 return size;
190}
191
192/* i2c operations for fmc */
193static int ff_read_ee(struct fmc_device *fmc, int pos, void *data, int len)
194{
195 if (!(fmc->flags & FMC_DEVICE_HAS_GOLDEN))
196 return -EOPNOTSUPP;
197 return ff_eeprom_read(fmc, pos, data, len);
198}
199
200static int ff_write_ee(struct fmc_device *fmc, int pos,
201 const void *data, int len)
202{
203 if (!(fmc->flags & FMC_DEVICE_HAS_GOLDEN))
204 return -EOPNOTSUPP;
205 return ff_eeprom_write(fmc, pos, data, len);
206}
207
208/* readl and writel do not do anything. Don't waste RAM with "base" */
209static uint32_t ff_readl(struct fmc_device *fmc, int offset)
210{
211 return 0;
212}
213
214static void ff_writel(struct fmc_device *fmc, uint32_t value, int offset)
215{
216 return;
217}
218
219/* validate is useful so fmc-write-eeprom will not reprogram every 2 seconds */
220static int ff_validate(struct fmc_device *fmc, struct fmc_driver *drv)
221{
222 int i;
223
224 if (!drv->busid_n)
225 return 0; /* everyhing is valid */
226 for (i = 0; i < drv->busid_n; i++)
227 if (drv->busid_val[i] == fmc->device_id)
228 return i;
229 return -ENOENT;
230}
231
232
233
234static struct fmc_operations ff_fmc_operations = {
235 .readl = ff_readl,
236 .writel = ff_writel,
237 .reprogram = ff_reprogram,
238 .irq_request = ff_irq_request,
239 .read_ee = ff_read_ee,
240 .write_ee = ff_write_ee,
241 .validate = ff_validate,
242};
243
244/* This device is kmalloced: release it */
245static void ff_dev_release(struct device *dev)
246{
247 struct ff_dev *ff = container_of(dev, struct ff_dev, dev);
248 kfree(ff);
249}
250
251static struct fmc_device ff_template_fmc = {
252 .version = FMC_VERSION,
253 .owner = THIS_MODULE,
254 .carrier_name = "fake-fmc-carrier",
255 .device_id = 0xf001, /* fool */
256 .eeprom_len = sizeof(ff_eeimg[0]),
257 .memlen = 0x1000, /* 4k, to show something */
258 .op = &ff_fmc_operations,
259 .hwdev = NULL, /* filled at creation time */
260 .flags = FMC_DEVICE_HAS_GOLDEN,
261};
262
263static struct ff_dev *ff_dev_create(void)
264{
265 struct ff_dev *ff;
266 struct fmc_device *fmc;
267 int i, ret;
268
269 ff = kzalloc(sizeof(*ff), GFP_KERNEL);
270 if (!ff)
271 return ERR_PTR(-ENOMEM);
272 dev_set_name(&ff->dev, "fake-fmc-carrier");
273 ff->dev.release = ff_dev_release;
274
275 ret = device_register(&ff->dev);
276 if (ret < 0) {
277 put_device(&ff->dev);
278 return ERR_PTR(ret);
279 }
280
281 /* Create fmc structures that refer to this new "hw" device */
282 for (i = 0; i < ff_nr_dev; i++) {
283 fmc = kmemdup(&ff_template_fmc, sizeof(ff_template_fmc),
284 GFP_KERNEL);
285 fmc->hwdev = &ff->dev;
286 fmc->carrier_data = ff;
287 fmc->nr_slots = ff_nr_dev;
288 /* the following fields are different for each slot */
289 fmc->eeprom = ff_eeimg[i];
290 fmc->eeprom_addr = 0x50 + 2 * i;
291 fmc->slot_id = i;
292 ff->fmc[i] = fmc;
293 /* increment the identifier, each must be different */
294 ff_template_fmc.device_id++;
295 }
296 return ff;
297}
298
299/* init and exit */
300static int ff_init(void)
301{
302 struct ff_dev *ff;
303 const struct firmware *fw;
304 int i, len, ret = 0;
305
306 /* Replicate the default eeprom for the max number of mezzanines */
307 for (i = 1; i < FF_MAX_MEZZANINES; i++)
308 memcpy(ff_eeimg[i], ff_eeimg[0], sizeof(ff_eeimg[0]));
309
310 if (ff_nr_eeprom > ff_nr_dev)
311 ff_nr_dev = ff_nr_eeprom;
312
313 ff = ff_dev_create();
314 if (IS_ERR(ff))
315 return PTR_ERR(ff);
316
317 /* If the user passed "eeprom=" as a parameter, fetch them */
318 for (i = 0; i < ff_nr_eeprom; i++) {
319 if (!strlen(ff_eeprom[i]))
320 continue;
321 ret = request_firmware(&fw, ff_eeprom[i], &ff->dev);
322 if (ret < 0) {
323 dev_err(&ff->dev, "Mezzanine %i: can't load \"%s\" "
324 "(error %i)\n", i, ff_eeprom[i], -ret);
325 } else {
326 len = min_t(size_t, fw->size, (size_t)FF_EEPROM_SIZE);
327 memcpy(ff_eeimg[i], fw->data, len);
328 release_firmware(fw);
329 dev_info(&ff->dev, "Mezzanine %i: eeprom \"%s\"\n", i,
330 ff_eeprom[i]);
331 }
332 }
333
334 ret = fmc_device_register_n(ff->fmc, ff_nr_dev);
335 if (ret) {
336 device_unregister(&ff->dev);
337 return ret;
338 }
339 ff_current_dev = ff;
340 return ret;
341}
342
343static void ff_exit(void)
344{
345 if (ff_current_dev) {
346 fmc_device_unregister_n(ff_current_dev->fmc, ff_nr_dev);
347 device_unregister(&ff_current_dev->dev);
348 }
349 cancel_delayed_work_sync(&ff_work);
350}
351
352module_init(ff_init);
353module_exit(ff_exit);
354
355MODULE_LICENSE("Dual BSD/GPL");