|  | /* | 
|  | * 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"); |