| /* | |
| * Copyright (C) 2012 CERN (www.cern.ch) | |
| * Author: Alessandro Rubini <rubini@gnudd.com> | |
| * | |
| * Released according to the GNU GPL, version 2 or any later version. | |
| * | |
| * This work is part of the White Rabbit project, a research effort led | |
| * by CERN, the European Institute for Nuclear Research. | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/string.h> | |
| #include <linux/firmware.h> | |
| #include <linux/init.h> | |
| #include <linux/fmc.h> | |
| #include <asm/unaligned.h> | |
| /* | |
| * This module uses the firmware loader to program the whole or part | |
| * of the FMC eeprom. The meat is in the _run functions. However, no | |
| * default file name is provided, to avoid accidental mishaps. Also, | |
| * you must pass the busid argument | |
| */ | |
| static struct fmc_driver fwe_drv; | |
| FMC_PARAM_BUSID(fwe_drv); | |
| /* The "file=" is like the generic "gateware=" used elsewhere */ | |
| static char *fwe_file[FMC_MAX_CARDS]; | |
| static int fwe_file_n; | |
| module_param_array_named(file, fwe_file, charp, &fwe_file_n, 0444); | |
| static int fwe_run_tlv(struct fmc_device *fmc, const struct firmware *fw, | |
| int write) | |
| { | |
| const uint8_t *p = fw->data; | |
| int len = fw->size; | |
| uint16_t thislen, thisaddr; | |
| int err; | |
| /* format is: 'w' addr16 len16 data... */ | |
| while (len > 5) { | |
| thisaddr = get_unaligned_le16(p+1); | |
| thislen = get_unaligned_le16(p+3); | |
| if (p[0] != 'w' || thislen + 5 > len) { | |
| dev_err(&fmc->dev, "invalid tlv at offset %ti\n", | |
| p - fw->data); | |
| return -EINVAL; | |
| } | |
| err = 0; | |
| if (write) { | |
| dev_info(&fmc->dev, "write %i bytes at 0x%04x\n", | |
| thislen, thisaddr); | |
| err = fmc_write_ee(fmc, thisaddr, p + 5, thislen); | |
| } | |
| if (err < 0) { | |
| dev_err(&fmc->dev, "write failure @0x%04x\n", | |
| thisaddr); | |
| return err; | |
| } | |
| p += 5 + thislen; | |
| len -= 5 + thislen; | |
| } | |
| if (write) | |
| dev_info(&fmc->dev, "write_eeprom: success\n"); | |
| return 0; | |
| } | |
| static int fwe_run_bin(struct fmc_device *fmc, const struct firmware *fw) | |
| { | |
| int ret; | |
| dev_info(&fmc->dev, "programming %zi bytes\n", fw->size); | |
| ret = fmc_write_ee(fmc, 0, (void *)fw->data, fw->size); | |
| if (ret < 0) { | |
| dev_info(&fmc->dev, "write_eeprom: error %i\n", ret); | |
| return ret; | |
| } | |
| dev_info(&fmc->dev, "write_eeprom: success\n"); | |
| return 0; | |
| } | |
| static int fwe_run(struct fmc_device *fmc, const struct firmware *fw, char *s) | |
| { | |
| char *last4 = s + strlen(s) - 4; | |
| int err; | |
| if (!strcmp(last4, ".bin")) | |
| return fwe_run_bin(fmc, fw); | |
| if (!strcmp(last4, ".tlv")) { | |
| err = fwe_run_tlv(fmc, fw, 0); | |
| if (!err) | |
| err = fwe_run_tlv(fmc, fw, 1); | |
| return err; | |
| } | |
| dev_err(&fmc->dev, "invalid file name \"%s\"\n", s); | |
| return -EINVAL; | |
| } | |
| /* | |
| * Programming is done at probe time. Morever, only those listed with | |
| * busid= are programmed. | |
| * card is probed for, only one is programmed. Unfortunately, it's | |
| * difficult to know in advance when probing the first card if others | |
| * are there. | |
| */ | |
| static int fwe_probe(struct fmc_device *fmc) | |
| { | |
| int err, index = 0; | |
| const struct firmware *fw; | |
| struct device *dev = &fmc->dev; | |
| char *s; | |
| if (!fwe_drv.busid_n) { | |
| dev_err(dev, "%s: no busid passed, refusing all cards\n", | |
| KBUILD_MODNAME); | |
| return -ENODEV; | |
| } | |
| index = fmc_validate(fmc, &fwe_drv); | |
| if (index < 0) { | |
| pr_err("%s: refusing device \"%s\"\n", KBUILD_MODNAME, | |
| dev_name(dev)); | |
| return -ENODEV; | |
| } | |
| if (index >= fwe_file_n) { | |
| pr_err("%s: no filename for device index %i\n", | |
| KBUILD_MODNAME, index); | |
| return -ENODEV; | |
| } | |
| s = fwe_file[index]; | |
| if (!s) { | |
| pr_err("%s: no filename for \"%s\" not programming\n", | |
| KBUILD_MODNAME, dev_name(dev)); | |
| return -ENOENT; | |
| } | |
| err = request_firmware(&fw, s, dev); | |
| if (err < 0) { | |
| dev_err(&fmc->dev, "request firmware \"%s\": error %i\n", | |
| s, err); | |
| return err; | |
| } | |
| fwe_run(fmc, fw, s); | |
| release_firmware(fw); | |
| return 0; | |
| } | |
| static int fwe_remove(struct fmc_device *fmc) | |
| { | |
| return 0; | |
| } | |
| static struct fmc_driver fwe_drv = { | |
| .version = FMC_VERSION, | |
| .driver.name = KBUILD_MODNAME, | |
| .probe = fwe_probe, | |
| .remove = fwe_remove, | |
| /* no table, as the current match just matches everything */ | |
| }; | |
| static int fwe_init(void) | |
| { | |
| int ret; | |
| ret = fmc_driver_register(&fwe_drv); | |
| return ret; | |
| } | |
| static void fwe_exit(void) | |
| { | |
| fmc_driver_unregister(&fwe_drv); | |
| } | |
| module_init(fwe_init); | |
| module_exit(fwe_exit); | |
| MODULE_LICENSE("GPL"); |