1998f70d1SHeiko Stuebner // SPDX-License-Identifier: GPL-2.0-only 2998f70d1SHeiko Stuebner /* 3998f70d1SHeiko Stuebner * Core driver for the microcontroller unit in QNAP NAS devices that is 4998f70d1SHeiko Stuebner * connected via a dedicated UART port. 5998f70d1SHeiko Stuebner * 6998f70d1SHeiko Stuebner * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de> 7998f70d1SHeiko Stuebner */ 8998f70d1SHeiko Stuebner 9998f70d1SHeiko Stuebner #include <linux/cleanup.h> 10998f70d1SHeiko Stuebner #include <linux/export.h> 11998f70d1SHeiko Stuebner #include <linux/mfd/core.h> 12998f70d1SHeiko Stuebner #include <linux/mfd/qnap-mcu.h> 13998f70d1SHeiko Stuebner #include <linux/module.h> 14998f70d1SHeiko Stuebner #include <linux/of.h> 15998f70d1SHeiko Stuebner #include <linux/reboot.h> 16998f70d1SHeiko Stuebner #include <linux/serdev.h> 17998f70d1SHeiko Stuebner #include <linux/slab.h> 18998f70d1SHeiko Stuebner 19998f70d1SHeiko Stuebner /* The longest command found so far is 5 bytes long */ 20998f70d1SHeiko Stuebner #define QNAP_MCU_MAX_CMD_SIZE 5 21998f70d1SHeiko Stuebner #define QNAP_MCU_MAX_DATA_SIZE 36 22998f70d1SHeiko Stuebner #define QNAP_MCU_CHECKSUM_SIZE 1 23998f70d1SHeiko Stuebner 24998f70d1SHeiko Stuebner #define QNAP_MCU_RX_BUFFER_SIZE \ 25998f70d1SHeiko Stuebner (QNAP_MCU_MAX_DATA_SIZE + QNAP_MCU_CHECKSUM_SIZE) 26998f70d1SHeiko Stuebner 27998f70d1SHeiko Stuebner #define QNAP_MCU_TX_BUFFER_SIZE \ 28998f70d1SHeiko Stuebner (QNAP_MCU_MAX_CMD_SIZE + QNAP_MCU_CHECKSUM_SIZE) 29998f70d1SHeiko Stuebner 30998f70d1SHeiko Stuebner #define QNAP_MCU_ACK_LEN 2 31998f70d1SHeiko Stuebner #define QNAP_MCU_VERSION_LEN 4 32998f70d1SHeiko Stuebner 33998f70d1SHeiko Stuebner #define QNAP_MCU_TIMEOUT_MS 500 34998f70d1SHeiko Stuebner 35998f70d1SHeiko Stuebner /** 36998f70d1SHeiko Stuebner * struct qnap_mcu_reply - Reply to a command 37998f70d1SHeiko Stuebner * 38998f70d1SHeiko Stuebner * @data: Buffer to store reply payload in 39998f70d1SHeiko Stuebner * @length: Expected reply length, including the checksum 40998f70d1SHeiko Stuebner * @received: Received number of bytes, so far 41998f70d1SHeiko Stuebner * @done: Triggered when the entire reply has been received 42998f70d1SHeiko Stuebner */ 43998f70d1SHeiko Stuebner struct qnap_mcu_reply { 44998f70d1SHeiko Stuebner u8 *data; 45998f70d1SHeiko Stuebner size_t length; 46998f70d1SHeiko Stuebner size_t received; 47998f70d1SHeiko Stuebner struct completion done; 48998f70d1SHeiko Stuebner }; 49998f70d1SHeiko Stuebner 50998f70d1SHeiko Stuebner /** 51998f70d1SHeiko Stuebner * struct qnap_mcu - QNAP NAS embedded controller 52998f70d1SHeiko Stuebner * 53998f70d1SHeiko Stuebner * @serdev: Pointer to underlying serdev 54998f70d1SHeiko Stuebner * @bus_lock: Lock to serialize access to the device 55998f70d1SHeiko Stuebner * @reply: Reply data structure 56998f70d1SHeiko Stuebner * @variant: Device variant specific information 57998f70d1SHeiko Stuebner * @version: MCU firmware version 58998f70d1SHeiko Stuebner */ 59998f70d1SHeiko Stuebner struct qnap_mcu { 60998f70d1SHeiko Stuebner struct serdev_device *serdev; 61998f70d1SHeiko Stuebner struct mutex bus_lock; 62998f70d1SHeiko Stuebner struct qnap_mcu_reply reply; 63998f70d1SHeiko Stuebner const struct qnap_mcu_variant *variant; 64998f70d1SHeiko Stuebner u8 version[QNAP_MCU_VERSION_LEN]; 65998f70d1SHeiko Stuebner }; 66998f70d1SHeiko Stuebner 67998f70d1SHeiko Stuebner /* 68998f70d1SHeiko Stuebner * The QNAP-MCU uses a basic XOR checksum. 69998f70d1SHeiko Stuebner * It is always the last byte and XORs the whole previous message. 70998f70d1SHeiko Stuebner */ 71998f70d1SHeiko Stuebner static u8 qnap_mcu_csum(const u8 *buf, size_t size) 72998f70d1SHeiko Stuebner { 73998f70d1SHeiko Stuebner u8 csum = 0; 74998f70d1SHeiko Stuebner 75998f70d1SHeiko Stuebner while (size--) 76998f70d1SHeiko Stuebner csum ^= *buf++; 77998f70d1SHeiko Stuebner 78998f70d1SHeiko Stuebner return csum; 79998f70d1SHeiko Stuebner } 80998f70d1SHeiko Stuebner 81998f70d1SHeiko Stuebner static int qnap_mcu_write(struct qnap_mcu *mcu, const u8 *data, u8 data_size) 82998f70d1SHeiko Stuebner { 83998f70d1SHeiko Stuebner unsigned char tx[QNAP_MCU_TX_BUFFER_SIZE]; 84998f70d1SHeiko Stuebner size_t length = data_size + QNAP_MCU_CHECKSUM_SIZE; 85998f70d1SHeiko Stuebner 86998f70d1SHeiko Stuebner if (length > sizeof(tx)) { 87998f70d1SHeiko Stuebner dev_err(&mcu->serdev->dev, "data too big for transmit buffer"); 88998f70d1SHeiko Stuebner return -EINVAL; 89998f70d1SHeiko Stuebner } 90998f70d1SHeiko Stuebner 91998f70d1SHeiko Stuebner memcpy(tx, data, data_size); 92998f70d1SHeiko Stuebner tx[data_size] = qnap_mcu_csum(data, data_size); 93998f70d1SHeiko Stuebner 94998f70d1SHeiko Stuebner serdev_device_write_flush(mcu->serdev); 95998f70d1SHeiko Stuebner 96998f70d1SHeiko Stuebner return serdev_device_write(mcu->serdev, tx, length, HZ); 97998f70d1SHeiko Stuebner } 98998f70d1SHeiko Stuebner 99998f70d1SHeiko Stuebner static size_t qnap_mcu_receive_buf(struct serdev_device *serdev, const u8 *buf, size_t size) 100998f70d1SHeiko Stuebner { 101998f70d1SHeiko Stuebner struct device *dev = &serdev->dev; 102998f70d1SHeiko Stuebner struct qnap_mcu *mcu = dev_get_drvdata(dev); 103998f70d1SHeiko Stuebner struct qnap_mcu_reply *reply = &mcu->reply; 104998f70d1SHeiko Stuebner const u8 *src = buf; 105998f70d1SHeiko Stuebner const u8 *end = buf + size; 106998f70d1SHeiko Stuebner 107998f70d1SHeiko Stuebner if (!reply->length) { 108998f70d1SHeiko Stuebner dev_warn(dev, "Received %zu bytes, we were not waiting for\n", size); 109998f70d1SHeiko Stuebner return size; 110998f70d1SHeiko Stuebner } 111998f70d1SHeiko Stuebner 112998f70d1SHeiko Stuebner while (src < end) { 113998f70d1SHeiko Stuebner reply->data[reply->received] = *src++; 114998f70d1SHeiko Stuebner reply->received++; 115998f70d1SHeiko Stuebner 116998f70d1SHeiko Stuebner if (reply->received == reply->length) { 117998f70d1SHeiko Stuebner /* We don't expect any characters from the device now */ 118998f70d1SHeiko Stuebner reply->length = 0; 119998f70d1SHeiko Stuebner 120998f70d1SHeiko Stuebner complete(&reply->done); 121998f70d1SHeiko Stuebner 122998f70d1SHeiko Stuebner /* 123998f70d1SHeiko Stuebner * We report the consumed number of bytes. If there 124998f70d1SHeiko Stuebner * are still bytes remaining (though there shouldn't) 125998f70d1SHeiko Stuebner * the serdev layer will re-execute this handler with 126998f70d1SHeiko Stuebner * the remainder of the Rx bytes. 127998f70d1SHeiko Stuebner */ 128998f70d1SHeiko Stuebner return src - buf; 129998f70d1SHeiko Stuebner } 130998f70d1SHeiko Stuebner } 131998f70d1SHeiko Stuebner 132998f70d1SHeiko Stuebner /* 133998f70d1SHeiko Stuebner * The only way to get out of the above loop and end up here 134998f70d1SHeiko Stuebner * is through consuming all of the supplied data, so here we 135998f70d1SHeiko Stuebner * report that we processed it all. 136998f70d1SHeiko Stuebner */ 137998f70d1SHeiko Stuebner return size; 138998f70d1SHeiko Stuebner } 139998f70d1SHeiko Stuebner 140998f70d1SHeiko Stuebner static const struct serdev_device_ops qnap_mcu_serdev_device_ops = { 141998f70d1SHeiko Stuebner .receive_buf = qnap_mcu_receive_buf, 142998f70d1SHeiko Stuebner .write_wakeup = serdev_device_write_wakeup, 143998f70d1SHeiko Stuebner }; 144998f70d1SHeiko Stuebner 145998f70d1SHeiko Stuebner int qnap_mcu_exec(struct qnap_mcu *mcu, 146998f70d1SHeiko Stuebner const u8 *cmd_data, size_t cmd_data_size, 147998f70d1SHeiko Stuebner u8 *reply_data, size_t reply_data_size) 148998f70d1SHeiko Stuebner { 149998f70d1SHeiko Stuebner unsigned char rx[QNAP_MCU_RX_BUFFER_SIZE]; 150998f70d1SHeiko Stuebner size_t length = reply_data_size + QNAP_MCU_CHECKSUM_SIZE; 151998f70d1SHeiko Stuebner struct qnap_mcu_reply *reply = &mcu->reply; 152998f70d1SHeiko Stuebner int ret = 0; 153998f70d1SHeiko Stuebner 154998f70d1SHeiko Stuebner if (length > sizeof(rx)) { 155998f70d1SHeiko Stuebner dev_err(&mcu->serdev->dev, "expected data too big for receive buffer"); 156998f70d1SHeiko Stuebner return -EINVAL; 157998f70d1SHeiko Stuebner } 158998f70d1SHeiko Stuebner 159998f70d1SHeiko Stuebner mutex_lock(&mcu->bus_lock); 160998f70d1SHeiko Stuebner 161*f8271825SChen Ni reply->data = rx; 162*f8271825SChen Ni reply->length = length; 163*f8271825SChen Ni reply->received = 0; 164998f70d1SHeiko Stuebner reinit_completion(&reply->done); 165998f70d1SHeiko Stuebner 166998f70d1SHeiko Stuebner qnap_mcu_write(mcu, cmd_data, cmd_data_size); 167998f70d1SHeiko Stuebner 168998f70d1SHeiko Stuebner serdev_device_wait_until_sent(mcu->serdev, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS)); 169998f70d1SHeiko Stuebner 170998f70d1SHeiko Stuebner if (!wait_for_completion_timeout(&reply->done, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS))) { 171998f70d1SHeiko Stuebner dev_err(&mcu->serdev->dev, "Command timeout\n"); 172998f70d1SHeiko Stuebner ret = -ETIMEDOUT; 173998f70d1SHeiko Stuebner } else { 174998f70d1SHeiko Stuebner u8 crc = qnap_mcu_csum(rx, reply_data_size); 175998f70d1SHeiko Stuebner 176998f70d1SHeiko Stuebner if (crc != rx[reply_data_size]) { 177998f70d1SHeiko Stuebner dev_err(&mcu->serdev->dev, 178998f70d1SHeiko Stuebner "Invalid Checksum received\n"); 179998f70d1SHeiko Stuebner ret = -EIO; 180998f70d1SHeiko Stuebner } else { 181998f70d1SHeiko Stuebner memcpy(reply_data, rx, reply_data_size); 182998f70d1SHeiko Stuebner } 183998f70d1SHeiko Stuebner } 184998f70d1SHeiko Stuebner 185998f70d1SHeiko Stuebner mutex_unlock(&mcu->bus_lock); 186998f70d1SHeiko Stuebner return ret; 187998f70d1SHeiko Stuebner } 188998f70d1SHeiko Stuebner EXPORT_SYMBOL_GPL(qnap_mcu_exec); 189998f70d1SHeiko Stuebner 190998f70d1SHeiko Stuebner int qnap_mcu_exec_with_ack(struct qnap_mcu *mcu, 191998f70d1SHeiko Stuebner const u8 *cmd_data, size_t cmd_data_size) 192998f70d1SHeiko Stuebner { 193998f70d1SHeiko Stuebner u8 ack[QNAP_MCU_ACK_LEN]; 194998f70d1SHeiko Stuebner int ret; 195998f70d1SHeiko Stuebner 196998f70d1SHeiko Stuebner ret = qnap_mcu_exec(mcu, cmd_data, cmd_data_size, ack, sizeof(ack)); 197998f70d1SHeiko Stuebner if (ret) 198998f70d1SHeiko Stuebner return ret; 199998f70d1SHeiko Stuebner 200998f70d1SHeiko Stuebner /* Should return @0 */ 201998f70d1SHeiko Stuebner if (ack[0] != '@' || ack[1] != '0') { 202998f70d1SHeiko Stuebner dev_err(&mcu->serdev->dev, "Did not receive ack\n"); 203998f70d1SHeiko Stuebner return -EIO; 204998f70d1SHeiko Stuebner } 205998f70d1SHeiko Stuebner 206998f70d1SHeiko Stuebner return 0; 207998f70d1SHeiko Stuebner } 208998f70d1SHeiko Stuebner EXPORT_SYMBOL_GPL(qnap_mcu_exec_with_ack); 209998f70d1SHeiko Stuebner 210998f70d1SHeiko Stuebner static int qnap_mcu_get_version(struct qnap_mcu *mcu) 211998f70d1SHeiko Stuebner { 212998f70d1SHeiko Stuebner const u8 cmd[] = { '%', 'V' }; 213998f70d1SHeiko Stuebner u8 rx[14]; 214998f70d1SHeiko Stuebner int ret; 215998f70d1SHeiko Stuebner 216998f70d1SHeiko Stuebner /* Reply is the 2 command-bytes + 4 bytes describing the version */ 217998f70d1SHeiko Stuebner ret = qnap_mcu_exec(mcu, cmd, sizeof(cmd), rx, QNAP_MCU_VERSION_LEN + 2); 218998f70d1SHeiko Stuebner if (ret) 219998f70d1SHeiko Stuebner return ret; 220998f70d1SHeiko Stuebner 221998f70d1SHeiko Stuebner memcpy(mcu->version, &rx[2], QNAP_MCU_VERSION_LEN); 222998f70d1SHeiko Stuebner 223998f70d1SHeiko Stuebner return 0; 224998f70d1SHeiko Stuebner } 225998f70d1SHeiko Stuebner 226998f70d1SHeiko Stuebner /* 227998f70d1SHeiko Stuebner * The MCU controls power to the peripherals but not the CPU. 228998f70d1SHeiko Stuebner * 229998f70d1SHeiko Stuebner * So using the PMIC to power off the system keeps the MCU and hard-drives 230998f70d1SHeiko Stuebner * running. This also then prevents the system from turning back on until 231998f70d1SHeiko Stuebner * the MCU is turned off by unplugging the power cable. 232998f70d1SHeiko Stuebner * Turning off the MCU alone on the other hand turns off the hard drives, 233998f70d1SHeiko Stuebner * LEDs, etc while the main SoC stays running - including its network ports. 234998f70d1SHeiko Stuebner */ 235998f70d1SHeiko Stuebner static int qnap_mcu_power_off(struct sys_off_data *data) 236998f70d1SHeiko Stuebner { 237998f70d1SHeiko Stuebner const u8 cmd[] = { '@', 'C', '0' }; 238998f70d1SHeiko Stuebner struct qnap_mcu *mcu = data->cb_data; 239998f70d1SHeiko Stuebner int ret; 240998f70d1SHeiko Stuebner 241998f70d1SHeiko Stuebner ret = qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd)); 242998f70d1SHeiko Stuebner if (ret) { 243998f70d1SHeiko Stuebner dev_err(&mcu->serdev->dev, "MCU poweroff failed %d\n", ret); 244998f70d1SHeiko Stuebner return NOTIFY_STOP; 245998f70d1SHeiko Stuebner } 246998f70d1SHeiko Stuebner 247998f70d1SHeiko Stuebner return NOTIFY_DONE; 248998f70d1SHeiko Stuebner } 249998f70d1SHeiko Stuebner 250998f70d1SHeiko Stuebner static const struct qnap_mcu_variant qnap_ts433_mcu = { 251998f70d1SHeiko Stuebner .baud_rate = 115200, 252998f70d1SHeiko Stuebner .num_drives = 4, 253998f70d1SHeiko Stuebner .fan_pwm_min = 51, /* Specified in original model.conf */ 254998f70d1SHeiko Stuebner .fan_pwm_max = 255, 255998f70d1SHeiko Stuebner .usb_led = true, 256998f70d1SHeiko Stuebner }; 257998f70d1SHeiko Stuebner 258998f70d1SHeiko Stuebner static struct mfd_cell qnap_mcu_cells[] = { 259998f70d1SHeiko Stuebner { .name = "qnap-mcu-input", }, 260998f70d1SHeiko Stuebner { .name = "qnap-mcu-leds", }, 261998f70d1SHeiko Stuebner { .name = "qnap-mcu-hwmon", } 262998f70d1SHeiko Stuebner }; 263998f70d1SHeiko Stuebner 264998f70d1SHeiko Stuebner static int qnap_mcu_probe(struct serdev_device *serdev) 265998f70d1SHeiko Stuebner { 266998f70d1SHeiko Stuebner struct device *dev = &serdev->dev; 267998f70d1SHeiko Stuebner struct qnap_mcu *mcu; 268998f70d1SHeiko Stuebner int ret; 269998f70d1SHeiko Stuebner 270998f70d1SHeiko Stuebner mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL); 271998f70d1SHeiko Stuebner if (!mcu) 272998f70d1SHeiko Stuebner return -ENOMEM; 273998f70d1SHeiko Stuebner 274998f70d1SHeiko Stuebner mcu->serdev = serdev; 275998f70d1SHeiko Stuebner dev_set_drvdata(dev, mcu); 276998f70d1SHeiko Stuebner 277998f70d1SHeiko Stuebner mcu->variant = of_device_get_match_data(dev); 278998f70d1SHeiko Stuebner if (!mcu->variant) 279998f70d1SHeiko Stuebner return -ENODEV; 280998f70d1SHeiko Stuebner 281998f70d1SHeiko Stuebner mutex_init(&mcu->bus_lock); 282998f70d1SHeiko Stuebner init_completion(&mcu->reply.done); 283998f70d1SHeiko Stuebner 284998f70d1SHeiko Stuebner serdev_device_set_client_ops(serdev, &qnap_mcu_serdev_device_ops); 285998f70d1SHeiko Stuebner ret = devm_serdev_device_open(dev, serdev); 286998f70d1SHeiko Stuebner if (ret) 287998f70d1SHeiko Stuebner return ret; 288998f70d1SHeiko Stuebner 289998f70d1SHeiko Stuebner serdev_device_set_baudrate(serdev, mcu->variant->baud_rate); 290998f70d1SHeiko Stuebner serdev_device_set_flow_control(serdev, false); 291998f70d1SHeiko Stuebner 292998f70d1SHeiko Stuebner ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); 293998f70d1SHeiko Stuebner if (ret) 294998f70d1SHeiko Stuebner return dev_err_probe(dev, ret, "Failed to set parity\n"); 295998f70d1SHeiko Stuebner 296998f70d1SHeiko Stuebner ret = qnap_mcu_get_version(mcu); 297998f70d1SHeiko Stuebner if (ret) 298998f70d1SHeiko Stuebner return ret; 299998f70d1SHeiko Stuebner 300998f70d1SHeiko Stuebner ret = devm_register_sys_off_handler(dev, 301998f70d1SHeiko Stuebner SYS_OFF_MODE_POWER_OFF_PREPARE, 302998f70d1SHeiko Stuebner SYS_OFF_PRIO_DEFAULT, 303998f70d1SHeiko Stuebner &qnap_mcu_power_off, mcu); 304998f70d1SHeiko Stuebner if (ret) 305998f70d1SHeiko Stuebner return dev_err_probe(dev, ret, 306998f70d1SHeiko Stuebner "Failed to register poweroff handler\n"); 307998f70d1SHeiko Stuebner 308998f70d1SHeiko Stuebner for (int i = 0; i < ARRAY_SIZE(qnap_mcu_cells); i++) { 309998f70d1SHeiko Stuebner qnap_mcu_cells[i].platform_data = mcu->variant; 310998f70d1SHeiko Stuebner qnap_mcu_cells[i].pdata_size = sizeof(*mcu->variant); 311998f70d1SHeiko Stuebner } 312998f70d1SHeiko Stuebner 313998f70d1SHeiko Stuebner ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, qnap_mcu_cells, 314998f70d1SHeiko Stuebner ARRAY_SIZE(qnap_mcu_cells), NULL, 0, NULL); 315998f70d1SHeiko Stuebner if (ret) 316998f70d1SHeiko Stuebner return dev_err_probe(dev, ret, "Failed to add child devices\n"); 317998f70d1SHeiko Stuebner 318998f70d1SHeiko Stuebner return 0; 319998f70d1SHeiko Stuebner } 320998f70d1SHeiko Stuebner 321998f70d1SHeiko Stuebner static const struct of_device_id qnap_mcu_dt_ids[] = { 322998f70d1SHeiko Stuebner { .compatible = "qnap,ts433-mcu", .data = &qnap_ts433_mcu }, 323998f70d1SHeiko Stuebner { /* sentinel */ } 324998f70d1SHeiko Stuebner }; 325998f70d1SHeiko Stuebner MODULE_DEVICE_TABLE(of, qnap_mcu_dt_ids); 326998f70d1SHeiko Stuebner 327998f70d1SHeiko Stuebner static struct serdev_device_driver qnap_mcu_drv = { 328998f70d1SHeiko Stuebner .probe = qnap_mcu_probe, 329998f70d1SHeiko Stuebner .driver = { 330998f70d1SHeiko Stuebner .name = "qnap-mcu", 331998f70d1SHeiko Stuebner .of_match_table = qnap_mcu_dt_ids, 332998f70d1SHeiko Stuebner }, 333998f70d1SHeiko Stuebner }; 334998f70d1SHeiko Stuebner module_serdev_device_driver(qnap_mcu_drv); 335998f70d1SHeiko Stuebner 336998f70d1SHeiko Stuebner MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); 337998f70d1SHeiko Stuebner MODULE_DESCRIPTION("QNAP MCU core driver"); 338998f70d1SHeiko Stuebner MODULE_LICENSE("GPL"); 339