|  | /* | 
|  | * Load Analog Devices SigmaStudio firmware files | 
|  | * | 
|  | * Copyright 2009-2011 Analog Devices Inc. | 
|  | * | 
|  | * Licensed under the GPL-2 or later. | 
|  | */ | 
|  |  | 
|  | #include <linux/crc32.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/firmware.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/sigma.h> | 
|  |  | 
|  | /* Return: 0==OK, <0==error, =1 ==no more actions */ | 
|  | static int | 
|  | process_sigma_action(struct i2c_client *client, struct sigma_firmware *ssfw) | 
|  | { | 
|  | struct sigma_action *sa = (void *)(ssfw->fw->data + ssfw->pos); | 
|  | size_t len = sigma_action_len(sa); | 
|  | int ret = 0; | 
|  |  | 
|  | pr_debug("%s: instr:%i addr:%#x len:%zu\n", __func__, | 
|  | sa->instr, sa->addr, len); | 
|  |  | 
|  | switch (sa->instr) { | 
|  | case SIGMA_ACTION_WRITEXBYTES: | 
|  | case SIGMA_ACTION_WRITESINGLE: | 
|  | case SIGMA_ACTION_WRITESAFELOAD: | 
|  | if (ssfw->fw->size < ssfw->pos + len) | 
|  | return -EINVAL; | 
|  | ret = i2c_master_send(client, (void *)&sa->addr, len); | 
|  | if (ret < 0) | 
|  | return -EINVAL; | 
|  | break; | 
|  |  | 
|  | case SIGMA_ACTION_DELAY: | 
|  | ret = 0; | 
|  | udelay(len); | 
|  | len = 0; | 
|  | break; | 
|  |  | 
|  | case SIGMA_ACTION_END: | 
|  | return 1; | 
|  |  | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* when arrive here ret=0 or sent data */ | 
|  | ssfw->pos += sigma_action_size(sa, len); | 
|  | return ssfw->pos == ssfw->fw->size; | 
|  | } | 
|  |  | 
|  | static int | 
|  | process_sigma_actions(struct i2c_client *client, struct sigma_firmware *ssfw) | 
|  | { | 
|  | pr_debug("%s: processing %p\n", __func__, ssfw); | 
|  |  | 
|  | while (1) { | 
|  | int ret = process_sigma_action(client, ssfw); | 
|  | pr_debug("%s: action returned %i\n", __func__, ret); | 
|  | if (ret == 1) | 
|  | return 0; | 
|  | else if (ret) | 
|  | return ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | int process_sigma_firmware(struct i2c_client *client, const char *name) | 
|  | { | 
|  | int ret; | 
|  | struct sigma_firmware_header *ssfw_head; | 
|  | struct sigma_firmware ssfw; | 
|  | const struct firmware *fw; | 
|  | u32 crc; | 
|  |  | 
|  | pr_debug("%s: loading firmware %s\n", __func__, name); | 
|  |  | 
|  | /* first load the blob */ | 
|  | ret = request_firmware(&fw, name, &client->dev); | 
|  | if (ret) { | 
|  | pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); | 
|  | return ret; | 
|  | } | 
|  | ssfw.fw = fw; | 
|  |  | 
|  | /* then verify the header */ | 
|  | ret = -EINVAL; | 
|  | if (fw->size < sizeof(*ssfw_head)) | 
|  | goto done; | 
|  |  | 
|  | ssfw_head = (void *)fw->data; | 
|  | if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) | 
|  | goto done; | 
|  |  | 
|  | crc = crc32(0, fw->data, fw->size); | 
|  | pr_debug("%s: crc=%x\n", __func__, crc); | 
|  | if (crc != ssfw_head->crc) | 
|  | goto done; | 
|  |  | 
|  | ssfw.pos = sizeof(*ssfw_head); | 
|  |  | 
|  | /* finally process all of the actions */ | 
|  | ret = process_sigma_actions(client, &ssfw); | 
|  |  | 
|  | done: | 
|  | release_firmware(fw); | 
|  |  | 
|  | pr_debug("%s: loaded %s\n", __func__, name); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  | EXPORT_SYMBOL(process_sigma_firmware); | 
|  |  | 
|  | MODULE_LICENSE("GPL"); |