From patchwork Thu Jul 25 19:45:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Heiko_St=C3=BCbner?= X-Patchwork-Id: 13742136 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 16FDAC3DA5D for ; Thu, 25 Jul 2024 19:47:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=kAlEqIqo+kLTe8K+wVJ8nVCtj5Xk7l36KpxSL3falXo=; b=cssKrysi3y7a6E 1gEoZCvwd2uDb4p5vh26nIRj0+FBCTAn1vzsjuTWM0hvANpa/o5+LYB0f9UWMEQZvQqrFKeExMLwt EJ5cy0csbasgy47UVNK2YGJtqwmAagslkP0ZAllQUYVnxZA9GiW9jxOKTQCTqQcy8EevrgRcXPngV RfZzUG6P2IJ9CoGrCmc6d7I92g7HWsmojEYnceOiIn99uoxc/kEWspJ2NjMjFnRGcXEzACsUOAqYD R1dxnxA5KB5VONoBqRwnrDDLQJBhgLF7PEZ/oTJ0kvANN7BCoefWhlmsY0FXUUha0hre8PD3xL+tJ syuomTQUYv+bbJ2RRm6w==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4RH-00000001wpQ-1hP7; Thu, 25 Jul 2024 19:47:47 +0000 Received: from gloria.sntech.de ([185.11.138.130]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Pc-00000001w91-4A1L; Thu, 25 Jul 2024 19:46:11 +0000 Received: from i5e860cdd.versanet.de ([94.134.12.221] helo=phil.lan) by gloria.sntech.de with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sX4PO-0001rD-6Z; Thu, 25 Jul 2024 21:45:50 +0200 From: Heiko Stuebner To: lee@kernel.org, jdelvare@suse.com, linux@roeck-us.net, dmitry.torokhov@gmail.com, pavel@ucw.cz Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, heiko@sntech.de, ukleinek@debian.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-hwmon@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org Subject: [PATCH 1/7] dt-bindings: mfd: add binding for qnap,mcu devices Date: Thu, 25 Jul 2024 21:45:33 +0200 Message-Id: <20240725194539.1780790-2-heiko@sntech.de> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240725194539.1780790-1-heiko@sntech.de> References: <20240725194539.1780790-1-heiko@sntech.de> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240725_124605_177579_07731EB0 X-CRM114-Status: GOOD ( 14.99 ) X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+linux-rockchip=archiver.kernel.org@lists.infradead.org These MCUs can be found in network attached storage devices made by QNAP. They are connected to a serial port of the host device and provide functionality like LEDs, power-control and temperature monitoring. Signed-off-by: Heiko Stuebner Reviewed-by: Krzysztof Kozlowski --- .../devicetree/bindings/mfd/qnap,mcu.yaml | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/qnap,mcu.yaml diff --git a/Documentation/devicetree/bindings/mfd/qnap,mcu.yaml b/Documentation/devicetree/bindings/mfd/qnap,mcu.yaml new file mode 100644 index 0000000000000..3b1ad8cb54da3 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/qnap,mcu.yaml @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/qnap,mcu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: QNAP NAS on-board Microcontroller + +maintainers: + - Heiko Stuebner + +description: | + QNAP embeds a microcontroller on their NAS devices adding system feature + as PWM Fan control, additional LEDs, power button status and more. + +properties: + compatible: + enum: + - qnap,ts433-mcu + + "#cooling-cells": + const: 2 + + cooling-levels: + description: PWM duty cycle values corresponding to thermal cooling states. + $ref: /schemas/types.yaml#/definitions/uint32-array + items: + maximum: 255 + +required: + - compatible + +additionalProperties: false + +examples: + - | + uart { + mcu { + compatible = "qnap,ts433-mcu"; + #cooling-cells = <2>; + cooling-levels = <0 64 89 128 166 204 221 238>; + }; + }; From patchwork Thu Jul 25 19:45:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Heiko_St=C3=BCbner?= X-Patchwork-Id: 13742139 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 10C1EC3DA5D for ; Thu, 25 Jul 2024 19:48:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=n14C7bJsgBsU4Llui/xVVGGHKkaj7aYL2GYbOCxgjXo=; b=ga6jq69o015V44 NpYVwkcpBERFSpKGwS+iegIlwHwiUZmHTd3tfBBtqiUaOZKoGLtNBbVeA51amQQBghnVXhqDlt4x3 0gj1ytIC31RJezRveNpZlOjhSqOKzhFRwNA08ysgZCuvBZrfNmRfxJgmpn0vJvT+EzFhYBVoNmaTa d6rDmkbPCGWAfoKMDrK9ww4qvfuxIOQG6Fl5DQ/xC98DOYU5IQfiesuebLiN1qW2SYPAvPcG57qOy M2TcO9famJTCq5BjSeg4R/CPWdM/aNoP0iEJV74U4UI6S6Q1hFPivKnLpd4Bi1F9OAv//a7EW+2rO 3ZKMFKO4jtjmXIHby+/g==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4S7-00000001xGI-2oBW; Thu, 25 Jul 2024 19:48:39 +0000 Received: from gloria.sntech.de ([185.11.138.130]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Pe-00000001wAB-46NQ; Thu, 25 Jul 2024 19:46:12 +0000 Received: from i5e860cdd.versanet.de ([94.134.12.221] helo=phil.lan) by gloria.sntech.de with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sX4PO-0001rD-Sf; Thu, 25 Jul 2024 21:45:50 +0200 From: Heiko Stuebner To: lee@kernel.org, jdelvare@suse.com, linux@roeck-us.net, dmitry.torokhov@gmail.com, pavel@ucw.cz Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, heiko@sntech.de, ukleinek@debian.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-hwmon@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org Subject: [PATCH 2/7] mfd: add base driver for qnap-mcu devices Date: Thu, 25 Jul 2024 21:45:34 +0200 Message-Id: <20240725194539.1780790-3-heiko@sntech.de> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240725194539.1780790-1-heiko@sntech.de> References: <20240725194539.1780790-1-heiko@sntech.de> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240725_124607_182354_EC6601B2 X-CRM114-Status: GOOD ( 36.14 ) X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+linux-rockchip=archiver.kernel.org@lists.infradead.org These MCUs are used in network-attached-storage devices made by QNAP and provide additional functionality to the system. This adds the base driver that implements the serial protocol via serdev and additionally hooks into the poweroff handlers to turn off the parts of the system not supplied by the general PMIC. Turning off (at least the TSx33 devices using Rockchip SoCs) is twofold. Turning off the MCU does not turn off the SoC and turning off the SoC does not turn off the hard-drives. And if the MCU is not turned off, the system also won't start again until it is unplugged from power. So on shutdown the MCU needs to be turned off before the general PMIC. The protocol spoken by the MCU is sadly not documented, but was obtained by listening to the chatter on the serial port, as thankfully the "hal_app" program from QNAPs firmware allows triggering all/most MCU actions from the command line. The implementation of how to talk to the serial device got some inspiration from the rave-sp servdev mfd. Signed-off-by: Heiko Stuebner --- drivers/mfd/Kconfig | 10 + drivers/mfd/Makefile | 2 + drivers/mfd/qnap-mcu.c | 356 +++++++++++++++++++++++++++++++++++ include/linux/mfd/qnap-mcu.h | 28 +++ 4 files changed, 396 insertions(+) create mode 100644 drivers/mfd/qnap-mcu.c create mode 100644 include/linux/mfd/qnap-mcu.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 266b4f54af603..bdd2d94d167cb 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2306,6 +2306,16 @@ config MFD_INTEL_M10_BMC_PMCI additional drivers must be enabled in order to use the functionality of the device. +config MFD_QNAP_MCU + tristate "QNAP MCU core driver" + depends on SERIAL_DEV_BUS + help + Select this to get support for the QNAP MCU device found in + several devices of QNAP network attached storages. + + It implements the base serial protocol to talk to the device + and provides functions for the other parts to hook into. + config MFD_RSMU_I2C tristate "Renesas Synchronization Management Unit with I2C" depends on I2C && OF diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index c66f07edcd0e6..5c54ddd6b2f94 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -280,6 +280,8 @@ obj-$(CONFIG_MFD_INTEL_M10_BMC_PMCI) += intel-m10-bmc-pmci.o obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o +obj-$(CONFIG_MFD_QNAP_MCU) += qnap-mcu.o + rsmu-i2c-objs := rsmu_core.o rsmu_i2c.o rsmu-spi-objs := rsmu_core.o rsmu_spi.o obj-$(CONFIG_MFD_RSMU_I2C) += rsmu-i2c.o diff --git a/drivers/mfd/qnap-mcu.c b/drivers/mfd/qnap-mcu.c new file mode 100644 index 0000000000000..3a0f55ea41331 --- /dev/null +++ b/drivers/mfd/qnap-mcu.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * MFD core driver for the MCU in Qnap NAS devices that is connected + * via a dedicated UART port + * + * Copyright (C) 2024 Heiko Stuebner + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* The longest command found so far is 5 bytes long */ +#define QNAP_MCU_MAX_CMD_SIZE 5 +#define QNAP_MCU_MAX_DATA_SIZE 36 +#define QNAP_MCU_CHECKSUM_SIZE 1 + +#define QNAP_MCU_RX_BUFFER_SIZE \ + (QNAP_MCU_MAX_DATA_SIZE + QNAP_MCU_CHECKSUM_SIZE) + +#define QNAP_MCU_TX_BUFFER_SIZE \ + (QNAP_MCU_MAX_CMD_SIZE + QNAP_MCU_CHECKSUM_SIZE) + +/** + * struct qnap_mcu_reply - Reply to a command + * + * @data: Buffer to store reply payload in + * @length: Expected reply length, including the checksum + * @received: So far received number of bytes + * @done: Reply received completely + */ +struct qnap_mcu_reply { + u8 *data; + size_t length; + size_t received; + struct completion done; +}; + +/** + * struct qnap_mcu - QNAP NAS embedded controller + * + * @serdev: Pointer to underlying serdev + * @bus_lock: Lock to serialize access to the device + * @reply_lock: Lock protecting @reply + * @reply: Pointer to memory to store reply payload + * @variant: Device variant specific information + * @version: MCU firmware version + */ +struct qnap_mcu { + struct serdev_device *serdev; + struct mutex bus_lock; + struct mutex reply_lock; + struct qnap_mcu_reply *reply; + const struct qnap_mcu_variant *variant; + u8 version[4]; +}; + +/* + * The QNAP-MCU uses a basic XOR checksum. + * It is always the last byte and XORs the whole previous message. + */ +static u8 qnap_mcu_csum(const u8 *buf, size_t size) +{ + u8 csum = 0; + + while (size--) + csum ^= *buf++; + + return csum; +} + +static int qnap_mcu_write(struct qnap_mcu *sp, const u8 *data, u8 data_size) +{ + unsigned char tx[QNAP_MCU_TX_BUFFER_SIZE]; + size_t length = data_size + QNAP_MCU_CHECKSUM_SIZE; + + if (WARN_ON(length > sizeof(tx))) + return -ENOMEM; + + memcpy(tx, data, data_size); + tx[data_size] = qnap_mcu_csum(data, data_size); + + print_hex_dump_debug("qnap-mcu tx: ", DUMP_PREFIX_NONE, + 16, 1, tx, length, false); + + return serdev_device_write(sp->serdev, tx, length, HZ); +} + +static size_t qnap_mcu_receive_buf(struct serdev_device *serdev, + const u8 *buf, size_t size) +{ + struct device *dev = &serdev->dev; + struct qnap_mcu *mcu = dev_get_drvdata(dev); + struct qnap_mcu_reply *reply = mcu->reply; + const u8 *src = buf; + const u8 *end = buf + size; + + mutex_lock(&mcu->reply_lock); + if (!reply) { + dev_warn(dev, "received %lu bytes, we were not waiting for\n", + size); + mutex_unlock(&mcu->reply_lock); + return size; + } + + while (src < end) { + reply->data[reply->received] = *src++; + reply->received++; + + if (reply->received == reply->length) { + complete(&reply->done); + mutex_unlock(&mcu->reply_lock); + + /* + * We report the consumed number of bytes. If there + * are still bytes remaining (though there shouldn't) + * the serdev layer will re-execute this handler with + * the remainder of the Rx bytes. + */ + return src - buf; + } + } + + /* + * The only way to get out of the above loop and end up here + * is through consuming all of the supplied data, so here we + * report that we processed it all. + */ + mutex_unlock(&mcu->reply_lock); + return size; +} + +static const struct serdev_device_ops qnap_mcu_serdev_device_ops = { + .receive_buf = qnap_mcu_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +int qnap_mcu_exec(struct qnap_mcu *mcu, + u8 *cmd_data, size_t cmd_data_size, + u8 *reply_data, size_t reply_data_size) +{ + unsigned char rx[QNAP_MCU_RX_BUFFER_SIZE]; + size_t length = reply_data_size + QNAP_MCU_CHECKSUM_SIZE; + struct qnap_mcu_reply reply = { + .data = rx, + .length = length, + .received = 0, + .done = COMPLETION_INITIALIZER_ONSTACK(reply.done), + }; + int ret; + + if (WARN_ON(length > sizeof(rx))) + return -ENOMEM; + + mutex_lock(&mcu->bus_lock); + + mutex_lock(&mcu->reply_lock); + mcu->reply = &reply; + mutex_unlock(&mcu->reply_lock); + + qnap_mcu_write(mcu, cmd_data, cmd_data_size); + + if (!wait_for_completion_timeout(&reply.done, + msecs_to_jiffies(500))) { + dev_err(&mcu->serdev->dev, "Command timeout\n"); + ret = -ETIMEDOUT; + } else { + u8 crc = qnap_mcu_csum(rx, reply_data_size); + + print_hex_dump_debug("qnap-mcu rx: ", DUMP_PREFIX_NONE, + 16, 1, rx, length, false); + + if (crc != rx[reply_data_size]) { + dev_err(&mcu->serdev->dev, + "Checksum 0x%02x wrong for data\n", crc); + ret = -EIO; + } else { + memcpy(reply_data, rx, reply_data_size); + ret = 0; + } + } + + mutex_lock(&mcu->reply_lock); + mcu->reply = NULL; + mutex_unlock(&mcu->reply_lock); + + mutex_unlock(&mcu->bus_lock); + return ret; +} +EXPORT_SYMBOL_GPL(qnap_mcu_exec); + +int qnap_mcu_exec_with_ack(struct qnap_mcu *mcu, + u8 *cmd_data, size_t cmd_data_size) +{ + u8 ack[2]; + int ret; + + ret = qnap_mcu_exec(mcu, cmd_data, cmd_data_size, ack, sizeof(ack)); + if (ret) + return ret; + + /* Should return @0 */ + if (ack[0] != 0x40 || ack[1] != 0x30) { + dev_err(&mcu->serdev->dev, "Did not receive ack\n"); + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL_GPL(qnap_mcu_exec_with_ack); + +const struct qnap_mcu_variant *qnap_mcu_get_variant_data(struct qnap_mcu *mcu) +{ + return mcu->variant; +} +EXPORT_SYMBOL_GPL(qnap_mcu_get_variant_data); + +static int qnap_mcu_get_version(struct qnap_mcu *mcu) +{ + u8 cmd[] = { + [0] = 0x25, /* % */ + [1] = 0x56 /* V */ + }; + u8 rx[14]; + int ret; + + ret = qnap_mcu_exec(mcu, cmd, sizeof(cmd), rx, 6); + if (ret) + return ret; + + memcpy(mcu->version, &rx[2], 4); + + return 0; +} + +/* + * The MCU controls power to the peripherals but not the CPU. + * + * So using the pmic to power off the system keeps the MCU and hard-drives + * running. This also then prevents the system from turning back on until + * the MCU is turned off by unplugging the power-cable. + * Turning off the MCU alone on the other hand turns off the hard-drives, + * LEDs, etc while the main SoC stays running - including its network ports. + */ +static int qnap_mcu_power_off(struct sys_off_data *data) +{ + struct qnap_mcu *mcu = data->cb_data; + int ret; + u8 cmd[] = { + [0] = 0x40, /* @ */ + [1] = 0x43, /* C */ + [2] = 0x30 /* 0 */ + }; + + dev_dbg(&mcu->serdev->dev, "running MCU poweroff\n"); + ret = qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd)); + if (ret) { + dev_err(&mcu->serdev->dev, "MCU poweroff failed %d\n", ret); + return NOTIFY_STOP; + } + + return NOTIFY_DONE; +} + +static const struct qnap_mcu_variant qnap_ts433_mcu = { + .baud_rate = 115200, + .num_drives = 4, + .fan_pwm_min = 51, /* specified in original model.conf */ + .fan_pwm_max = 255, + .usb_led = true, +}; + +static const struct of_device_id qnap_mcu_dt_ids[] = { + { .compatible = "qnap,ts433-mcu", .data = &qnap_ts433_mcu }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, qnap_mcu_dt_ids); + +static const struct mfd_cell qnap_mcu_subdevs[] = { + { .name = "qnap-mcu-input", }, + { .name = "qnap-mcu-leds", }, + { .name = "qnap-mcu-hwmon", } +}; + +static int qnap_mcu_probe(struct serdev_device *serdev) +{ + struct device *dev = &serdev->dev; + struct qnap_mcu *mcu; + int ret; + + mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL); + if (!mcu) + return -ENOMEM; + + mcu->serdev = serdev; + dev_set_drvdata(dev, mcu); + + mcu->variant = of_device_get_match_data(dev); + if (!mcu->variant) + return -ENODEV; + + mutex_init(&mcu->bus_lock); + mutex_init(&mcu->reply_lock); + + serdev_device_set_client_ops(serdev, &qnap_mcu_serdev_device_ops); + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return ret; + + serdev_device_set_baudrate(serdev, mcu->variant->baud_rate); + serdev_device_set_flow_control(serdev, false); + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) { + dev_err(dev, "Failed to set parity\n"); + return ret; + } + + ret = qnap_mcu_get_version(mcu); + if (ret) + return ret; + + ret = devm_register_sys_off_handler(dev, + SYS_OFF_MODE_POWER_OFF_PREPARE, + SYS_OFF_PRIO_FIRMWARE, + &qnap_mcu_power_off, mcu); + if (ret) + return dev_err_probe(dev, ret, + "failed to register poweroff handler\n"); + + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, qnap_mcu_subdevs, + ARRAY_SIZE(qnap_mcu_subdevs), NULL, 0, NULL); + if (ret) + return dev_err_probe(dev, ret, "adding qnap mfd devices failed\n"); + + return 0; +} + +static struct serdev_device_driver qnap_mcu_drv = { + .probe = qnap_mcu_probe, + .driver = { + .name = "qnap-mcu", + .of_match_table = qnap_mcu_dt_ids, + }, +}; +module_serdev_device_driver(qnap_mcu_drv); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Heiko Stuebner "); +MODULE_DESCRIPTION("QNAP MCU core driver"); diff --git a/include/linux/mfd/qnap-mcu.h b/include/linux/mfd/qnap-mcu.h new file mode 100644 index 0000000000000..a71d753705546 --- /dev/null +++ b/include/linux/mfd/qnap-mcu.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +/* + * Core definitions for QNAP MCU MFD driver. + * Copyright (C) 2024 Heiko Stuebner + */ + +#ifndef _LINUX_QNAP_MCU_H_ +#define _LINUX_QNAP_MCU_H_ + +struct qnap_mcu; + +struct qnap_mcu_variant { + u32 baud_rate; + int num_drives; + int fan_pwm_min; + int fan_pwm_max; + bool usb_led; +}; + +int qnap_mcu_exec(struct qnap_mcu *mcu, + u8 *cmd_data, size_t cmd_data_size, + u8 *reply_data, size_t reply_data_size); +int qnap_mcu_exec_with_ack(struct qnap_mcu *mcu, + u8 *cmd_data, size_t cmd_data_size); +const struct qnap_mcu_variant *qnap_mcu_get_variant_data(struct qnap_mcu *mcu); + +#endif /* _LINUX_QNAP_MCU_H_ */ From patchwork Thu Jul 25 19:45:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Heiko_St=C3=BCbner?= X-Patchwork-Id: 13742141 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 7F119C3DA5D for ; Thu, 25 Jul 2024 19:49:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=Ckn1DplXRXZSpUI+YgNhsPt9AFg50/UvRnM5wWRdnZI=; b=ctrbPokv2ApocC vKUujTRm2erMRb5KBy/4oPmy2fDLALY0+uL/7x3b18/1y5uMiEfKQ8rCbG2HFPI9c0Yzt8VK8aL7W ecS3tR540+bLj1WC7g50/BwCu0mq+PqeUyFTixpmiZY46SIVRcmAZLhQynNcZTsHxUfkizaqvsD+J ycx2EgMIyB6V9GaXJTAMvhH0tDox1zSQokoROdSPX9R0EQxHdGeynyrf4oG0WjOQTyNiiKsQyelJR Z7DZz6veJ/Bkj+11cp1/2G0kOq7bAoMYPMXzSrIvMI7vZljsKxR6WKF5/TX/OFXtmOlPXp+wAAMiC BNLnjwS5yz7V+FW64i+A==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Sw-00000001xcH-2Xb9; Thu, 25 Jul 2024 19:49:30 +0000 Received: from gloria.sntech.de ([185.11.138.130]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Pf-00000001wAC-0P0I; Thu, 25 Jul 2024 19:46:13 +0000 Received: from i5e860cdd.versanet.de ([94.134.12.221] helo=phil.lan) by gloria.sntech.de with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sX4PP-0001rD-Iv; Thu, 25 Jul 2024 21:45:51 +0200 From: Heiko Stuebner To: lee@kernel.org, jdelvare@suse.com, linux@roeck-us.net, dmitry.torokhov@gmail.com, pavel@ucw.cz Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, heiko@sntech.de, ukleinek@debian.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-hwmon@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org Subject: [PATCH 3/7] leds: add driver for LEDs from qnap-mcu devices Date: Thu, 25 Jul 2024 21:45:35 +0200 Message-Id: <20240725194539.1780790-4-heiko@sntech.de> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240725194539.1780790-1-heiko@sntech.de> References: <20240725194539.1780790-1-heiko@sntech.de> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240725_124607_301569_FADF0546 X-CRM114-Status: GOOD ( 28.89 ) X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+linux-rockchip=archiver.kernel.org@lists.infradead.org This adds a driver that connects to the qnap-mcu mfd driver and provides access to the LEDs on it. Signed-off-by: Heiko Stuebner --- drivers/leds/Kconfig | 11 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-qnap-mcu.c | 247 +++++++++++++++++++++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 drivers/leds/leds-qnap-mcu.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 05e6af88b88cd..e128a8016b84c 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -555,6 +555,17 @@ config LEDS_PCA995X LED driver chips accessed via the I2C bus. Supported devices include PCA9955BTW, PCA9952TW and PCA9955TW. +config LEDS_QNAP_MCU + tristate "LED Support for QNAP MCU controllers" + depends on LEDS_CLASS + depends on MFD_QNAP_MCU + help + This option enables support for LEDs available on embedded + controllers used in QNAP NAS devices. + + This driver can also be built as a module. If so, the module + will be called qnap-mcu-leds. + config LEDS_WM831X_STATUS tristate "LED support for status LEDs on WM831x PMICs" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index effdfc6f1e951..27eb6cd827610 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o obj-$(CONFIG_LEDS_PWM) += leds-pwm.o +obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o diff --git a/drivers/leds/leds-qnap-mcu.c b/drivers/leds/leds-qnap-mcu.c new file mode 100644 index 0000000000000..e3244923759d2 --- /dev/null +++ b/drivers/leds/leds-qnap-mcu.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Driver for LEDs found on QNAP MCU devices + * + * Copyright (C) 2024 Heiko Stuebner + */ + +#include +#include +#include +#include +#include +#include + +enum qnap_mcu_err_led_mode { + QNAP_MCU_ERR_LED_ON = 0, + QNAP_MCU_ERR_LED_OFF = 1, + QNAP_MCU_ERR_LED_BLINK_FAST = 2, + QNAP_MCU_ERR_LED_BLINK_SLOW = 3, +}; + +struct qnap_mcu_err_led { + struct qnap_mcu *mcu; + struct led_classdev cdev; + u8 num; + u8 mode; +}; + +static inline struct qnap_mcu_err_led * + cdev_to_qnap_mcu_err_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct qnap_mcu_err_led, cdev); +} + +static int qnap_mcu_err_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev); + u8 cmd[] = { + [0] = 0x40, + [1] = 0x52, + [2] = 0x30 + err_led->num, + [3] = 0x30 + }; + + /* + * If the led is off, turn it on. Otherwise don't disturb + * a possible set blink-mode. + */ + if (value == 0) + err_led->mode = QNAP_MCU_ERR_LED_OFF; + else if (err_led->mode == QNAP_MCU_ERR_LED_OFF) + err_led->mode = QNAP_MCU_ERR_LED_ON; + + cmd[3] = 0x30 + err_led->mode; + + return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_err_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev); + u8 cmd[] = { + [0] = 0x40, + [1] = 0x52, + [2] = 0x30 + err_led->num, + [3] = 0x30 + }; + + /* LED is off, nothing to do */ + if (err_led->mode == QNAP_MCU_ERR_LED_OFF) + return 0; + + if (*delay_on < 500) { + *delay_on = 100; + *delay_off = 100; + err_led->mode = QNAP_MCU_ERR_LED_BLINK_FAST; + } else { + *delay_on = 500; + *delay_off = 500; + err_led->mode = QNAP_MCU_ERR_LED_BLINK_SLOW; + } + + cmd[3] = 0x30 + err_led->mode; + + return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, int num) +{ + struct qnap_mcu_err_led *err_led; + char tmp_buf[LED_MAX_NAME_SIZE]; + int ret; + + err_led = devm_kzalloc(dev, sizeof(*err_led), GFP_KERNEL); + if (!err_led) + return -ENOMEM; + + err_led->mcu = mcu; + err_led->num = num; + err_led->mode = QNAP_MCU_ERR_LED_OFF; + + snprintf(tmp_buf, LED_MAX_NAME_SIZE, "hdd%d:red:status", num + 1); + err_led->cdev.name = tmp_buf; + + err_led->cdev.brightness_set_blocking = qnap_mcu_err_led_set; + err_led->cdev.blink_set = qnap_mcu_err_led_blink_set; + err_led->cdev.brightness = 0; + err_led->cdev.max_brightness = 1; + + ret = devm_led_classdev_register(dev, &err_led->cdev); + if (ret) + return dev_err_probe(dev, ret, "failed to register hdd led %d", num); + + return qnap_mcu_err_led_set(&err_led->cdev, 0); +} + +enum qnap_mcu_usb_led_mode { + QNAP_MCU_USB_LED_ON = 1, + QNAP_MCU_USB_LED_OFF = 3, + QNAP_MCU_USB_LED_BLINK = 2, +}; + +struct qnap_mcu_usb_led { + struct qnap_mcu *mcu; + struct led_classdev cdev; + u8 mode; +}; + +static inline struct qnap_mcu_usb_led * + cdev_to_qnap_mcu_usb_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct qnap_mcu_usb_led, cdev); +} + +static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev); + u8 cmd[] = { + [0] = 0x40, + [1] = 0x43, + [2] = 0 + }; + + /* + * If the led is off, turn it on. Otherwise don't disturb + * a possible set blink-mode. + */ + if (value == 0) + usb_led->mode = QNAP_MCU_USB_LED_OFF; + else if (usb_led->mode == QNAP_MCU_USB_LED_OFF) + usb_led->mode = QNAP_MCU_USB_LED_ON; + + /* byte 3 is shared between the usb led target and setting the mode */ + cmd[2] = 0x44 | usb_led->mode; + + return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev); + u8 cmd[] = { + [0] = 0x40, + [1] = 0x43, + [2] = 0 + }; + + /* LED is off, nothing to do */ + if (usb_led->mode == QNAP_MCU_USB_LED_OFF) + return 0; + + *delay_on = 250; + *delay_off = 250; + usb_led->mode = QNAP_MCU_USB_LED_BLINK; + + /* byte 3 is shared between the usb led target and setting the mode */ + cmd[2] = 0x44 | usb_led->mode; + + return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu) +{ + struct qnap_mcu_usb_led *usb_led; + int ret; + + usb_led = devm_kzalloc(dev, sizeof(*usb_led), GFP_KERNEL); + if (!usb_led) + return -ENOMEM; + + usb_led->mcu = mcu; + usb_led->mode = QNAP_MCU_USB_LED_OFF; + usb_led->cdev.name = "usb:blue:disk"; + usb_led->cdev.brightness_set_blocking = qnap_mcu_usb_led_set; + usb_led->cdev.blink_set = qnap_mcu_usb_led_blink_set; + usb_led->cdev.brightness = 0; + usb_led->cdev.max_brightness = 1; + + ret = devm_led_classdev_register(dev, &usb_led->cdev); + if (ret) + return dev_err_probe(dev, ret, "failed to register usb led"); + + return qnap_mcu_usb_led_set(&usb_led->cdev, 0); +} + +static int qnap_mcu_leds_probe(struct platform_device *pdev) +{ + struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); + const struct qnap_mcu_variant *variant = qnap_mcu_get_variant_data(mcu); + int ret, i; + + for (i = 0; i < variant->num_drives; i++) { + ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register error led %d\n", i); + } + + if (variant->usb_led) { + ret = qnap_mcu_register_usb_led(&pdev->dev, mcu); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register usb led %d\n", i); + } + + return 0; +} + +static struct platform_driver qnap_mcu_leds_driver = { + .probe = qnap_mcu_leds_probe, + .driver = { + .name = "qnap-mcu-leds", + }, +}; +module_platform_driver(qnap_mcu_leds_driver); + +MODULE_ALIAS("platform:qnap-mcu-leds"); +MODULE_AUTHOR("Heiko Stuebner "); +MODULE_DESCRIPTION("QNAP MCU LEDs driver"); +MODULE_LICENSE("GPL"); From patchwork Thu Jul 25 19:45:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Heiko_St=C3=BCbner?= X-Patchwork-Id: 13742138 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 23A2AC3DA49 for ; Thu, 25 Jul 2024 19:48:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=I6saJLr8L2niUe1/cDsVcqr8HSJLo09GIP2MWsDNf40=; b=EV2/lx27Nkov14 SdZIm851EaS8qtUa5DVNNESyq6tvA7TTZ1HsA/tWGwS+mhiyTHdcLOgFOzgKWBvx9OkxL2wh+v6R9 g7c84F8rtl1sVQL+lNUAEa1yoGv5U4va0bYywDdCQUW4GR2QiZRi/gTQQOUs5YSfEffkScSK3kpM0 K2Tdk4lAdJbVmUj9V8d774uRUmFQkRne8mwft1LlTbdUvWY7i8/ulBGZtkayWloFb9w0glorHocdC PouXN1b9cs7bw459jG9zQ5hceHEYZdHuLAgLAS8uINnH+/nnhd3qs/87UkcqnS+QPh1y+0Yhu8RZi I3c2/AeioSHXTdqKYlVQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Rf-00000001wzM-3Bfi; Thu, 25 Jul 2024 19:48:11 +0000 Received: from gloria.sntech.de ([185.11.138.130]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Pe-00000001wAA-40OA; Thu, 25 Jul 2024 19:46:11 +0000 Received: from i5e860cdd.versanet.de ([94.134.12.221] helo=phil.lan) by gloria.sntech.de with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sX4PQ-0001rD-7y; Thu, 25 Jul 2024 21:45:52 +0200 From: Heiko Stuebner To: lee@kernel.org, jdelvare@suse.com, linux@roeck-us.net, dmitry.torokhov@gmail.com, pavel@ucw.cz Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, heiko@sntech.de, ukleinek@debian.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-hwmon@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org Subject: [PATCH 4/7] Input: add driver for the input part of qnap-mcu devices Date: Thu, 25 Jul 2024 21:45:36 +0200 Message-Id: <20240725194539.1780790-5-heiko@sntech.de> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240725194539.1780790-1-heiko@sntech.de> References: <20240725194539.1780790-1-heiko@sntech.de> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240725_124607_017911_AA200862 X-CRM114-Status: GOOD ( 28.25 ) X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+linux-rockchip=archiver.kernel.org@lists.infradead.org The MCU controls the power-button and beeper, so expose them as input device. There is of course no interrupt line, so the status of the power-button needs to be polled. To generate an event the power-button also needs to be held for 1-2 seconds, so the polling interval does not need to be overly fast. Signed-off-by: Heiko Stuebner --- drivers/input/misc/Kconfig | 12 +++ drivers/input/misc/Makefile | 1 + drivers/input/misc/qnap-mcu-input.c | 156 ++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 drivers/input/misc/qnap-mcu-input.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 6ba984d7f0b18..4ab8fe8301635 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -900,6 +900,18 @@ config INPUT_HISI_POWERKEY To compile this driver as a module, choose M here: the module will be called hisi_powerkey. +config INPUT_QNAP_MCU + tristate "Input Support for QNAP MCU controllers" + depends on MFD_QNAP_MCU + help + This option enables support for input elements available on + embedded controllers used in QNAP NAS devices. + + This includes a polled power-button as well as a beeper. + + To compile this driver as a module, choose M here: the + module will be called qnap-mcu-input. + config INPUT_RAVE_SP_PWRBUTTON tristate "RAVE SP Power button Driver" depends on RAVE_SP_CORE diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 04296a4abe8e8..05f5d0072b08f 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o obj-$(CONFIG_INPUT_POWERMATE) += powermate.o obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o +obj-$(CONFIG_INPUT_QNAP_MCU) += qnap-mcu-input.o obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o diff --git a/drivers/input/misc/qnap-mcu-input.c b/drivers/input/misc/qnap-mcu-input.c new file mode 100644 index 0000000000000..9bac7ea2c6b80 --- /dev/null +++ b/drivers/input/misc/qnap-mcu-input.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Driver for input events on QNAP-MCUs + * + * Copyright (C) 2024 Heiko Stuebner + */ + +#include +#include +#include +#include +#include +#include + +/* + * The power-key needs to be pressed for a while to create an event, + * so there is no use for overly frequent polling. + */ +#define POLL_INTERVAL 500 + +struct qnap_mcu_input_dev { + struct input_dev *input; + struct qnap_mcu *mcu; + struct device *dev; + + struct work_struct beep_work; + int beep_type; +}; + +static void qnap_mcu_input_poll(struct input_dev *input) +{ + struct qnap_mcu_input_dev *idev = input_get_drvdata(input); + u8 cmd[] = { + [0] = 0x40, /* @ */ + [1] = 0x43, /* C */ + [2] = 0x56 /* V */ + }; + u8 reply[4]; + int state, ret; + + /* poll the power button */ + ret = qnap_mcu_exec(idev->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); + if (ret) + return; + + /* First bytes must mirror the sent command */ + if (memcmp(cmd, reply, sizeof(cmd))) { + dev_err(idev->dev, "malformed data received\n"); + return; + } + + state = reply[3] - 0x30; + input_event(input, EV_KEY, KEY_POWER, state); + input_sync(input); +} + +static void qnap_mcu_input_beeper_work(struct work_struct *work) +{ + struct qnap_mcu_input_dev *idev = + container_of(work, struct qnap_mcu_input_dev, beep_work); + u8 cmd[] = { + [0] = 0x40, /* @ */ + [1] = 0x43, /* C */ + [2] = (idev->beep_type == SND_TONE) ? 0x33 : 0x32 + }; + + qnap_mcu_exec_with_ack(idev->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_input_event(struct input_dev *input, unsigned int type, + unsigned int code, int value) +{ + struct qnap_mcu_input_dev *idev = input_get_drvdata(input); + + if (type != EV_SND || (code != SND_BELL && code != SND_TONE)) + return -EOPNOTSUPP; + + if (value < 0) + return -EINVAL; + + /* beep runtime is determined by the MCU */ + if (value == 0) + return 0; + + /* Schedule work to actually turn the beeper on */ + idev->beep_type = code; + schedule_work(&idev->beep_work); + + return 0; +} + +static int qnap_mcu_input_probe(struct platform_device *pdev) +{ + struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); + struct qnap_mcu_input_dev *idev; + struct device *dev = &pdev->dev; + struct input_dev *input; + int ret; + + idev = devm_kzalloc(dev, sizeof(*idev), GFP_KERNEL); + if (!idev) + return -ENOMEM; + + input = devm_input_allocate_device(dev); + if (!input) + return dev_err_probe(dev, -ENOMEM, "no memory for input device\n"); + + idev->input = input; + idev->dev = dev; + idev->mcu = mcu; + + input_set_drvdata(input, idev); + + input->name = "qnap-mcu"; + input->phys = "qnap-mcu-input/input0"; + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + input->event = qnap_mcu_input_event; + + input_set_capability(input, EV_KEY, KEY_POWER); + input_set_capability(input, EV_SND, SND_BELL); + input_set_capability(input, EV_SND, SND_TONE); + + INIT_WORK(&idev->beep_work, qnap_mcu_input_beeper_work); + + ret = input_setup_polling(input, qnap_mcu_input_poll); + if (ret) + return dev_err_probe(dev, ret, "unable to set up polling\n"); + + input_set_poll_interval(input, POLL_INTERVAL); + + ret = input_register_device(input); + if (ret) + return dev_err_probe(dev, ret, "unable to register input device\n"); + + qnap_mcu_input_poll(input); + input_sync(input); + + return 0; +} + +static struct platform_driver qnap_mcu_input_driver = { + .probe = qnap_mcu_input_probe, + .driver = { + .name = "qnap-mcu-input", + }, +}; +module_platform_driver(qnap_mcu_input_driver); + +MODULE_ALIAS("platform:qnap-mcu-input"); +MODULE_AUTHOR("Heiko Stuebner "); +MODULE_DESCRIPTION("QNAP MCU input driver"); +MODULE_LICENSE("GPL"); From patchwork Thu Jul 25 19:45:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Heiko_St=C3=BCbner?= X-Patchwork-Id: 13742140 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id BA80BC3DA49 for ; Thu, 25 Jul 2024 19:49:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=nUNRHMj2XRrewsD4fiqZ1/JtiHl4PCXYkMaYxpNjw8w=; b=mPB0KiTJwXHpkL /EDw0Hzz7Cdxk9nhvdW7xe/SYlnefbIX1C9A30T4IvPHB13lpOBgViHh4SlLq2ovSo9+7h/lUp8N8 ZPiacp+jqDDThtaFClJTEYYOh8hyWOENj78Jrv25NQ9pOZYcIlNj53/MaaYh9VzUPjkUyx8kzRs9V tFU7bo1PHqODayaTfRC2KUHht8A3UrMbDpqBJzx2gX007HRp/+txvhnixSNrPVeeBy0QM/Tls+t9Q zcWYCbe8dBidWpedtwWtuyHVvj+U1NLtRkmzP5Qa7ycPnKaxXK0DiDQVKyHSfIH0msRbgdDlJd0EN IfTmQ8ABG+Kc4xaeybiA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4SX-00000001xQ5-2g5R; Thu, 25 Jul 2024 19:49:05 +0000 Received: from gloria.sntech.de ([185.11.138.130]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Pf-00000001wAD-06pu; Thu, 25 Jul 2024 19:46:12 +0000 Received: from i5e860cdd.versanet.de ([94.134.12.221] helo=phil.lan) by gloria.sntech.de with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sX4PQ-0001rD-Td; Thu, 25 Jul 2024 21:45:52 +0200 From: Heiko Stuebner To: lee@kernel.org, jdelvare@suse.com, linux@roeck-us.net, dmitry.torokhov@gmail.com, pavel@ucw.cz Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, heiko@sntech.de, ukleinek@debian.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-hwmon@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org Subject: [PATCH 5/7] hwmon: add driver for the hwmon parts of qnap-mcu devices Date: Thu, 25 Jul 2024 21:45:37 +0200 Message-Id: <20240725194539.1780790-6-heiko@sntech.de> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240725194539.1780790-1-heiko@sntech.de> References: <20240725194539.1780790-1-heiko@sntech.de> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240725_124607_232069_7EB02AD2 X-CRM114-Status: GOOD ( 30.68 ) X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+linux-rockchip=archiver.kernel.org@lists.infradead.org The MCU can be found on network-attached-storage devices made by QNAP and provides access to fan control including reading back its RPM as well as reading the temperature of the NAS case. Signed-off-by: Heiko Stuebner --- drivers/hwmon/Kconfig | 12 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/qnap-mcu-hwmon.c | 352 +++++++++++++++++++++++++++++++++ 3 files changed, 365 insertions(+) create mode 100644 drivers/hwmon/qnap-mcu-hwmon.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e14ae18a973b3..87b6ee463a967 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1806,6 +1806,18 @@ config SENSORS_PWM_FAN This driver can also be built as a module. If so, the module will be called pwm-fan. +config SENSORS_QNAP_MCU_HWMON + tristate "QNAP MCU hardware monitoring" + depends on MFD_QNAP_MCU + depends on THERMAL || THERMAL=n + help + Say yes here to enable support for fans and temperatures sensors + connected to a QNAP MCU, as found in a number of QNAP network + attached storage devices. + + This driver can also be built as a module. If so, the module + will be called qnap-mcu-hwmon. + config SENSORS_RASPBERRYPI_HWMON tristate "Raspberry Pi voltage monitor" depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e3f25475d1f04..b72c6a059e4af 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -188,6 +188,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o +obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o diff --git a/drivers/hwmon/qnap-mcu-hwmon.c b/drivers/hwmon/qnap-mcu-hwmon.c new file mode 100644 index 0000000000000..c4925ece165e2 --- /dev/null +++ b/drivers/hwmon/qnap-mcu-hwmon.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Driver for hwmon elements found on QNAP-MCU devices + * + * Copyright (C) 2024 Heiko Stuebner + */ + +#include +#include +#include +#include +#include +#include + +struct qnap_mcu_hwmon { + struct qnap_mcu *mcu; + struct device *dev; + + unsigned int pwm_min; + unsigned int pwm_max; + + unsigned int pwm_value; + bool pwm_enabled; + + unsigned int fan_state; + unsigned int fan_max_state; + unsigned int *fan_cooling_levels; + + struct thermal_cooling_device *cdev; + struct hwmon_chip_info info; +}; + +static int qnap_mcu_hwmon_get_rpm(struct qnap_mcu_hwmon *hwm) +{ + u8 cmd[] = { + [0] = 0x40, /* @ */ + [1] = 0x46, /* F */ + [2] = 0x41 /* A */ + }; + u8 reply[6]; + int ret; + + /* poll the fan rpm */ + ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); + if (ret) + return ret; + + /* First 2 bytes must mirror the sent command */ + if (memcmp(cmd, reply, 2)) { + dev_err(hwm->dev, "malformed data received\n"); + return -EIO; + } + + return reply[4] * 450; +} + +static int qnap_mcu_hwmon_update_fan(struct qnap_mcu_hwmon *hwm) +{ + u8 cmd[] = { + [0] = 0x40, /* @ */ + [1] = 0x46, /* F */ + [2] = 0x57, /* W */ + [3] = 0x30, /* 0 ... fan-id? */ + [4] = hwm->pwm_enabled ? hwm->pwm_value : 0 + }; + + /* poll the fan rpm */ + return qnap_mcu_exec_with_ack(hwm->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_hwmon_get_temp(struct qnap_mcu_hwmon *hwm) +{ + u8 cmd[] = { + [0] = 0x40, /* @ */ + [1] = 0x54, /* T */ + [2] = 0x33 /* 3 */ + }; + u8 reply[4]; + int ret; + + /* poll the fan rpm */ + ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); + if (ret) + return ret; + + /* First bytes must mirror the sent command */ + if (memcmp(cmd, reply, sizeof(cmd))) { + dev_err(hwm->dev, "malformed data received\n"); + return -EIO; + } + + /* + * There is an unknown bit set in bit7. + * Bits [6:0] report the actual temperature as returned by the + * original qnap firmware-tools, so just drop bit7 for now. + */ + return reply[3] & 0x7f; +} + +static int qnap_mcu_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_pwm_input: + if (val < hwm->pwm_min || val > hwm->pwm_max) + return -EINVAL; + + hwm->pwm_value = val; + return qnap_mcu_hwmon_update_fan(hwm); + case hwmon_pwm_enable: + if (val < 0 || val > 1) + return -EINVAL; + + hwm->pwm_enabled = val; + return qnap_mcu_hwmon_update_fan(hwm); + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int qnap_mcu_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev); + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + *val = hwm->pwm_value; + return 0; + case hwmon_pwm_enable: + *val = hwm->pwm_enabled; + return 0; + } + return -EOPNOTSUPP; + case hwmon_fan: + *val = qnap_mcu_hwmon_get_rpm(hwm); + return 0; + case hwmon_temp: + *val = qnap_mcu_hwmon_get_temp(hwm); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static umode_t qnap_mcu_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + return 0444; + + case hwmon_pwm: + return 0644; + + case hwmon_fan: + return 0444; + + default: + return 0; + } +} + +static const struct hwmon_ops qnap_mcu_hwmon_hwmon_ops = { + .is_visible = qnap_mcu_hwmon_is_visible, + .read = qnap_mcu_hwmon_read, + .write = qnap_mcu_hwmon_write, +}; + +/* thermal cooling device callbacks */ +static int qnap_mcu_hwmon_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct qnap_mcu_hwmon *hwm = cdev->devdata; + + if (!hwm) + return -EINVAL; + + *state = hwm->fan_max_state; + + return 0; +} + +static int qnap_mcu_hwmon_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct qnap_mcu_hwmon *hwm = cdev->devdata; + + if (!hwm) + return -EINVAL; + + *state = hwm->fan_state; + + return 0; +} + +static int qnap_mcu_hwmon_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct qnap_mcu_hwmon *hwm = cdev->devdata; + int ret; + + if (!hwm || (state > hwm->fan_max_state)) + return -EINVAL; + + if (state == hwm->fan_state) + return 0; + + if (hwm->fan_cooling_levels[state]) { + hwm->pwm_enabled = 1; + hwm->pwm_value = hwm->fan_cooling_levels[state]; + } else { + hwm->pwm_enabled = 0; + } + + ret = qnap_mcu_hwmon_update_fan(hwm); + if (ret) + return ret; + + hwm->fan_state = state; + + return ret; +} + +static const struct thermal_cooling_device_ops qnap_mcu_hwmon_cooling_ops = { + .get_max_state = qnap_mcu_hwmon_get_max_state, + .get_cur_state = qnap_mcu_hwmon_get_cur_state, + .set_cur_state = qnap_mcu_hwmon_set_cur_state, +}; + +static int qnap_mcu_hwmon_get_cooling_data(struct device *dev, struct qnap_mcu_hwmon *hwm) +{ + int num, i, ret; + + if (!device_property_present(dev->parent, "cooling-levels")) + return 0; + + ret = device_property_count_u32(dev->parent, "cooling-levels"); + if (ret <= 0) { + dev_err(dev, "Wrong data!\n"); + return ret ? : -EINVAL; + } + + num = ret; + hwm->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32), + GFP_KERNEL); + if (!hwm->fan_cooling_levels) + return -ENOMEM; + + ret = device_property_read_u32_array(dev->parent, "cooling-levels", + hwm->fan_cooling_levels, num); + if (ret) { + dev_err(dev, "Property 'cooling-levels' cannot be read!\n"); + return ret; + } + + for (i = 0; i < num; i++) { + if (hwm->fan_cooling_levels[i] > hwm->pwm_max) { + dev_err(dev, "fan state[%d]:%d > %d\n", i, + hwm->fan_cooling_levels[i], hwm->pwm_max); + return -EINVAL; + } + } + + hwm->fan_max_state = num - 1; + + return 0; +} + +static const struct hwmon_channel_info * const qnap_mcu_hwmon_channels[] = { + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE), + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static int qnap_mcu_hwmon_probe(struct platform_device *pdev) +{ + struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); + const struct qnap_mcu_variant *variant = qnap_mcu_get_variant_data(mcu); + struct qnap_mcu_hwmon *hwm; + struct thermal_cooling_device *cdev; + struct device *dev = &pdev->dev; + struct device *hwmon; + int ret; + + hwm = devm_kzalloc(dev, sizeof(*hwm), GFP_KERNEL); + if (!hwm) + return -ENOMEM; + + hwm->mcu = mcu; + hwm->dev = &pdev->dev; + hwm->pwm_min = variant->fan_pwm_min; + hwm->pwm_max = variant->fan_pwm_max; + + platform_set_drvdata(pdev, hwm); + + /* + * Set duty cycle to maximum allowed and enable PWM output as well as + * the regulator. In case of error nothing is changed + */ + hwm->pwm_value = hwm->pwm_max; + hwm->pwm_enabled = 1; + qnap_mcu_hwmon_update_fan(hwm); + + hwm->info.ops = &qnap_mcu_hwmon_hwmon_ops; + hwm->info.info = qnap_mcu_hwmon_channels; + + hwmon = devm_hwmon_device_register_with_info(dev, "qnapmcu", + hwm, &hwm->info, NULL); + if (IS_ERR(hwmon)) + return dev_err_probe(dev, PTR_ERR(hwmon), "Failed to register hwmon device\n"); + + ret = qnap_mcu_hwmon_get_cooling_data(dev, hwm); + if (ret) + return ret; + + hwm->fan_state = hwm->fan_max_state; + + if (IS_ENABLED(CONFIG_THERMAL)) { + cdev = devm_thermal_of_cooling_device_register(dev, + dev->parent->of_node, "qnap-mcu-hwmon", + hwm, &qnap_mcu_hwmon_cooling_ops); + if (IS_ERR(cdev)) + return dev_err_probe(dev, PTR_ERR(cdev), + "Failed to register qnap-mcu-hwmon as cooling device\n"); + hwm->cdev = cdev; + } + + return 0; +} + +static struct platform_driver qnap_mcu_hwmon_driver = { + .probe = qnap_mcu_hwmon_probe, + .driver = { + .name = "qnap-mcu-hwmon", + }, +}; +module_platform_driver(qnap_mcu_hwmon_driver); + +MODULE_ALIAS("platform:qnap-mcu-hwmon"); +MODULE_AUTHOR("Heiko Stuebner "); +MODULE_DESCRIPTION("QNAP MCU hwmon driver"); +MODULE_LICENSE("GPL"); From patchwork Thu Jul 25 19:45:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Heiko_St=C3=BCbner?= X-Patchwork-Id: 13742108 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 30966C3DA49 for ; Thu, 25 Jul 2024 19:47:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=ytSvN2Rf9MHf79qWya94jQ2h2UnlrOs0BWwZPkj0JLA=; b=3cakApSeAWAJDg EMksStRFIzuQYIK9yLWIzsqfjAlVMllXZLBdk7vUJvwRQNI7PzS8cTricXg/n5babm7vK6OUPnTPU TAr3SuYrA/vd3NcMroWgvIodLAbsYRO+6s3fHbkBbTQckBpSe27qohDJOHvz0FAg0c/YoWt7U1hEI hxB8SjYIVo24yWo4P/fWSxPcI9pUDgmyFMHW5ZjaI8JSNDGikHIKuE2bdIWisVEHy9bdebU3rWHby tDQRZivigGyX7ydohQTN6BR7uw935IoJTnZGAMeQqGCiz8oKnV6WjlEhS6G4E2lltvEyNyT4g5TGM FybnGm7zXiooejO2CNdA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4QW-00000001wWh-3Wl6; Thu, 25 Jul 2024 19:47:00 +0000 Received: from gloria.sntech.de ([185.11.138.130]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Pc-00000001w8h-2yZo; Thu, 25 Jul 2024 19:46:09 +0000 Received: from i5e860cdd.versanet.de ([94.134.12.221] helo=phil.lan) by gloria.sntech.de with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sX4PR-0001rD-Ie; Thu, 25 Jul 2024 21:45:53 +0200 From: Heiko Stuebner To: lee@kernel.org, jdelvare@suse.com, linux@roeck-us.net, dmitry.torokhov@gmail.com, pavel@ucw.cz Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, heiko@sntech.de, ukleinek@debian.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-hwmon@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org Subject: [PATCH 6/7] arm64: dts: rockchip: hook up the MCU on the QNAP TS433 Date: Thu, 25 Jul 2024 21:45:38 +0200 Message-Id: <20240725194539.1780790-7-heiko@sntech.de> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240725194539.1780790-1-heiko@sntech.de> References: <20240725194539.1780790-1-heiko@sntech.de> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240725_124604_773039_5BF9E542 X-CRM114-Status: GOOD ( 15.42 ) X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+linux-rockchip=archiver.kernel.org@lists.infradead.org The MCU is an important part of the device functionality. It provides functionality like fan-control, more leds, etc and even more important without it, the NAS-device cannot even fully turned off. Hook up the serial device to its uart and hook into the thermal management to control the fan according to the cpu temperature. While the MCU also provides a temperature sensor for the case, this one is just polled and does not provide functionality for handling trip points in hardware, so a lot of polling would be involved. As the cpu is only cooled passively in these devices, it's temperature rising will indicate the temperature level of the system just earlier. Signed-off-by: Heiko Stuebner --- .../boot/dts/rockchip/rk3568-qnap-ts433.dts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts b/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts index 6c4269b3d9597..bff21684a3970 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts +++ b/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts @@ -465,6 +465,54 @@ rgmii_phy0: ethernet-phy@0 { }; }; +/* + * The MCU can provide system temperature too, but only by polling and of + * course also cannot set trip points. So attach to the cpu thermal-zone + * instead to control the fan. + */ +&cpu_thermal { + trips { + case_fan0: case-fan0 { + hysteresis = <2000>; + temperature = <35000>; + type = "active"; + }; + + case_fan1: case-fan1 { + hysteresis = <2000>; + temperature = <45000>; + type = "active"; + }; + + case_fan2: case-fan2 { + hysteresis = <2000>; + temperature = <65000>; + type = "active"; + }; + }; + + cooling-maps { + /* + * Always provide some air movement, due to small case + * full of harddrives. + */ + map1 { + cooling-device = <&fan THERMAL_NO_LIMIT 1>; + trip = <&case_fan0>; + }; + + map2 { + cooling-device = <&fan 2 3>; + trip = <&case_fan1>; + }; + + map3 { + cooling-device = <&fan 4 THERMAL_NO_LIMIT>; + trip = <&case_fan2>; + }; + }; +}; + &pcie30phy { data-lanes = <1 2>; status = "okay"; @@ -563,6 +611,12 @@ &tsadc { */ &uart0 { status = "okay"; + + fan: mcu { + compatible = "qnap,ts433-mcu"; + #cooling-cells = <2>; + cooling-levels = <0 64 89 128 166 204 221 238>; + }; }; /* From patchwork Thu Jul 25 19:45:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Heiko_St=C3=BCbner?= X-Patchwork-Id: 13742107 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 071F6C3DA49 for ; Thu, 25 Jul 2024 19:46:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=jIHeQrGSfv2s9caymjlxgmGxhkmSpAVfFbemnnD4l80=; b=SR+FltxUXv9k2k 8c3LjlKgHyd0zRPNCKAMZC5uUMGMBkXKCUnyJpeEJQStaaXa9LDXqvOukCvC2pNZg2p2LUur1aMOp jMNABYaOILrxSt2WRHh4uM2iVUJrTgK0jIMT/MaGuCQGQNB56L4oL9fs3SrFSQgw0WObnlzAuxqki X3smhihXIFX6hW7ohpT2xnjXirEQgqZg6bm1iguTcJGIYyXFEseNYY3NqVH2tVSm0SY1c6kFKZdQ0 Cpni+7tNvgYyHz6HIxZO6HKuJ3BRqqxpk9YErH2XdFEqFR5DKLa1GJZptbZH2CC6/aXXId/IXD3bl jmHIrhjnmrtxt+MzA61A==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Q9-00000001wMy-3cjG; Thu, 25 Jul 2024 19:46:37 +0000 Received: from gloria.sntech.de ([185.11.138.130]) by bombadil.infradead.org with esmtps (Exim 4.97.1 #2 (Red Hat Linux)) id 1sX4Pc-00000001w8g-2yoH; Thu, 25 Jul 2024 19:46:09 +0000 Received: from i5e860cdd.versanet.de ([94.134.12.221] helo=phil.lan) by gloria.sntech.de with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sX4PS-0001rD-Bx; Thu, 25 Jul 2024 21:45:54 +0200 From: Heiko Stuebner To: lee@kernel.org, jdelvare@suse.com, linux@roeck-us.net, dmitry.torokhov@gmail.com, pavel@ucw.cz Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, heiko@sntech.de, ukleinek@debian.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-rockchip@lists.infradead.org, linux-hwmon@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org Subject: [PATCH 7/7] arm64: dts: rockchip: set hdd led labels on qnap-ts433 Date: Thu, 25 Jul 2024 21:45:39 +0200 Message-Id: <20240725194539.1780790-8-heiko@sntech.de> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240725194539.1780790-1-heiko@sntech.de> References: <20240725194539.1780790-1-heiko@sntech.de> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20240725_124604_773372_7E76E5C9 X-CRM114-Status: GOOD ( 11.76 ) X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+linux-rockchip=archiver.kernel.org@lists.infradead.org The automatically generated names for the LEDs from color and function do not match nicely for the 4 hdds, so set them manually per the label property to also match the LEDs generated from the MCU. Signed-off-by: Heiko Stuebner --- arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts b/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts index bff21684a3970..c35c11203e903 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts +++ b/arch/arm64/boot/dts/rockchip/rk3568-qnap-ts433.dts @@ -50,6 +50,7 @@ led-0 { color = ; function = LED_FUNCTION_DISK; gpios = <&gpio1 RK_PD5 GPIO_ACTIVE_LOW>; + label = "hdd1:green:disk"; linux,default-trigger = "disk-activity"; pinctrl-names = "default"; pinctrl-0 = <&hdd1_led_pin>; @@ -59,6 +60,7 @@ led-1 { color = ; function = LED_FUNCTION_DISK; gpios = <&gpio1 RK_PD6 GPIO_ACTIVE_LOW>; + label = "hdd2:green:disk"; linux,default-trigger = "disk-activity"; pinctrl-names = "default"; pinctrl-0 = <&hdd2_led_pin>; @@ -68,6 +70,7 @@ led-2 { color = ; function = LED_FUNCTION_DISK; gpios = <&gpio1 RK_PD7 GPIO_ACTIVE_LOW>; + label = "hdd3:green:disk"; linux,default-trigger = "disk-activity"; pinctrl-names = "default"; pinctrl-0 = <&hdd3_led_pin>; @@ -77,6 +80,7 @@ led-3 { color = ; function = LED_FUNCTION_DISK; gpios = <&gpio2 RK_PA0 GPIO_ACTIVE_LOW>; + label = "hdd4:green:disk"; linux,default-trigger = "disk-activity"; pinctrl-names = "default"; pinctrl-0 = <&hdd4_led_pin>;