| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2012 CERN (www.cern.ch) |
| * Author: Alessandro Rubini <rubini@gnudd.com> |
| * |
| * 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"); |