| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * 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. |
| * |
| * |
| */ |
| #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 { |
| u32 tag; /*!< Tag identifier, also checks endianness */ |
| u32 size; /*!< Container size including this header */ |
| u32 version; /*!< Version, format 0xYYMMDDVV */ |
| u32 revision; /*!< Revision, format 0xYYMMDDVV */ |
| u32 config_bits; /*!< Configuration flag bits set */ |
| u32 checksum; /*!< Global checksum, header included */ |
| } __packed; |
| |
| struct tbd_record_header { |
| u32 size; /*!< Size of record including header */ |
| u8 format_id; /*!< tbd_format_t enumeration values used */ |
| u8 packing_key; /*!< Packing method; 0 = no packing */ |
| u16 class_id; /*!< tbd_class_t enumeration values used */ |
| } __packed; |
| |
| struct tbd_data_record_header { |
| u16 next_offset; |
| u16 flags; |
| u16 data_offset; |
| u16 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. |
| */ |
| |
| u8 *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) |
| { |
| u8 *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"); |