File Index
Files and directories
Sign in
FILE

qnap-mcu.c

File preview with syntax highlighting.

linux-6.19.14/drivers/mfd/qnap-mcu.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Core driver for the microcontroller unit in QNAP NAS devices that is
  4 * connected via a dedicated UART port.
  5 *
  6 * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
  7 */
  8
  9#include <linux/cleanup.h>
 10#include <linux/export.h>
 11#include <linux/mfd/core.h>
 12#include <linux/mfd/qnap-mcu.h>
 13#include <linux/module.h>
 14#include <linux/of.h>
 15#include <linux/reboot.h>
 16#include <linux/serdev.h>
 17#include <linux/slab.h>
 18
 19/* The longest command found so far is 5 bytes long */
 20#define QNAP_MCU_MAX_CMD_SIZE		5
 21#define QNAP_MCU_MAX_DATA_SIZE		36
 22#define QNAP_MCU_ERROR_SIZE		2
 23#define QNAP_MCU_CHECKSUM_SIZE		1
 24
 25#define QNAP_MCU_RX_BUFFER_SIZE		\
 26		(QNAP_MCU_MAX_DATA_SIZE + QNAP_MCU_CHECKSUM_SIZE)
 27
 28#define QNAP_MCU_TX_BUFFER_SIZE		\
 29		(QNAP_MCU_MAX_CMD_SIZE + QNAP_MCU_CHECKSUM_SIZE)
 30
 31#define QNAP_MCU_ACK_LEN		2
 32#define QNAP_MCU_VERSION_LEN		4
 33
 34#define QNAP_MCU_TIMEOUT_MS		500
 35
 36/**
 37 * struct qnap_mcu_reply - Reply to a command
 38 *
 39 * @data:	Buffer to store reply payload in
 40 * @length:	Expected reply length, including the checksum
 41 * @received:	Received number of bytes, so far
 42 * @done:	Triggered when the entire reply has been received
 43 */
 44struct qnap_mcu_reply {
 45	u8 *data;
 46	size_t length;
 47	size_t received;
 48	struct completion done;
 49};
 50
 51/**
 52 * struct qnap_mcu - QNAP NAS embedded controller
 53 *
 54 * @serdev:	Pointer to underlying serdev
 55 * @bus_lock:	Lock to serialize access to the device
 56 * @reply:	Reply data structure
 57 * @variant:	Device variant specific information
 58 * @version:	MCU firmware version
 59 */
 60struct qnap_mcu {
 61	struct serdev_device *serdev;
 62	struct mutex bus_lock;
 63	struct qnap_mcu_reply reply;
 64	const struct qnap_mcu_variant *variant;
 65	u8 version[QNAP_MCU_VERSION_LEN];
 66};
 67
 68/*
 69 * The QNAP-MCU uses a basic XOR checksum.
 70 * It is always the last byte and XORs the whole previous message.
 71 */
 72static u8 qnap_mcu_csum(const u8 *buf, size_t size)
 73{
 74	u8 csum = 0;
 75
 76	while (size--)
 77		csum ^= *buf++;
 78
 79	return csum;
 80}
 81
 82static bool qnap_mcu_verify_checksum(const u8 *buf, size_t size)
 83{
 84	u8 crc = qnap_mcu_csum(buf, size - QNAP_MCU_CHECKSUM_SIZE);
 85
 86	return crc == buf[size - QNAP_MCU_CHECKSUM_SIZE];
 87}
 88
 89static int qnap_mcu_write(struct qnap_mcu *mcu, const u8 *data, u8 data_size)
 90{
 91	unsigned char tx[QNAP_MCU_TX_BUFFER_SIZE];
 92	size_t length = data_size + QNAP_MCU_CHECKSUM_SIZE;
 93
 94	if (length > sizeof(tx)) {
 95		dev_err(&mcu->serdev->dev, "data too big for transmit buffer");
 96		return -EINVAL;
 97	}
 98
 99	memcpy(tx, data, data_size);
100	tx[data_size] = qnap_mcu_csum(data, data_size);
101
102	serdev_device_write_flush(mcu->serdev);
103
104	return serdev_device_write(mcu->serdev, tx, length, HZ);
105}
106
107static bool qnap_mcu_is_error_msg(size_t size)
108{
109	return (size == QNAP_MCU_ERROR_SIZE + QNAP_MCU_CHECKSUM_SIZE);
110}
111
112static bool qnap_mcu_reply_is_generic_error(unsigned char *buf, size_t size)
113{
114	if (!qnap_mcu_is_error_msg(size))
115		return false;
116
117	if (buf[0] == '@' && buf[1] == '9')
118		return true;
119
120	return false;
121}
122
123static bool qnap_mcu_reply_is_checksum_error(unsigned char *buf, size_t size)
124{
125	if (!qnap_mcu_is_error_msg(size))
126		return false;
127
128	if (buf[0] == '@' && buf[1] == '8')
129		return true;
130
131	return false;
132}
133
134static bool qnap_mcu_reply_is_any_error(struct qnap_mcu *mcu, unsigned char *buf, size_t size)
135{
136	if (qnap_mcu_reply_is_generic_error(buf, size)) {
137		dev_err(&mcu->serdev->dev, "Controller sent generic error response\n");
138		return true;
139	}
140
141	if (qnap_mcu_reply_is_checksum_error(buf, size)) {
142		dev_err(&mcu->serdev->dev, "Controller received invalid checksum for the command\n");
143		return true;
144	}
145
146	return false;
147}
148
149static size_t qnap_mcu_receive_buf(struct serdev_device *serdev, const u8 *buf, size_t size)
150{
151	struct device *dev = &serdev->dev;
152	struct qnap_mcu *mcu = dev_get_drvdata(dev);
153	struct qnap_mcu_reply *reply = &mcu->reply;
154	const u8 *src = buf;
155	const u8 *end = buf + size;
156
157	if (!reply->length) {
158		dev_warn(dev, "Received %zu bytes, we were not waiting for\n", size);
159		return size;
160	}
161
162	while (src < end) {
163		reply->data[reply->received] = *src++;
164		reply->received++;
165
166		if (reply->received == reply->length) {
167			/* We don't expect any characters from the device now */
168			reply->length = 0;
169
170			complete(&reply->done);
171
172			/*
173			 * We report the consumed number of bytes. If there
174			 * are still bytes remaining (though there shouldn't)
175			 * the serdev layer will re-execute this handler with
176			 * the remainder of the Rx bytes.
177			 */
178			return src - buf;
179		}
180	}
181
182	/*
183	 * We received everything the uart had to offer for now.
184	 * This could mean that either the uart will send more in a 2nd
185	 * receive run, or that the MCU cut the reply short because it
186	 * sent an error code instead of the expected reply.
187	 *
188	 * So check if the received data has the correct size for an error
189	 * reply and if it matches, is an actual error code.
190	 */
191	if (qnap_mcu_is_error_msg(reply->received) &&
192	    qnap_mcu_verify_checksum(reply->data, reply->received) &&
193	    qnap_mcu_reply_is_any_error(mcu, reply->data, reply->received)) {
194		/* The reply was an error code, we're done */
195		reply->length = 0;
196
197		complete(&reply->done);
198	}
199
200	/*
201	 * The only way to get out of the above loop and end up here
202	 * is through consuming all of the supplied data, so here we
203	 * report that we processed it all.
204	 */
205	return size;
206}
207
208static const struct serdev_device_ops qnap_mcu_serdev_device_ops = {
209	.receive_buf  = qnap_mcu_receive_buf,
210	.write_wakeup = serdev_device_write_wakeup,
211};
212
213int qnap_mcu_exec(struct qnap_mcu *mcu,
214		  const u8 *cmd_data, size_t cmd_data_size,
215		  u8 *reply_data, size_t reply_data_size)
216{
217	unsigned char rx[QNAP_MCU_RX_BUFFER_SIZE];
218	size_t length = reply_data_size + QNAP_MCU_CHECKSUM_SIZE;
219	struct qnap_mcu_reply *reply = &mcu->reply;
220	int ret = 0;
221
222	if (length > sizeof(rx)) {
223		dev_err(&mcu->serdev->dev, "expected data too big for receive buffer");
224		return -EINVAL;
225	}
226
227	guard(mutex)(&mcu->bus_lock);
228
229	reply->data = rx;
230	reply->length = length;
231	reply->received = 0;
232	reinit_completion(&reply->done);
233
234	ret = qnap_mcu_write(mcu, cmd_data, cmd_data_size);
235	if (ret < 0)
236		return ret;
237
238	serdev_device_wait_until_sent(mcu->serdev, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS));
239
240	if (!wait_for_completion_timeout(&reply->done, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS))) {
241		dev_err(&mcu->serdev->dev, "Command timeout\n");
242		return -ETIMEDOUT;
243	}
244
245	if (!qnap_mcu_verify_checksum(rx, reply->received)) {
246		dev_err(&mcu->serdev->dev, "Invalid Checksum received from controller\n");
247		return -EPROTO;
248	}
249
250	if (qnap_mcu_reply_is_any_error(mcu, rx, reply->received))
251		return -EPROTO;
252
253	memcpy(reply_data, rx, reply_data_size);
254
255	return 0;
256}
257EXPORT_SYMBOL_GPL(qnap_mcu_exec);
258
259int qnap_mcu_exec_with_ack(struct qnap_mcu *mcu,
260			   const u8 *cmd_data, size_t cmd_data_size)
261{
262	u8 ack[QNAP_MCU_ACK_LEN];
263	int ret;
264
265	ret = qnap_mcu_exec(mcu, cmd_data, cmd_data_size, ack, sizeof(ack));
266	if (ret)
267		return ret;
268
269	/* Should return @0 */
270	if (ack[0] != '@' || ack[1] != '0') {
271		dev_err(&mcu->serdev->dev, "Did not receive ack\n");
272		return -EIO;
273	}
274
275	return 0;
276}
277EXPORT_SYMBOL_GPL(qnap_mcu_exec_with_ack);
278
279static int qnap_mcu_get_version(struct qnap_mcu *mcu)
280{
281	const u8 cmd[] = { '%', 'V' };
282	u8 rx[14];
283	int ret;
284
285	/* Reply is the 2 command-bytes + 4 bytes describing the version */
286	ret = qnap_mcu_exec(mcu, cmd, sizeof(cmd), rx, QNAP_MCU_VERSION_LEN + 2);
287	if (ret)
288		return ret;
289
290	memcpy(mcu->version, &rx[2], QNAP_MCU_VERSION_LEN);
291
292	return 0;
293}
294
295/*
296 * The MCU controls power to the peripherals but not the CPU.
297 *
298 * So using the PMIC to power off the system keeps the MCU and hard-drives
299 * running. This also then prevents the system from turning back on until
300 * the MCU is turned off by unplugging the power cable.
301 * Turning off the MCU alone on the other hand turns off the hard drives,
302 * LEDs, etc while the main SoC stays running - including its network ports.
303 */
304static int qnap_mcu_power_off(struct sys_off_data *data)
305{
306	const u8 cmd[] = { '@', 'C', '0' };
307	struct qnap_mcu *mcu = data->cb_data;
308	int ret;
309
310	ret = qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd));
311	if (ret) {
312		dev_err(&mcu->serdev->dev, "MCU poweroff failed %d\n", ret);
313		return NOTIFY_STOP;
314	}
315
316	return NOTIFY_DONE;
317}
318
319static const struct qnap_mcu_variant qnap_ts233_mcu = {
320	.baud_rate = 115200,
321	.num_drives = 2,
322	.fan_pwm_min = 51,  /* Specified in original model.conf */
323	.fan_pwm_max = 255,
324	.usb_led = true,
325};
326
327static const struct qnap_mcu_variant qnap_ts433_mcu = {
328	.baud_rate = 115200,
329	.num_drives = 4,
330	.fan_pwm_min = 51,  /* Specified in original model.conf */
331	.fan_pwm_max = 255,
332	.usb_led = true,
333};
334
335static struct mfd_cell qnap_mcu_cells[] = {
336	{ .name = "qnap-mcu-eeprom", },
337	{ .name = "qnap-mcu-input", },
338	{ .name = "qnap-mcu-leds", },
339	{ .name = "qnap-mcu-hwmon", }
340};
341
342static int qnap_mcu_probe(struct serdev_device *serdev)
343{
344	struct device *dev = &serdev->dev;
345	struct qnap_mcu *mcu;
346	int ret;
347
348	mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);
349	if (!mcu)
350		return -ENOMEM;
351
352	mcu->serdev = serdev;
353	dev_set_drvdata(dev, mcu);
354
355	mcu->variant = of_device_get_match_data(dev);
356	if (!mcu->variant)
357		return -ENODEV;
358
359	mutex_init(&mcu->bus_lock);
360	init_completion(&mcu->reply.done);
361
362	serdev_device_set_client_ops(serdev, &qnap_mcu_serdev_device_ops);
363	ret = devm_serdev_device_open(dev, serdev);
364	if (ret)
365		return ret;
366
367	serdev_device_set_baudrate(serdev, mcu->variant->baud_rate);
368	serdev_device_set_flow_control(serdev, false);
369
370	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
371	if (ret)
372		return dev_err_probe(dev, ret, "Failed to set parity\n");
373
374	ret = qnap_mcu_get_version(mcu);
375	if (ret)
376		return ret;
377
378	ret = devm_register_sys_off_handler(dev,
379					    SYS_OFF_MODE_POWER_OFF_PREPARE,
380					    SYS_OFF_PRIO_DEFAULT,
381					    &qnap_mcu_power_off, mcu);
382	if (ret)
383		return dev_err_probe(dev, ret,
384				     "Failed to register poweroff handler\n");
385
386	for (int i = 0; i < ARRAY_SIZE(qnap_mcu_cells); i++) {
387		qnap_mcu_cells[i].platform_data = mcu->variant;
388		qnap_mcu_cells[i].pdata_size = sizeof(*mcu->variant);
389	}
390
391	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, qnap_mcu_cells,
392				   ARRAY_SIZE(qnap_mcu_cells), NULL, 0, NULL);
393	if (ret)
394		return dev_err_probe(dev, ret, "Failed to add child devices\n");
395
396	return 0;
397}
398
399static const struct of_device_id qnap_mcu_dt_ids[] = {
400	{ .compatible = "qnap,ts233-mcu", .data = &qnap_ts233_mcu },
401	{ .compatible = "qnap,ts433-mcu", .data = &qnap_ts433_mcu },
402	{ /* sentinel */ }
403};
404MODULE_DEVICE_TABLE(of, qnap_mcu_dt_ids);
405
406static struct serdev_device_driver qnap_mcu_drv = {
407	.probe = qnap_mcu_probe,
408	.driver = {
409		.name = "qnap-mcu",
410		.of_match_table = qnap_mcu_dt_ids,
411	},
412};
413module_serdev_device_driver(qnap_mcu_drv);
414
415MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
416MODULE_DESCRIPTION("QNAP MCU core driver");
417MODULE_LICENSE("GPL");
~/ Read-only file preview.