aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/media/atomisp/i2c/libmsrlisthelper.c
blob: decb65cfd7c9f63b991d337965f6bc2f067a6ead (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
 * Copyright (c) 2013 Intel Corporation. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 */
#include <linux/i2c.h>
#include <linux/firmware.h>
#include <linux/device.h>
#include <linux/export.h>
#include "../include/linux/libmsrlisthelper.h"
#include <linux/module.h>
#include <linux/slab.h>

/* Tagged binary data container structure definitions. */
struct tbd_header {
	uint32_t tag;          /*!< Tag identifier, also checks endianness */
	uint32_t size;         /*!< Container size including this header */
	uint32_t version;      /*!< Version, format 0xYYMMDDVV */
	uint32_t revision;     /*!< Revision, format 0xYYMMDDVV */
	uint32_t config_bits;  /*!< Configuration flag bits set */
	uint32_t checksum;     /*!< Global checksum, header included */
} __packed;

struct tbd_record_header {
	uint32_t size;        /*!< Size of record including header */
	uint8_t format_id;    /*!< tbd_format_t enumeration values used */
	uint8_t packing_key;  /*!< Packing method; 0 = no packing */
	uint16_t class_id;    /*!< tbd_class_t enumeration values used */
} __packed;

struct tbd_data_record_header {
	uint16_t next_offset;
	uint16_t flags;
	uint16_t data_offset;
	uint16_t data_size;
} __packed;

#define TBD_CLASS_DRV_ID 2

static int set_msr_configuration(struct i2c_client *client, uint8_t *bufptr,
		unsigned int size)
{
	/* The configuration data contains any number of sequences where
	 * the first byte (that is, uint8_t) that marks the number of bytes
	 * in the sequence to follow, is indeed followed by the indicated
	 * number of bytes of actual data to be written to sensor.
	 * By convention, the first two bytes of actual data should be
	 * understood as an address in the sensor address space (hibyte
	 * followed by lobyte) where the remaining data in the sequence
	 * will be written. */

	uint8_t *ptr = bufptr;
	while (ptr < bufptr + size) {
		struct i2c_msg msg = {
			.addr = client->addr,
			.flags = 0,
		};
		int ret;

		/* How many bytes */
		msg.len = *ptr++;
		/* Where the bytes are located */
		msg.buf = ptr;
		ptr += msg.len;

		if (ptr > bufptr + size)
			/* Accessing data beyond bounds is not tolerated */
			return -EINVAL;

		ret = i2c_transfer(client->adapter, &msg, 1);
		if (ret < 0) {
			dev_err(&client->dev, "i2c write error: %d", ret);
			return ret;
		}
	}
	return 0;
}

static int parse_and_apply(struct i2c_client *client, uint8_t *buffer,
		unsigned int size)
{
	uint8_t *endptr8 = buffer + size;
	struct tbd_data_record_header *header =
		(struct tbd_data_record_header *)buffer;

	/* There may be any number of datasets present */
	unsigned int dataset = 0;

	do {
		/* In below, four variables are read from buffer */
		if ((uint8_t *)header + sizeof(*header) > endptr8)
			return -EINVAL;

		/* All data should be located within given buffer */
		if ((uint8_t *)header + header->data_offset +
				header->data_size > endptr8)
			return -EINVAL;

		/* We have a new valid dataset */
		dataset++;
		/* See whether there is MSR data */
		/* If yes, update the reg info */
		if (header->data_size && (header->flags & 1)) {
			int ret;

			dev_info(&client->dev,
				"New MSR data for sensor driver (dataset %02d) size:%d\n",
				dataset, header->data_size);
			ret = set_msr_configuration(client,
						buffer + header->data_offset,
						header->data_size);
			if (ret)
				return ret;
		}
		header = (struct tbd_data_record_header *)(buffer +
			header->next_offset);
	} while (header->next_offset);

	return 0;
}

int apply_msr_data(struct i2c_client *client, const struct firmware *fw)
{
	struct tbd_header *header;
	struct tbd_record_header *record;

	if (!fw) {
		dev_warn(&client->dev, "Drv data is not loaded.\n");
		return -EINVAL;
	}

	if (sizeof(*header) > fw->size)
		return -EINVAL;

	header = (struct tbd_header *)fw->data;
	/* Check that we have drvb block. */
	if (memcmp(&header->tag, "DRVB", 4))
		return -EINVAL;

	/* Check the size */
	if (header->size != fw->size)
		return -EINVAL;

	if (sizeof(*header) + sizeof(*record) > fw->size)
		return -EINVAL;

	record = (struct tbd_record_header *)(header + 1);
	/* Check that class id mathes tbd's drv id. */
	if (record->class_id != TBD_CLASS_DRV_ID)
		return -EINVAL;

	/* Size 0 shall not be treated as an error */
	if (!record->size)
		return 0;

	return parse_and_apply(client, (uint8_t *)(record + 1), record->size);
}
EXPORT_SYMBOL_GPL(apply_msr_data);

int load_msr_list(struct i2c_client *client, char *name,
		const struct firmware **fw)
{
	int ret = request_firmware(fw, name, &client->dev);
	if (ret) {
		dev_err(&client->dev,
			"Error %d while requesting firmware %s\n",
			ret, name);
		return ret;
	}
	dev_info(&client->dev, "Received %lu bytes drv data\n",
			(unsigned long)(*fw)->size);

	return 0;
}
EXPORT_SYMBOL_GPL(load_msr_list);

void release_msr_list(struct i2c_client *client, const struct firmware *fw)
{
	release_firmware(fw);
}
EXPORT_SYMBOL_GPL(release_msr_list);

static int init_msrlisthelper(void)
{
	return 0;
}

static void exit_msrlisthelper(void)
{
}

module_init(init_msrlisthelper);
module_exit(exit_msrlisthelper);

MODULE_AUTHOR("Jukka Kaartinen <jukka.o.kaartinen@intel.com>");
MODULE_LICENSE("GPL");