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 */ 23static struct fmc_driver fwe_drv; 24 25FMC_PARAM_BUSID(fwe_drv); 26 27/* The "file=" is like the generic "gateware=" used elsewhere */ 28static char *fwe_file[FMC_MAX_CARDS]; 29static int fwe_file_n; 30module_param_array_named(file, fwe_file, charp, &fwe_file_n, 0444); 31 32static 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->op->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 68static 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->op->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 82static 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 */ 106static 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 if (fmc->op->validate) 119 index = fmc->op->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 147static int fwe_remove(struct fmc_device *fmc) 148{ 149 return 0; 150} 151 152static 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 160static int fwe_init(void) 161{ 162 int ret; 163 164 ret = fmc_driver_register(&fwe_drv); 165 return ret; 166} 167 168static void fwe_exit(void) 169{ 170 fmc_driver_unregister(&fwe_drv); 171} 172 173module_init(fwe_init); 174module_exit(fwe_exit); 175 176MODULE_LICENSE("GPL"); 177