static int iqs62x_firmware_parse()

in iqs62x.c [220:338]


static int iqs62x_firmware_parse(struct iqs62x_core *iqs62x,
				 const struct firmware *fw)
{
	struct i2c_client *client = iqs62x->client;
	struct iqs62x_fw_rec *fw_rec;
	struct iqs62x_fw_blk *fw_blk;
	unsigned int val;
	size_t pos = 0;
	int ret = 0;
	u8 mask, len, *data;
	u8 hall_cal_index = 0;

	while (pos < fw->size) {
		if (pos + sizeof(*fw_rec) > fw->size) {
			ret = -EINVAL;
			break;
		}
		fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos);
		pos += sizeof(*fw_rec);

		if (pos + fw_rec->len - 1 > fw->size) {
			ret = -EINVAL;
			break;
		}
		pos += fw_rec->len - 1;

		switch (fw_rec->type) {
		case IQS62X_FW_REC_TYPE_INFO:
			continue;

		case IQS62X_FW_REC_TYPE_PROD:
			if (fw_rec->data == iqs62x->dev_desc->prod_num)
				continue;

			dev_err(&client->dev,
				"Incompatible product number: 0x%02X\n",
				fw_rec->data);
			ret = -EINVAL;
			break;

		case IQS62X_FW_REC_TYPE_HALL:
			if (!hall_cal_index) {
				ret = regmap_write(iqs62x->regmap,
						   IQS62X_OTP_CMD,
						   IQS62X_OTP_CMD_FG3);
				if (ret)
					break;

				ret = regmap_read(iqs62x->regmap,
						  IQS62X_OTP_DATA, &val);
				if (ret)
					break;

				hall_cal_index = val & IQS62X_HALL_CAL_MASK;
				if (!hall_cal_index) {
					dev_err(&client->dev,
						"Uncalibrated device\n");
					ret = -ENODATA;
					break;
				}
			}

			if (hall_cal_index > fw_rec->len) {
				ret = -EINVAL;
				break;
			}

			mask = 0;
			data = &fw_rec->data + hall_cal_index - 1;
			len = sizeof(*data);
			break;

		case IQS62X_FW_REC_TYPE_MASK:
			if (fw_rec->len < (sizeof(mask) + sizeof(*data))) {
				ret = -EINVAL;
				break;
			}

			mask = fw_rec->data;
			data = &fw_rec->data + sizeof(mask);
			len = sizeof(*data);
			break;

		case IQS62X_FW_REC_TYPE_DATA:
			mask = 0;
			data = &fw_rec->data;
			len = fw_rec->len;
			break;

		default:
			dev_err(&client->dev,
				"Unrecognized record type: 0x%02X\n",
				fw_rec->type);
			ret = -EINVAL;
		}

		if (ret)
			break;

		fw_blk = devm_kzalloc(&client->dev,
				      struct_size(fw_blk, data, len),
				      GFP_KERNEL);
		if (!fw_blk) {
			ret = -ENOMEM;
			break;
		}

		fw_blk->addr = fw_rec->addr;
		fw_blk->mask = mask;
		fw_blk->len = len;
		memcpy(fw_blk->data, data, len);

		list_add(&fw_blk->list, &iqs62x->fw_blk_head);
	}

	release_firmware(fw);

	return ret;
}