xref: /linux/sound/soc/codecs/ntpfw.c (revision 7f71507851fc7764b36a3221839607d3a45c2025)
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 
14 struct ntpfw_chunk {
15 	__be16 length;
16 	u8 step;
17 	u8 data[];
18 } __packed;
19 
20 struct ntpfw_header {
21 	__be32 magic;
22 } __packed;
23 
24 static 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 
43 static 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 
71 static 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 
88 int 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 
128 done:
129 	release_firmware(fw);
130 
131 	return ret;
132 }
133 EXPORT_SYMBOL_GPL(ntpfw_load);
134 
135 MODULE_AUTHOR("Igor Prusov <ivprusov@salutedevices.com>");
136 MODULE_DESCRIPTION("Helper for loading Neofidelity amplifiers firmware");
137 MODULE_LICENSE("GPL");
138