Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.10.11.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * ntpfw.c - Firmware helper functions for Neofidelity codecs
  4 *
  5 * Copyright (c) 2024, SaluteDevices. All Rights Reserved.
  6 */
  7
  8#include <linux/i2c.h>
  9#include <linux/firmware.h>
 10#include <linux/module.h>
 11
 12#include "ntpfw.h"
 13
 14struct ntpfw_chunk {
 15	__be16 length;
 16	u8 step;
 17	u8 data[];
 18} __packed;
 19
 20struct ntpfw_header {
 21	__be32 magic;
 22} __packed;
 23
 24static bool ntpfw_verify(struct device *dev, const u8 *buf, size_t buf_size, u32 magic)
 25{
 26	const struct ntpfw_header *header = (struct ntpfw_header *)buf;
 27	u32 buf_magic;
 28
 29	if (buf_size <= sizeof(*header)) {
 30		dev_err(dev, "Failed to load firmware: image too small\n");
 31		return false;
 32	}
 33
 34	buf_magic = be32_to_cpu(header->magic);
 35	if (buf_magic != magic) {
 36		dev_err(dev, "Failed to load firmware: invalid magic 0x%x:\n", buf_magic);
 37		return false;
 38	}
 39
 40	return true;
 41}
 42
 43static bool ntpfw_verify_chunk(struct device *dev, const struct ntpfw_chunk *chunk, size_t buf_size)
 44{
 45	size_t chunk_size;
 46
 47	if (buf_size <= sizeof(*chunk)) {
 48		dev_err(dev, "Failed to load firmware: chunk size too big\n");
 49		return false;
 50	}
 51
 52	if (chunk->step != 2 && chunk->step != 5) {
 53		dev_err(dev, "Failed to load firmware: invalid chunk step: %d\n", chunk->step);
 54		return false;
 55	}
 56
 57	chunk_size = be16_to_cpu(chunk->length);
 58	if (chunk_size > buf_size) {
 59		dev_err(dev, "Failed to load firmware: invalid chunk length\n");
 60		return false;
 61	}
 62
 63	if (chunk_size % chunk->step) {
 64		dev_err(dev, "Failed to load firmware: chunk length and step mismatch\n");
 65		return false;
 66	}
 67
 68	return true;
 69}
 70
 71static int ntpfw_send_chunk(struct i2c_client *i2c, const struct ntpfw_chunk *chunk)
 72{
 73	int ret;
 74	size_t i;
 75	size_t length = be16_to_cpu(chunk->length);
 76
 77	for (i = 0; i < length; i += chunk->step) {
 78		ret = i2c_master_send(i2c, &chunk->data[i], chunk->step);
 79		if (ret != chunk->step) {
 80			dev_err(&i2c->dev, "I2C send failed: %d\n", ret);
 81			return ret < 0 ? ret : -EIO;
 82		}
 83	}
 84
 85	return 0;
 86}
 87
 88int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic)
 89{
 90	struct device *dev = &i2c->dev;
 91	const struct ntpfw_chunk *chunk;
 92	const struct firmware *fw;
 93	const u8 *data;
 94	size_t leftover;
 95	int ret;
 96
 97	ret = request_firmware(&fw, name, dev);
 98	if (ret) {
 99		dev_warn(dev, "request_firmware '%s' failed with %d\n",
100			 name, ret);
101		return ret;
102	}
103
104	if (!ntpfw_verify(dev, fw->data, fw->size, magic)) {
105		ret = -EINVAL;
106		goto done;
107	}
108
109	data = fw->data + sizeof(struct ntpfw_header);
110	leftover = fw->size - sizeof(struct ntpfw_header);
111
112	while (leftover) {
113		chunk = (struct ntpfw_chunk *)data;
114
115		if (!ntpfw_verify_chunk(dev, chunk, leftover)) {
116			ret = -EINVAL;
117			goto done;
118		}
119
120		ret = ntpfw_send_chunk(i2c, chunk);
121		if (ret)
122			goto done;
123
124		data += be16_to_cpu(chunk->length) + sizeof(*chunk);
125		leftover -= be16_to_cpu(chunk->length) + sizeof(*chunk);
126	}
127
128done:
129	release_firmware(fw);
130
131	return ret;
132}
133EXPORT_SYMBOL_GPL(ntpfw_load);
134
135MODULE_AUTHOR("Igor Prusov <ivprusov@salutedevices.com>");
136MODULE_DESCRIPTION("Helper for loading Neofidelity amplifiers firmware");
137MODULE_LICENSE("GPL");