| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Load Analog Devices SigmaStudio firmware files | 
|  | 3 | * | 
|  | 4 | * Copyright 2009-2011 Analog Devices Inc. | 
|  | 5 | * | 
|  | 6 | * Licensed under the GPL-2 or later. | 
|  | 7 | */ | 
|  | 8 |  | 
|  | 9 | #include <linux/crc32.h> | 
|  | 10 | #include <linux/delay.h> | 
|  | 11 | #include <linux/firmware.h> | 
|  | 12 | #include <linux/kernel.h> | 
|  | 13 | #include <linux/i2c.h> | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 14 | #include <linux/regmap.h> | 
| Randy Dunlap | 27c46a2 | 2011-07-25 17:13:21 -0700 | [diff] [blame] | 15 | #include <linux/module.h> | 
| Lars-Peter Clausen | 40216ce | 2011-11-28 09:44:17 +0100 | [diff] [blame] | 16 |  | 
|  | 17 | #include "sigmadsp.h" | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 18 |  | 
| Lars-Peter Clausen | a4c1d7e | 2011-11-28 09:44:19 +0100 | [diff] [blame] | 19 | #define SIGMA_MAGIC "ADISIGM" | 
|  | 20 |  | 
|  | 21 | struct sigma_firmware_header { | 
|  | 22 | unsigned char magic[7]; | 
|  | 23 | u8 version; | 
|  | 24 | __le32 crc; | 
|  | 25 | } __packed; | 
|  | 26 |  | 
|  | 27 | enum { | 
|  | 28 | SIGMA_ACTION_WRITEXBYTES = 0, | 
|  | 29 | SIGMA_ACTION_WRITESINGLE, | 
|  | 30 | SIGMA_ACTION_WRITESAFELOAD, | 
|  | 31 | SIGMA_ACTION_DELAY, | 
|  | 32 | SIGMA_ACTION_PLLWAIT, | 
|  | 33 | SIGMA_ACTION_NOOP, | 
|  | 34 | SIGMA_ACTION_END, | 
|  | 35 | }; | 
|  | 36 |  | 
|  | 37 | struct sigma_action { | 
|  | 38 | u8 instr; | 
|  | 39 | u8 len_hi; | 
|  | 40 | __le16 len; | 
|  | 41 | __be16 addr; | 
|  | 42 | unsigned char payload[]; | 
|  | 43 | } __packed; | 
|  | 44 |  | 
|  | 45 | struct sigma_firmware { | 
|  | 46 | const struct firmware *fw; | 
|  | 47 | size_t pos; | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 48 |  | 
|  | 49 | void *control_data; | 
|  | 50 | int (*write)(void *control_data, const struct sigma_action *sa, | 
|  | 51 | size_t len); | 
| Lars-Peter Clausen | a4c1d7e | 2011-11-28 09:44:19 +0100 | [diff] [blame] | 52 | }; | 
|  | 53 |  | 
|  | 54 | static inline u32 sigma_action_len(struct sigma_action *sa) | 
|  | 55 | { | 
|  | 56 | return (sa->len_hi << 16) | le16_to_cpu(sa->len); | 
|  | 57 | } | 
|  | 58 |  | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 59 | static size_t sigma_action_size(struct sigma_action *sa) | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 60 | { | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 61 | size_t payload = 0; | 
|  | 62 |  | 
|  | 63 | switch (sa->instr) { | 
|  | 64 | case SIGMA_ACTION_WRITEXBYTES: | 
|  | 65 | case SIGMA_ACTION_WRITESINGLE: | 
|  | 66 | case SIGMA_ACTION_WRITESAFELOAD: | 
|  | 67 | payload = sigma_action_len(sa); | 
|  | 68 | break; | 
|  | 69 | default: | 
|  | 70 | break; | 
|  | 71 | } | 
|  | 72 |  | 
|  | 73 | payload = ALIGN(payload, 2); | 
|  | 74 |  | 
|  | 75 | return payload + sizeof(struct sigma_action); | 
|  | 76 | } | 
|  | 77 |  | 
|  | 78 | /* | 
|  | 79 | * Returns a negative error value in case of an error, 0 if processing of | 
|  | 80 | * the firmware should be stopped after this action, 1 otherwise. | 
|  | 81 | */ | 
|  | 82 | static int | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 83 | process_sigma_action(struct sigma_firmware *ssfw, struct sigma_action *sa) | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 84 | { | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 85 | size_t len = sigma_action_len(sa); | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 86 | int ret; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 87 |  | 
|  | 88 | pr_debug("%s: instr:%i addr:%#x len:%zu\n", __func__, | 
|  | 89 | sa->instr, sa->addr, len); | 
|  | 90 |  | 
|  | 91 | switch (sa->instr) { | 
|  | 92 | case SIGMA_ACTION_WRITEXBYTES: | 
|  | 93 | case SIGMA_ACTION_WRITESINGLE: | 
|  | 94 | case SIGMA_ACTION_WRITESAFELOAD: | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 95 | ret = ssfw->write(ssfw->control_data, sa, len); | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 96 | if (ret < 0) | 
|  | 97 | return -EINVAL; | 
|  | 98 | break; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 99 | case SIGMA_ACTION_DELAY: | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 100 | udelay(len); | 
|  | 101 | len = 0; | 
|  | 102 | break; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 103 | case SIGMA_ACTION_END: | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 104 | return 0; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 105 | default: | 
|  | 106 | return -EINVAL; | 
|  | 107 | } | 
|  | 108 |  | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 109 | return 1; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 110 | } | 
|  | 111 |  | 
|  | 112 | static int | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 113 | process_sigma_actions(struct sigma_firmware *ssfw) | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 114 | { | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 115 | struct sigma_action *sa; | 
|  | 116 | size_t size; | 
|  | 117 | int ret; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 118 |  | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 119 | while (ssfw->pos + sizeof(*sa) <= ssfw->fw->size) { | 
|  | 120 | sa = (struct sigma_action *)(ssfw->fw->data + ssfw->pos); | 
|  | 121 |  | 
|  | 122 | size = sigma_action_size(sa); | 
|  | 123 | ssfw->pos += size; | 
|  | 124 | if (ssfw->pos > ssfw->fw->size || size == 0) | 
|  | 125 | break; | 
|  | 126 |  | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 127 | ret = process_sigma_action(ssfw, sa); | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 128 |  | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 129 | pr_debug("%s: action returned %i\n", __func__, ret); | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 130 |  | 
|  | 131 | if (ret <= 0) | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 132 | return ret; | 
|  | 133 | } | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 134 |  | 
|  | 135 | if (ssfw->pos != ssfw->fw->size) | 
|  | 136 | return -EINVAL; | 
|  | 137 |  | 
|  | 138 | return 0; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 139 | } | 
|  | 140 |  | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 141 | static int _process_sigma_firmware(struct device *dev, | 
|  | 142 | struct sigma_firmware *ssfw, const char *name) | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 143 | { | 
|  | 144 | int ret; | 
|  | 145 | struct sigma_firmware_header *ssfw_head; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 146 | const struct firmware *fw; | 
|  | 147 | u32 crc; | 
|  | 148 |  | 
|  | 149 | pr_debug("%s: loading firmware %s\n", __func__, name); | 
|  | 150 |  | 
|  | 151 | /* first load the blob */ | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 152 | ret = request_firmware(&fw, name, dev); | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 153 | if (ret) { | 
|  | 154 | pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); | 
|  | 155 | return ret; | 
|  | 156 | } | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 157 | ssfw->fw = fw; | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 158 |  | 
|  | 159 | /* then verify the header */ | 
|  | 160 | ret = -EINVAL; | 
| Lars-Peter Clausen | 4f718a2 | 2011-11-28 09:44:14 +0100 | [diff] [blame] | 161 |  | 
|  | 162 | /* | 
|  | 163 | * Reject too small or unreasonable large files. The upper limit has been | 
|  | 164 | * chosen a bit arbitrarily, but it should be enough for all practical | 
|  | 165 | * purposes and having the limit makes it easier to avoid integer | 
|  | 166 | * overflows later in the loading process. | 
|  | 167 | */ | 
| Lars-Peter Clausen | 48afc52 | 2011-11-28 09:44:18 +0100 | [diff] [blame] | 168 | if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 169 | dev_err(dev, "Failed to load firmware: Invalid size\n"); | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 170 | goto done; | 
| Lars-Peter Clausen | 48afc52 | 2011-11-28 09:44:18 +0100 | [diff] [blame] | 171 | } | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 172 |  | 
|  | 173 | ssfw_head = (void *)fw->data; | 
| Lars-Peter Clausen | 48afc52 | 2011-11-28 09:44:18 +0100 | [diff] [blame] | 174 | if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 175 | dev_err(dev, "Failed to load firmware: Invalid magic\n"); | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 176 | goto done; | 
| Lars-Peter Clausen | 48afc52 | 2011-11-28 09:44:18 +0100 | [diff] [blame] | 177 | } | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 178 |  | 
| Lars-Peter Clausen | c56935b | 2011-11-28 09:44:15 +0100 | [diff] [blame] | 179 | crc = crc32(0, fw->data + sizeof(*ssfw_head), | 
|  | 180 | fw->size - sizeof(*ssfw_head)); | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 181 | pr_debug("%s: crc=%x\n", __func__, crc); | 
| Lars-Peter Clausen | 48afc52 | 2011-11-28 09:44:18 +0100 | [diff] [blame] | 182 | if (crc != le32_to_cpu(ssfw_head->crc)) { | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 183 | dev_err(dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", | 
| Lars-Peter Clausen | 48afc52 | 2011-11-28 09:44:18 +0100 | [diff] [blame] | 184 | le32_to_cpu(ssfw_head->crc), crc); | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 185 | goto done; | 
| Lars-Peter Clausen | 48afc52 | 2011-11-28 09:44:18 +0100 | [diff] [blame] | 186 | } | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 187 |  | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 188 | ssfw->pos = sizeof(*ssfw_head); | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 189 |  | 
|  | 190 | /* finally process all of the actions */ | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 191 | ret = process_sigma_actions(ssfw); | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 192 |  | 
|  | 193 | done: | 
|  | 194 | release_firmware(fw); | 
|  | 195 |  | 
|  | 196 | pr_debug("%s: loaded %s\n", __func__, name); | 
|  | 197 |  | 
|  | 198 | return ret; | 
|  | 199 | } | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 200 |  | 
|  | 201 | #if IS_ENABLED(CONFIG_I2C) | 
|  | 202 |  | 
|  | 203 | static int sigma_action_write_i2c(void *control_data, | 
|  | 204 | const struct sigma_action *sa, size_t len) | 
|  | 205 | { | 
|  | 206 | return i2c_master_send(control_data, (const unsigned char *)&sa->addr, | 
|  | 207 | len); | 
|  | 208 | } | 
|  | 209 |  | 
|  | 210 | int process_sigma_firmware(struct i2c_client *client, const char *name) | 
|  | 211 | { | 
|  | 212 | struct sigma_firmware ssfw; | 
|  | 213 |  | 
|  | 214 | ssfw.control_data = client; | 
|  | 215 | ssfw.write = sigma_action_write_i2c; | 
|  | 216 |  | 
|  | 217 | return _process_sigma_firmware(&client->dev, &ssfw, name); | 
|  | 218 | } | 
| Mike Frysinger | e359dc2 | 2011-03-22 16:34:40 -0700 | [diff] [blame] | 219 | EXPORT_SYMBOL(process_sigma_firmware); | 
| Randy Dunlap | 27c46a2 | 2011-07-25 17:13:21 -0700 | [diff] [blame] | 220 |  | 
| Lars-Peter Clausen | 38fd54e | 2011-11-28 09:44:20 +0100 | [diff] [blame] | 221 | #endif | 
|  | 222 |  | 
|  | 223 | #if IS_ENABLED(CONFIG_REGMAP) | 
|  | 224 |  | 
|  | 225 | static int sigma_action_write_regmap(void *control_data, | 
|  | 226 | const struct sigma_action *sa, size_t len) | 
|  | 227 | { | 
|  | 228 | return regmap_raw_write(control_data, le16_to_cpu(sa->addr), | 
|  | 229 | sa->payload, len - 2); | 
|  | 230 | } | 
|  | 231 |  | 
|  | 232 | int process_sigma_firmware_regmap(struct device *dev, struct regmap *regmap, | 
|  | 233 | const char *name) | 
|  | 234 | { | 
|  | 235 | struct sigma_firmware ssfw; | 
|  | 236 |  | 
|  | 237 | ssfw.control_data = regmap; | 
|  | 238 | ssfw.write = sigma_action_write_regmap; | 
|  | 239 |  | 
|  | 240 | return _process_sigma_firmware(dev, &ssfw, name); | 
|  | 241 | } | 
|  | 242 | EXPORT_SYMBOL(process_sigma_firmware_regmap); | 
|  | 243 |  | 
|  | 244 | #endif | 
|  | 245 |  | 
| Randy Dunlap | 27c46a2 | 2011-07-25 17:13:21 -0700 | [diff] [blame] | 246 | MODULE_LICENSE("GPL"); |