From nobody Sat Sep 26 14:39:24 2026 Received: from smtpbguseast1.qq.com (smtpbguseast1.qq.com [54.204.34.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7387F531B06; Mon, 31 Aug 2026 15:09:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=54.204.34.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788189005; cv=none; b=IX8BA6omBCbx3tZ6IIgp9yKwggJ+EQBm0oezPvWkE9qLIJCVTSj31q/cohxkqBN/R0auUf6WvdIyOzUJJZmwotRb5QI8q8Zd+cBvzOxWqazTb9VsX8POR2RBfGNxX1JpMI+plIJEyMp4KcsMWT/XW2Md/SLRm1fUg7+OKqQFB5M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788189005; c=relaxed/simple; bh=+HY9roeovzLPLp9PfnmL3ix2ruSeghbpxMKPT8WC9Vs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Hf7/8prCSWN5LTPaXEyO+Pty4kZi2x/e/AC7WUcvPJPqSZ74eLz6iHFh99GCpkOSKvXB3UV6It6ilBK2C6KRmpirmrSbOD13tAXF45KcrvN8GfLFSdlQWpM7Dsp7hSq30+0Ht+XB0Z2ezhMpjG4oTcsiA6t+zYF+RW+MGfF9l48= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=radxa.com; spf=pass smtp.mailfrom=radxa.com; arc=none smtp.client-ip=54.204.34.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=radxa.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=radxa.com X-QQ-mid: zesmtpip2t1788188982t03bb5d34 X-QQ-Originating-IP: 4MRKJyemqlG+kh2C8mZXGJ6mJl4yOClYvuJoSQB9Wes= Received: from [192.168.30.32] ( [localhost]) by bizesmtp.qq.com (ESMTP) with id ; Mon, 31 Aug 2026 23:09:41 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 13722627836330943033 EX-QQ-RecipientCnt: 13 From: Xilin Wu Date: Mon, 31 Aug 2026 23:09:32 +0800 Subject: [PATCH 1/2] platform: arm64: Add Radxa SVC GLINK driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260831-radxa-svc-v1-1-7c028de6a387@radxa.com> References: <20260831-radxa-svc-v1-0-7c028de6a387@radxa.com> In-Reply-To: <20260831-radxa-svc-v1-0-7c028de6a387@radxa.com> To: Guenter Roeck , Jonathan Corbet , Shuah Khan , Randy Dunlap , Hans de Goede , =?utf-8?q?Ilpo_J=C3=A4rvinen?= , Bryan O'Donoghue Cc: linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, platform-driver-x86@vger.kernel.org, Xilin Wu X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=22911; i=sophon@radxa.com; h=from:subject:message-id; bh=+HY9roeovzLPLp9PfnmL3ix2ruSeghbpxMKPT8WC9Vs=; b=owGbwMvMwCVmdFg0fe08Iz/G02pJDFlTZxpVcn9deyTpwc6tEts3vtLls5D6Mdtu+y9VI3uf1 xPMJmvM7ShlYRDjYpAVU2RRiGeYy16Ze+2pWKkezBxWJpAhDFycAjARlZ2MDPceCEbsX8v/ZWJk 13eHHLFtees+H3igkZn9y2bdTRHbv7cZGR43Ze9e8dBK8Z1A5e3EFa5ebZY5HZoTiviuSrf/mf3 GnREA X-Developer-Key: i=sophon@radxa.com; a=openpgp; fpr=205F009D07796DD6E516752E32C31567AD9E324E X-QQ-SENDSIZE: 520 Feedback-ID: zesmtpip:radxa.com:qybglogicsvrsz:qybglogicsvrsz3b-0 X-QQ-XMAILINFO: MeFNLUIt+GJ0WDPs/sM2ts28EXBGk5VeNztIof0SemNgDrzIj8iyCZ5w t/765lI4xBCbI/EFfw9crBuKiIXQMt2JgKvf3v7MoFMUPx65GQdGD33iMe8rL1GjdRnoAT5 sZ8+3r1Mqp1erIJVGAUMMyl+XO+Ir3MJ1nTfhG/14YQrvKDBFoMfTXzuXwbf4uUoJ9K83Da UaxbhZKw99GJv+XeXIojjZjP4RC6EbLmREg8EM+MzhapZL3KoXHSy4DwBcZ+cpsPOfexbfx JjTJKjRO9Lw6tM7MxdIK6WdxOTI4ix/RLbKdg/PyGWfJVxVXJCzCrssj1aUvkQLrVzCRvjM I+y526jdBXPY7JfNsXCYft9oYmP84i/vJBs6zmhNiv8V/VwHQd9IKCUZznifOok0+ukBGi1 TdD6olHt+kcuYxQRhvrQKlHlDkN7ipcLWFKDc/KFN3QGRBBk9BR3Ph85c+NL1DDtJdacxRK 5vhSPFcJ3cvqa3SzfiB9Ip/nn1Ur0FTNx4+24fEbft/WKvE+anAbVnELDKHSOdQtQjOqTu5 vOz7P99Ryg8lpEhNAaRf9SKokZbyel8PjqwY8Gr8Br3cO8UfXL6+dspsyy9UHq66UWm+l/9 5bQ4+QBt7ZIwtWuHpBrw6ChligElpzZOJ1eiRL5KHvanTJ6doP3bUV0+cqpjANw3uGKzpy4 9D7HtiKKTQ/DgrzpxHDyitkqFhAO50RzLaxXFx0bexvSA28FqVWMZm+69fQ6GJMcZdw9WXf E8pvqYFthF2MTFGeZa14RlW4UG3250K3//MJo6nObSF39S0EuV4pv2KRDuRgPfkALFpORgL a3qiJ5PhYooqqVUI8NAB6CDPxk87Pr9GcWJBWoVlob8SVGVsnkq+KNuDeoS8q+wQpl53v4O VucJ38WM5qiSIM2gaObtYGTvY9RreZtgU8w0+60tsTlEU+P98VuYWsf/oXJuYwyNSlelvEO jA/DthdqiGxZaRsbpCYmQlHOl70S1+jwoey5YxQ+h7IyUeq3Uny4l5sJKythK9lDEilagOU QhoJlS/F/Wfx30hCsBxV4fRssUobY= X-QQ-XMRINFO: OWPUhxQsoeAVwkVaQIEGSKwwgKCxK/fD5g== X-QQ-RECHKSPAM: 0 Add an rpmsg client for the RADXA_SVC_ADSP_APPS firmware service used on supported Radxa boards with Qualcomm SoCs. Expose fan control through the standard hwmon pwm1 and pwm1_enable attributes. SET_PROFILE only changes the fan curve, so map its quiet and performance curves to automatic modes instead of platform_profile. Signed-off-by: Xilin Wu --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/radxa-svc-glink.rst | 32 ++ MAINTAINERS | 8 + drivers/platform/arm64/Kconfig | 13 + drivers/platform/arm64/Makefile | 1 + drivers/platform/arm64/radxa_svc_glink.c | 735 +++++++++++++++++++++++++++= ++++ 6 files changed, 790 insertions(+) diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 9955a525436a..08a2062b4f84 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -234,6 +234,7 @@ Hardware Monitoring Kernel Drivers pwm-fan q54sj108a2 qnap-mcu-hwmon + radxa-svc-glink raspberrypi-hwmon sbrmi sbtsi_temp diff --git a/Documentation/hwmon/radxa-svc-glink.rst b/Documentation/hwmon/= radxa-svc-glink.rst new file mode 100644 index 000000000000..c464b130a4e4 --- /dev/null +++ b/Documentation/hwmon/radxa-svc-glink.rst @@ -0,0 +1,32 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver radxa-svc-glink +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D + +Description +----------- + +The Radxa SVC GLINK driver communicates with the ``RADXA_SVC_ADSP_APPS`` +firmware service found on supported Radxa boards with Qualcomm SoCs. The +firmware provides fan control. + +Fan control +----------- + +The fan controller provides the following attributes: + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D =3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D +``pwm1`` RW Current fan PWM value. Writes are accepted in manu= al + mode and use values from 0 to 255. +``pwm1_enable`` RW Fan control mode, as described below. +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D =3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D + +The supported ``pwm1_enable`` values are: + + - 0: fan at full speed + - 1: manual control using ``pwm1`` + - 2: automatic control using the quiet curve + - 3: automatic control using the performance curve + +When manual mode is selected, the driver starts with the current fan speed= . If +the current speed cannot be determined, it starts at full speed. diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..9e4685759194 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22719,6 +22719,14 @@ F: Documentation/ABI/testing/sysfs-bus-rbd F: drivers/block/rbd.c F: drivers/block/rbd_types.h =20 +RADXA SVC GLINK DRIVER +M: Xilin Wu +L: linux-arm-msm@vger.kernel.org +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/radxa-svc-glink.rst +F: drivers/platform/arm64/radxa_svc_glink.c + RAGE128 FRAMEBUFFER DISPLAY DRIVER L: linux-fbdev@vger.kernel.org S: Orphan diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index e32e01b2a9bd..60c1e541fe89 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -103,4 +103,17 @@ config EC_QCOM_HAMOA =20 This driver currently supports Hamoa/Purwa/Glymur reference devices. =20 +config RADXA_SVC_GLINK + tristate "Radxa SVC GLINK driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on RPMSG + depends on HWMON + help + Enable support for the Radxa SVC firmware service found on supported + Radxa boards using Qualcomm SoCs. The driver communicates with the + RADXA_SVC_ADSP_APPS service over rpmsg and exposes fan control through + the standard hwmon interface. + + Say M or Y here to include this support. + endif # ARM64_PLATFORM_DEVICES diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makef= ile index 7681be4a46e9..327acb7b983c 100644 --- a/drivers/platform/arm64/Makefile +++ b/drivers/platform/arm64/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_EC_HUAWEI_GAOKUN) +=3D huawei-gaokun-ec.o obj-$(CONFIG_EC_LENOVO_YOGA_C630) +=3D lenovo-yoga-c630.o obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) +=3D lenovo-thinkpad-t14s.o obj-$(CONFIG_EC_QCOM_HAMOA) +=3D qcom-hamoa-ec.o +obj-$(CONFIG_RADXA_SVC_GLINK) +=3D radxa_svc_glink.o diff --git a/drivers/platform/arm64/radxa_svc_glink.c b/drivers/platform/ar= m64/radxa_svc_glink.c new file mode 100644 index 000000000000..1c91bfabcd12 --- /dev/null +++ b/drivers/platform/arm64/radxa_svc_glink.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Radxa SVC GLINK driver. + * + * Copyright (c) 2026 Radxa Computer (Shenzhen) Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RADXA_SVC_MAGIC 0x58444152 /* "RADX" */ +#define RADXA_SVC_VERSION 1 +#define RADXA_SVC_TIMEOUT msecs_to_jiffies(5000) + +#define RADXA_SVC_MAX_TX_PAYLOAD 64 +#define RADXA_SVC_MAX_RX_PAYLOAD 2048 + +#define RADXA_SVC_OP_GET_VERSION 0x02 +#define RADXA_SVC_OP_SET_PROFILE 0x10 +#define RADXA_SVC_OP_GET_PROFILE 0x11 +#define RADXA_SVC_OP_FAN_GET_STATE 0x50 +#define RADXA_SVC_OP_FAN_SET_CONTROL 0x51 +#define RADXA_SVC_OP_FAN_GET_CONTROL 0x52 + +#define RADXA_SVC_PROFILE_QUIET 0 +#define RADXA_SVC_PROFILE_PERFORMANCE 1 + +#define RADXA_SVC_FAN_CONTROL_FULL_SPEED 0 +#define RADXA_SVC_FAN_CONTROL_MANUAL 1 +#define RADXA_SVC_FAN_CONTROL_AUTO 2 +#define RADXA_SVC_FAN_PWM_MAX 255 + +#define RADXA_SVC_PWM_MODE_FULL_SPEED 0 +#define RADXA_SVC_PWM_MODE_MANUAL 1 +#define RADXA_SVC_PWM_MODE_AUTO_QUIET 2 +#define RADXA_SVC_PWM_MODE_AUTO_PERFORMANCE 3 + +#define RADXA_SVC_CAP_PROFILE BIT(1) +#define RADXA_SVC_CAP_FANCTL BIT(5) +#define RADXA_SVC_CAP_FANCTL_CTRL BIT(6) + +#define RADXA_SVC_REQUIRED_CAPS (RADXA_SVC_CAP_PROFILE | \ + RADXA_SVC_CAP_FANCTL | \ + RADXA_SVC_CAP_FANCTL_CTRL) + +struct radxa_svc_hdr { + __le32 magic; + __le16 version; + __le16 header_size; + __le32 opcode; + __le32 seq; + __le32 status; + __le32 payload_len; +} __packed; + +struct radxa_svc_version_resp { + __le16 major; + __le16 minor; + __le32 caps; +} __packed; + +struct radxa_svc_fan_state_resp { + __le32 profile; + __le32 running; + __le32 emergency; + __le32 cpu_valid; + __le32 cpu_stale_ticks; + __le32 cpu_temp_deci_c; + __le32 gpu_valid; + __le32 gpu_stale_ticks; + __le32 gpu_temp_deci_c; + __le32 current_duty_ns; + __le32 target_duty_ns; + __le32 pwm_channel; + __le32 pwm_period_ns; + __le32 loop_count; + __le32 fault_count; + __le32 last_status; + __le32 control_mode; + __le32 manual_pwm; +} __packed; + +struct radxa_svc_fan_control_req { + __le32 control_mode; + __le32 manual_pwm; +} __packed; + +struct radxa_svc_fan_control_resp { + __le32 control_mode; + __le32 manual_pwm; +} __packed; + +struct radxa_svc_glink { + struct device *dev; + struct rpmsg_device *rpdev; + struct device *hwmon_dev; + + struct mutex xfer_lock; /* serializes request/response transactions */ + struct mutex fan_lock; /* serializes multi-request fan configuration */ + spinlock_t rsp_lock; + struct completion rsp; + bool pending; + bool shutting_down; + u32 pending_seq; + u32 seq; + + u32 rsp_opcode; + int rsp_status; + size_t rsp_len; + u8 rsp_payload[RADXA_SVC_MAX_RX_PAYLOAD]; + u32 caps; +}; + +static int radxa_svc_request(struct radxa_svc_glink *svc, u32 opcode, + const void *req_payload, size_t req_len, + void *rsp_payload, size_t *rsp_len); + +static int radxa_svc_get_version(struct radxa_svc_glink *svc, + struct radxa_svc_version_resp *resp) +{ + size_t len =3D sizeof(*resp); + int ret; + + ret =3D radxa_svc_request(svc, RADXA_SVC_OP_GET_VERSION, NULL, 0, + resp, &len); + if (ret) + return ret; + + if (len < sizeof(*resp)) + return -EIO; + + return 0; +} + +static int radxa_svc_check_version(struct device *dev, + const struct radxa_svc_version_resp *resp, + u32 *caps) +{ + u32 missing_caps; + u16 major; + + major =3D le16_to_cpu(resp->major); + *caps =3D le32_to_cpu(resp->caps); + + if (major !=3D RADXA_SVC_VERSION) + return dev_err_probe(dev, -EPROTONOSUPPORT, + "unsupported service major version %u\n", + major); + + missing_caps =3D RADXA_SVC_REQUIRED_CAPS & ~*caps; + if (missing_caps) + return dev_err_probe(dev, -ENODEV, + "service missing required caps 0x%08x\n", + missing_caps); + + return 0; +} + +static int radxa_svc_get_profile(struct radxa_svc_glink *svc, u32 *profile) +{ + __le32 resp; + size_t len =3D sizeof(resp); + int ret; + + ret =3D radxa_svc_request(svc, RADXA_SVC_OP_GET_PROFILE, NULL, 0, + &resp, &len); + if (ret) + return ret; + + if (len < sizeof(resp)) + return -EIO; + + *profile =3D le32_to_cpu(resp); + + return 0; +} + +static int radxa_svc_set_profile(struct radxa_svc_glink *svc, u32 profile) +{ + __le32 payload; + + switch (profile) { + case RADXA_SVC_PROFILE_QUIET: + case RADXA_SVC_PROFILE_PERFORMANCE: + break; + default: + return -EINVAL; + } + + payload =3D cpu_to_le32(profile); + + return radxa_svc_request(svc, RADXA_SVC_OP_SET_PROFILE, &payload, + sizeof(payload), NULL, NULL); +} + +static int radxa_svc_fan_get_state(struct radxa_svc_glink *svc, + struct radxa_svc_fan_state_resp *resp) +{ + size_t len =3D sizeof(*resp); + size_t base_len =3D offsetof(struct radxa_svc_fan_state_resp, + control_mode); + int ret; + + ret =3D radxa_svc_request(svc, RADXA_SVC_OP_FAN_GET_STATE, NULL, 0, + resp, &len); + if (ret) + return ret; + + if (len < base_len) + return -EIO; + + return 0; +} + +static int radxa_svc_fan_get_control(struct radxa_svc_glink *svc, + struct radxa_svc_fan_control_resp *resp) +{ + size_t len =3D sizeof(*resp); + u32 control_mode; + u32 manual_pwm; + int ret; + + ret =3D radxa_svc_request(svc, RADXA_SVC_OP_FAN_GET_CONTROL, NULL, 0, + resp, &len); + if (ret) + return ret; + + if (len < sizeof(*resp)) + return -EIO; + + control_mode =3D le32_to_cpu(resp->control_mode); + manual_pwm =3D le32_to_cpu(resp->manual_pwm); + if (control_mode > RADXA_SVC_FAN_CONTROL_AUTO || + manual_pwm > RADXA_SVC_FAN_PWM_MAX) + return -EIO; + + return 0; +} + +static int radxa_svc_fan_set_control(struct radxa_svc_glink *svc, + u32 control_mode, u32 manual_pwm) +{ + struct radxa_svc_fan_control_req req; + + if (control_mode > RADXA_SVC_FAN_CONTROL_AUTO || + manual_pwm > RADXA_SVC_FAN_PWM_MAX) + return -EINVAL; + + req.control_mode =3D cpu_to_le32(control_mode); + req.manual_pwm =3D cpu_to_le32(manual_pwm); + + return radxa_svc_request(svc, RADXA_SVC_OP_FAN_SET_CONTROL, &req, + sizeof(req), NULL, NULL); +} + +static int radxa_svc_fan_duty_to_pwm(u32 period_ns, u32 duty_ns, u32 *valu= e) +{ + u64 pwm; + + if (!period_ns) + return -ENODATA; + + if (duty_ns > period_ns) + return -EPROTO; + + pwm =3D (u64)(period_ns - duty_ns) * RADXA_SVC_FAN_PWM_MAX; + *value =3D min_t(u64, DIV_ROUND_CLOSEST_ULL(pwm, period_ns), + RADXA_SVC_FAN_PWM_MAX); + + return 0; +} + +static int radxa_svc_request(struct radxa_svc_glink *svc, u32 opcode, + const void *req_payload, size_t req_len, + void *rsp_payload, size_t *rsp_len) +{ + struct radxa_svc_hdr *hdr; + unsigned long flags; + size_t tx_len; + u32 seq; + u8 *tx_buf; + int ret; + + if (req_len > RADXA_SVC_MAX_TX_PAYLOAD) + return -EMSGSIZE; + + tx_len =3D sizeof(*hdr) + req_len; + tx_buf =3D kzalloc(tx_len, GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + + mutex_lock(&svc->xfer_lock); + + if (!svc->rpdev || !svc->rpdev->ept) { + ret =3D -ENODEV; + goto out_unlock; + } + + seq =3D ++svc->seq; + if (!seq) + seq =3D ++svc->seq; + + hdr =3D (struct radxa_svc_hdr *)tx_buf; + hdr->magic =3D cpu_to_le32(RADXA_SVC_MAGIC); + hdr->version =3D cpu_to_le16(RADXA_SVC_VERSION); + hdr->header_size =3D cpu_to_le16(sizeof(*hdr)); + hdr->opcode =3D cpu_to_le32(opcode); + hdr->seq =3D cpu_to_le32(seq); + hdr->status =3D cpu_to_le32(0); + hdr->payload_len =3D cpu_to_le32(req_len); + + if (req_len) + memcpy(tx_buf + sizeof(*hdr), req_payload, req_len); + + reinit_completion(&svc->rsp); + + spin_lock_irqsave(&svc->rsp_lock, flags); + if (svc->shutting_down) { + spin_unlock_irqrestore(&svc->rsp_lock, flags); + ret =3D -ENODEV; + goto out_unlock; + } + + svc->pending =3D true; + svc->pending_seq =3D seq; + svc->rsp_opcode =3D 0; + svc->rsp_status =3D 0; + svc->rsp_len =3D 0; + spin_unlock_irqrestore(&svc->rsp_lock, flags); + + ret =3D rpmsg_send(svc->rpdev->ept, tx_buf, tx_len); + if (ret) { + spin_lock_irqsave(&svc->rsp_lock, flags); + svc->pending =3D false; + spin_unlock_irqrestore(&svc->rsp_lock, flags); + goto out_unlock; + } + + if (!wait_for_completion_timeout(&svc->rsp, RADXA_SVC_TIMEOUT)) { + spin_lock_irqsave(&svc->rsp_lock, flags); + svc->pending =3D false; + spin_unlock_irqrestore(&svc->rsp_lock, flags); + ret =3D -ETIMEDOUT; + goto out_unlock; + } + + spin_lock_irqsave(&svc->rsp_lock, flags); + if (svc->shutting_down) { + ret =3D -ENODEV; + } else if (svc->rsp_opcode !=3D opcode) { + ret =3D -EIO; + } else { + ret =3D svc->rsp_status; + if (rsp_payload && rsp_len) { + size_t copy_len =3D min(*rsp_len, svc->rsp_len); + + memcpy(rsp_payload, svc->rsp_payload, copy_len); + if (*rsp_len < svc->rsp_len && !ret) + ret =3D -EMSGSIZE; + *rsp_len =3D svc->rsp_len; + } + } + spin_unlock_irqrestore(&svc->rsp_lock, flags); + +out_unlock: + mutex_unlock(&svc->xfer_lock); + kfree(tx_buf); + + return ret; +} + +static int radxa_svc_rpmsg_callback(struct rpmsg_device *rpdev, void *data, + int len, void *priv, u32 addr) +{ + struct radxa_svc_glink *svc =3D dev_get_drvdata(&rpdev->dev); + const struct radxa_svc_hdr *hdr =3D data; + unsigned long flags; + size_t header_size; + size_t payload_len; + bool do_complete =3D false; + u32 seq; + + if (len < sizeof(*hdr)) + goto bad_msg; + + if (le32_to_cpu(hdr->magic) !=3D RADXA_SVC_MAGIC || + le16_to_cpu(hdr->version) !=3D RADXA_SVC_VERSION) + goto bad_msg; + + header_size =3D le16_to_cpu(hdr->header_size); + if (header_size < sizeof(*hdr) || header_size > len) + goto bad_msg; + + payload_len =3D le32_to_cpu(hdr->payload_len); + if (payload_len > len - header_size || + payload_len > RADXA_SVC_MAX_RX_PAYLOAD) + goto bad_msg; + + seq =3D le32_to_cpu(hdr->seq); + + spin_lock_irqsave(&svc->rsp_lock, flags); + if (svc->pending && seq =3D=3D svc->pending_seq) { + svc->rsp_opcode =3D le32_to_cpu(hdr->opcode); + svc->rsp_status =3D (s32)le32_to_cpu(hdr->status); + svc->rsp_len =3D payload_len; + memcpy(svc->rsp_payload, data + header_size, payload_len); + svc->pending =3D false; + do_complete =3D true; + } + spin_unlock_irqrestore(&svc->rsp_lock, flags); + + if (do_complete) + complete(&svc->rsp); + + return 0; + +bad_msg: + return 0; +} + +static int radxa_svc_fan_get_pwm_mode(struct radxa_svc_glink *svc, + long *mode) +{ + struct radxa_svc_fan_control_resp control =3D {}; + u32 control_mode; + u32 profile; + int ret; + + ret =3D radxa_svc_fan_get_control(svc, &control); + if (ret) + return ret; + + control_mode =3D le32_to_cpu(control.control_mode); + + switch (control_mode) { + case RADXA_SVC_FAN_CONTROL_FULL_SPEED: + *mode =3D RADXA_SVC_PWM_MODE_FULL_SPEED; + return 0; + case RADXA_SVC_FAN_CONTROL_MANUAL: + *mode =3D RADXA_SVC_PWM_MODE_MANUAL; + return 0; + case RADXA_SVC_FAN_CONTROL_AUTO: + break; + default: + return -EIO; + } + + ret =3D radxa_svc_get_profile(svc, &profile); + if (ret) + return ret; + + switch (profile) { + case RADXA_SVC_PROFILE_QUIET: + *mode =3D RADXA_SVC_PWM_MODE_AUTO_QUIET; + return 0; + case RADXA_SVC_PROFILE_PERFORMANCE: + *mode =3D RADXA_SVC_PWM_MODE_AUTO_PERFORMANCE; + return 0; + default: + return -EIO; + } +} + +static int radxa_svc_fan_set_auto_mode(struct radxa_svc_glink *svc, + u32 profile) +{ + struct radxa_svc_fan_control_resp control =3D {}; + int ret; + + ret =3D radxa_svc_fan_get_control(svc, &control); + if (ret) + return ret; + + ret =3D radxa_svc_set_profile(svc, profile); + if (ret) + return ret; + + return radxa_svc_fan_set_control(svc, RADXA_SVC_FAN_CONTROL_AUTO, + le32_to_cpu(control.manual_pwm)); +} + +static int radxa_svc_fan_set_pwm_mode(struct radxa_svc_glink *svc, long mo= de) +{ + struct radxa_svc_fan_state_resp state =3D {}; + u32 duty_ns; + u32 manual_pwm; + u32 period_ns; + int ret; + + switch (mode) { + case RADXA_SVC_PWM_MODE_FULL_SPEED: + return radxa_svc_fan_set_control(svc, + RADXA_SVC_FAN_CONTROL_FULL_SPEED, + RADXA_SVC_FAN_PWM_MAX); + case RADXA_SVC_PWM_MODE_MANUAL: + ret =3D radxa_svc_fan_get_state(svc, &state); + if (ret) + return ret; + + period_ns =3D le32_to_cpu(state.pwm_period_ns); + duty_ns =3D le32_to_cpu(state.current_duty_ns); + if (!period_ns) { + manual_pwm =3D RADXA_SVC_FAN_PWM_MAX; + } else { + ret =3D radxa_svc_fan_duty_to_pwm(period_ns, duty_ns, + &manual_pwm); + if (ret) + return ret; + } + + return radxa_svc_fan_set_control(svc, + RADXA_SVC_FAN_CONTROL_MANUAL, + manual_pwm); + case RADXA_SVC_PWM_MODE_AUTO_QUIET: + return radxa_svc_fan_set_auto_mode(svc, + RADXA_SVC_PROFILE_QUIET); + case RADXA_SVC_PWM_MODE_AUTO_PERFORMANCE: + return radxa_svc_fan_set_auto_mode(svc, + RADXA_SVC_PROFILE_PERFORMANCE); + default: + return -EINVAL; + } +} + +static umode_t radxa_svc_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type !=3D hwmon_pwm || channel) + return 0; + + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: + return 0644; + default: + return 0; + } +} + +static int radxa_svc_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct radxa_svc_glink *svc =3D dev_get_drvdata(dev); + struct radxa_svc_fan_state_resp state =3D {}; + u32 duty_ns; + u32 period_ns; + u32 pwm; + int ret; + + if (type !=3D hwmon_pwm || channel) + return -EOPNOTSUPP; + + switch (attr) { + case hwmon_pwm_input: + ret =3D radxa_svc_fan_get_state(svc, &state); + if (ret) + return ret; + + period_ns =3D le32_to_cpu(state.pwm_period_ns); + duty_ns =3D le32_to_cpu(state.current_duty_ns); + ret =3D radxa_svc_fan_duty_to_pwm(period_ns, duty_ns, &pwm); + if (ret) + return ret; + + *val =3D pwm; + return 0; + case hwmon_pwm_enable: + mutex_lock(&svc->fan_lock); + ret =3D radxa_svc_fan_get_pwm_mode(svc, val); + mutex_unlock(&svc->fan_lock); + + return ret; + default: + return -EOPNOTSUPP; + } +} + +static int radxa_svc_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct radxa_svc_glink *svc =3D dev_get_drvdata(dev); + struct radxa_svc_fan_control_resp control =3D {}; + int ret; + + if (type !=3D hwmon_pwm || channel) + return -EOPNOTSUPP; + + mutex_lock(&svc->fan_lock); + + switch (attr) { + case hwmon_pwm_input: + if (val < 0 || val > RADXA_SVC_FAN_PWM_MAX) { + ret =3D -EINVAL; + break; + } + + ret =3D radxa_svc_fan_get_control(svc, &control); + if (ret) + break; + + if (le32_to_cpu(control.control_mode) !=3D + RADXA_SVC_FAN_CONTROL_MANUAL) { + ret =3D -EINVAL; + break; + } + + ret =3D radxa_svc_fan_set_control(svc, + RADXA_SVC_FAN_CONTROL_MANUAL, + val); + break; + case hwmon_pwm_enable: + ret =3D radxa_svc_fan_set_pwm_mode(svc, val); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + + mutex_unlock(&svc->fan_lock); + + return ret; +} + +static const struct hwmon_ops radxa_svc_hwmon_ops =3D { + .is_visible =3D radxa_svc_hwmon_is_visible, + .read =3D radxa_svc_hwmon_read, + .write =3D radxa_svc_hwmon_write, +}; + +static const struct hwmon_channel_info * const radxa_svc_hwmon_info[] =3D { + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE), + NULL +}; + +static const struct hwmon_chip_info radxa_svc_hwmon_chip_info =3D { + .ops =3D &radxa_svc_hwmon_ops, + .info =3D radxa_svc_hwmon_info, +}; + +static int radxa_svc_hwmon_init(struct radxa_svc_glink *svc) +{ + svc->hwmon_dev =3D devm_hwmon_device_register_with_info(svc->dev, + "radxa_svc_glink", + svc, + &radxa_svc_hwmon_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(svc->hwmon_dev); +} + +static int radxa_svc_rpmsg_probe(struct rpmsg_device *rpdev) +{ + struct radxa_svc_version_resp version =3D {}; + struct radxa_svc_glink *svc; + int ret; + + svc =3D devm_kzalloc(&rpdev->dev, sizeof(*svc), GFP_KERNEL); + if (!svc) + return -ENOMEM; + + svc->dev =3D &rpdev->dev; + svc->rpdev =3D rpdev; + mutex_init(&svc->xfer_lock); + mutex_init(&svc->fan_lock); + spin_lock_init(&svc->rsp_lock); + init_completion(&svc->rsp); + + dev_set_drvdata(&rpdev->dev, svc); + + ret =3D radxa_svc_get_version(svc, &version); + if (ret) + return dev_err_probe(&rpdev->dev, ret, + "failed to read service version\n"); + + ret =3D radxa_svc_check_version(&rpdev->dev, &version, &svc->caps); + if (ret) + return ret; + + ret =3D radxa_svc_hwmon_init(svc); + if (ret) + return dev_err_probe(&rpdev->dev, ret, + "failed to register hwmon\n"); + + return 0; +} + +static void radxa_svc_rpmsg_remove(struct rpmsg_device *rpdev) +{ + struct radxa_svc_glink *svc =3D dev_get_drvdata(&rpdev->dev); + unsigned long flags; + + spin_lock_irqsave(&svc->rsp_lock, flags); + svc->shutting_down =3D true; + svc->pending =3D false; + spin_unlock_irqrestore(&svc->rsp_lock, flags); + complete_all(&svc->rsp); + + mutex_lock(&svc->xfer_lock); + svc->rpdev =3D NULL; + mutex_unlock(&svc->xfer_lock); +} + +static const struct rpmsg_device_id radxa_svc_rpmsg_id_match[] =3D { + { "RADXA_SVC_ADSP_APPS" }, + {} +}; +MODULE_DEVICE_TABLE(rpmsg, radxa_svc_rpmsg_id_match); + +static struct rpmsg_driver radxa_svc_rpmsg_driver =3D { + .probe =3D radxa_svc_rpmsg_probe, + .remove =3D radxa_svc_rpmsg_remove, + .callback =3D radxa_svc_rpmsg_callback, + .id_table =3D radxa_svc_rpmsg_id_match, + .drv =3D { + .name =3D "radxa_svc_glink", + }, +}; +module_rpmsg_driver(radxa_svc_rpmsg_driver); + +MODULE_AUTHOR("Xilin Wu "); +MODULE_DESCRIPTION("Radxa SVC GLINK driver"); +MODULE_LICENSE("GPL"); --=20 2.55.0 From nobody Sat Sep 26 14:39:24 2026 Received: from smtpbgau2.qq.com (smtpbgau2.qq.com [54.206.34.216]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E35D24A99D1; Mon, 31 Aug 2026 15:09:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=54.206.34.216 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788189004; cv=none; b=nDtlkEDIkx/jSVOiVlPvlguO4Gxfo85Tr4U/1ukwGEfebYzx4sSFItiFsiR/QZw8ju91Sc+HDio3s2rOVQZxrmGOt2Um2XMHzbZPluEAbjLitlwgz3Aw5ZAKAnkw77Tc0qKMjGJGKh7L9HiFcsrkbS6cUW13wAGRlCtXgtwbow4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788189004; c=relaxed/simple; bh=KR5zIC9MjS9KDL+E3J1GN63LF/NOwojJqczy6h31rWo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=EHASEoBb7rttx6z5fiClYUvhLwL+MfoOC0q290RJdrNBuDTtz8jOwnKECcxQMJzgMIFBdqrXAo12RLu9yGOEXR2LYgh09uO+KnoO/st1Jb1XmPFzUnD3j1o6WbP4fGZB2MrUJk4yBPYKlreqnIIzYCYWq3NsR3Xan3r0KGAI67E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=radxa.com; spf=pass smtp.mailfrom=radxa.com; arc=none smtp.client-ip=54.206.34.216 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=radxa.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=radxa.com X-QQ-mid: zesmtpip2t1788188985tad3ad028 X-QQ-Originating-IP: +93kN03FFqH1G4SnEf9Z7LhYXbcRYqmYq0ZGb7jdsFw= Received: from [192.168.30.32] ( [localhost]) by bizesmtp.qq.com (ESMTP) with id ; Mon, 31 Aug 2026 23:09:43 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 5458043899803403433 EX-QQ-RecipientCnt: 13 From: Xilin Wu Date: Mon, 31 Aug 2026 23:09:33 +0800 Subject: [PATCH 2/2] platform: arm64: radxa-svc: Add hwmon sensors Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260831-radxa-svc-v1-2-7c028de6a387@radxa.com> References: <20260831-radxa-svc-v1-0-7c028de6a387@radxa.com> In-Reply-To: <20260831-radxa-svc-v1-0-7c028de6a387@radxa.com> To: Guenter Roeck , Jonathan Corbet , Shuah Khan , Randy Dunlap , Hans de Goede , =?utf-8?q?Ilpo_J=C3=A4rvinen?= , Bryan O'Donoghue Cc: linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, platform-driver-x86@vger.kernel.org, Xilin Wu X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=17010; i=sophon@radxa.com; h=from:subject:message-id; bh=KR5zIC9MjS9KDL+E3J1GN63LF/NOwojJqczy6h31rWo=; b=owGbwMvMwCVmdFg0fe08Iz/G02pJDFlTZxrFnNaMr0/Yn2f2N3r3zmnzr+udFd6p4b5pVpTRB Xet42x3OkpZGMS4GGTFFFkU4hnmslfmXnsqVqoHM4eVCWQIAxenAEyERY/hf7aSfZbWqWkJW+9Z RkS2hqxraKm24DMJ57pxpO21tED5G4b/QfMz8t13Tl/EOlVVgfe8Rl3FWTvRxjmnDuXdur3mvOo ePgA= X-Developer-Key: i=sophon@radxa.com; a=openpgp; fpr=205F009D07796DD6E516752E32C31567AD9E324E X-QQ-SENDSIZE: 520 Feedback-ID: zesmtpip:radxa.com:qybglogicsvrsz:qybglogicsvrsz3b-0 X-QQ-XMAILINFO: NlVgJxuVJf8S399MKbIkmeXMujjosvqWQB68gAWE7fOCITBRlBv96zTJ 1Gzd10Z63PfsxWLE0o8nkn90N5gSi+nVvBoyevgvWA4Y69S7V2We2IP56GpWKQJ25b0Bikp DGiKymmZPygBvFK3WqyhnDEw5UMqrs99pbwrZllca+lnwTTbVw5+5kcJ+YXE6Kkzsi84gko 7OewwvFZBbaqoRG4Zt4f1VB13jm/aIeJKMTzUlGyBZQFtBE/jxlcxOYEq2/YcPJKG8FyW2n VQNraP5xJJac311DN1qluHK9k0pAAX/leR/yav6FaCBaoJ5a/2FHB4jx2ExmkcG/rEKk9NC 99O5KHw9loiXSlG1Ho/5WsAY34/CLYouolcpm2fPLUSrFeHpHD7qD3sNtuaTFjp3QBQoIR2 fjzWkSkS8ytCUJe1jT6WPlE/WT5bN59pmE2qiOWuZ7jTRXkDhnJ4ODpV7o0JwFY9NqtJ+k6 33KC5Y/5AVQ4IVrOClL09oabot4XaKZPPudu2CFauH1aGfBODvlUmkcGs8A/upPVvMaHJcE w0MXXrtDkWvfsj62v6DWLRAFfCwHPZfhNmAMogb1BRBfZYPtZN63eIm6aRQeqqKf0e923gd WGkKOuZUWnXtrALKtfz9Ld3L9JmNPKNXE3DuBoumoiw/MWi6758Ywrr/yMe8s7rS9hKk5E+ 5lLP9wG/JPLqfqV7TH4EFGVBTmj8Ia6HCY+i24G1u5aoQTDHcioIHKLV5BdGqGZaFZCRc3X w0WdChHVGs+93HtnXJTge1CRzIzdy3JQqUy8kvRrSXkXxhCdCQoDAnrTNcvWenpYuebTIiz AajZ5D6zxAVkhL1G3KGvpnZGyYES8QNSldghVWahj9w8wi7CGp9lZvNIixiyVv2eIXD0x8R UlOn9+XJO0URU9+/xCSTbLgvCGNkKtc7JDfrxMMT1EvqXllgnvRoICEPvJmGZzWyOleniEP c6AcE+E5Dld5ica2SXjV/Aqv5xymAbDXkrMnerLphjX1B0nwrfx6gF0UpsuDD0OGONtrKYU oyXhdbvXyF2MO1BiQzJ7XOBAOBx2iP4QyQR5xt4tTEpzfgFYwMAiXMHAPUzn8= X-QQ-XMRINFO: Mp0Kj//9VHAxzExpfF+O8yhSrljjwrznVg== X-QQ-RECHKSPAM: 0 Add support for the optional sensor discovery and sampling operations. Expose firmware temperature, voltage, current, and combined sensors as read-only hwmon devices. Validate firmware descriptors and samples, cache complete readings, and calculate power for combined voltage and current sensors. Signed-off-by: Xilin Wu --- Documentation/hwmon/radxa-svc-glink.rst | 11 +- drivers/platform/arm64/Kconfig | 4 +- drivers/platform/arm64/radxa_svc_glink.c | 470 +++++++++++++++++++++++++++= ++++ 3 files changed, 482 insertions(+), 3 deletions(-) diff --git a/Documentation/hwmon/radxa-svc-glink.rst b/Documentation/hwmon/= radxa-svc-glink.rst index c464b130a4e4..99750a1f876e 100644 --- a/Documentation/hwmon/radxa-svc-glink.rst +++ b/Documentation/hwmon/radxa-svc-glink.rst @@ -8,7 +8,8 @@ Description =20 The Radxa SVC GLINK driver communicates with the ``RADXA_SVC_ADSP_APPS`` firmware service found on supported Radxa boards with Qualcomm SoCs. The -firmware provides fan control. +firmware provides fan control and dynamically discoverable temperature, +voltage, current, and power sensors. =20 Fan control ----------- @@ -30,3 +31,11 @@ The supported ``pwm1_enable`` values are: =20 When manual mode is selected, the driver starts with the current fan speed= . If the current speed cannot be determined, it starts at full speed. + +Sensors +------- + +Each sensor discovered through the firmware service is registered as a +separate hwmon device. Depending on the sensor type, it exposes the standa= rd +``temp1_input``, ``in0_input``, ``curr1_input``, and ``power1_input`` +attributes and their corresponding labels. diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index 60c1e541fe89..c4e70a3c5089 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -111,8 +111,8 @@ config RADXA_SVC_GLINK help Enable support for the Radxa SVC firmware service found on supported Radxa boards using Qualcomm SoCs. The driver communicates with the - RADXA_SVC_ADSP_APPS service over rpmsg and exposes fan control through - the standard hwmon interface. + RADXA_SVC_ADSP_APPS service over rpmsg and exposes fan control and + sensor readings through the standard hwmon interface. =20 Say M or Y here to include this support. =20 diff --git a/drivers/platform/arm64/radxa_svc_glink.c b/drivers/platform/ar= m64/radxa_svc_glink.c index 1c91bfabcd12..b2a4f0c5317a 100644 --- a/drivers/platform/arm64/radxa_svc_glink.c +++ b/drivers/platform/arm64/radxa_svc_glink.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,8 @@ #define RADXA_SVC_OP_FAN_GET_STATE 0x50 #define RADXA_SVC_OP_FAN_SET_CONTROL 0x51 #define RADXA_SVC_OP_FAN_GET_CONTROL 0x52 +#define RADXA_SVC_OP_SENSOR_LIST 0x60 +#define RADXA_SVC_OP_SENSOR_READ 0x61 =20 #define RADXA_SVC_PROFILE_QUIET 0 #define RADXA_SVC_PROFILE_PERFORMANCE 1 @@ -48,11 +51,29 @@ #define RADXA_SVC_CAP_PROFILE BIT(1) #define RADXA_SVC_CAP_FANCTL BIT(5) #define RADXA_SVC_CAP_FANCTL_CTRL BIT(6) +#define RADXA_SVC_CAP_SENSORS BIT(7) =20 #define RADXA_SVC_REQUIRED_CAPS (RADXA_SVC_CAP_PROFILE | \ RADXA_SVC_CAP_FANCTL | \ RADXA_SVC_CAP_FANCTL_CTRL) =20 +#define RADXA_SVC_SENSOR_MAX_SENSORS 64 +#define RADXA_SVC_SENSOR_NAME_LEN 32 +#define RADXA_SVC_SENSOR_PAGE_MAX 5 +#define RADXA_SVC_SENSOR_CACHE_TIME msecs_to_jiffies(50) + +#define RADXA_SVC_SENSOR_TEMP 1 +#define RADXA_SVC_SENSOR_VOLTAGE 2 +#define RADXA_SVC_SENSOR_CURRENT 3 +#define RADXA_SVC_SENSOR_VOLTAGE_CURRENT 4 + +#define RADXA_SVC_SENSOR_VALID_TEMP BIT(0) +#define RADXA_SVC_SENSOR_VALID_VOLTAGE BIT(1) +#define RADXA_SVC_SENSOR_VALID_CURRENT BIT(2) +#define RADXA_SVC_SENSOR_VALID_MASK (RADXA_SVC_SENSOR_VALID_TEMP | \ + RADXA_SVC_SENSOR_VALID_VOLTAGE | \ + RADXA_SVC_SENSOR_VALID_CURRENT) + struct radxa_svc_hdr { __le32 magic; __le16 version; @@ -100,10 +121,57 @@ struct radxa_svc_fan_control_resp { __le32 manual_pwm; } __packed; =20 +struct radxa_svc_sensor_list_req { + __le32 start_index; + __le32 max_entries; +} __packed; + +struct radxa_svc_sensor_desc { + __le32 sensor_id; + __le32 sensor_type; + char name[RADXA_SVC_SENSOR_NAME_LEN]; +} __packed; + +struct radxa_svc_sensor_list_resp { + __le32 total_count; + __le32 returned_count; + struct radxa_svc_sensor_desc entries[]; +} __packed; + +struct radxa_svc_sensor_read_req { + __le32 sensor_id; +} __packed; + +struct radxa_svc_sensor_read_resp { + __le32 sensor_id; + __le32 valid_mask; + __le32 temp_millic; + __le32 voltage_mv; + __le32 current_ma; +} __packed; + +struct radxa_svc_glink; + +struct radxa_svc_sensor { + struct radxa_svc_glink *svc; + struct device *hwmon_dev; + struct mutex cache_lock; /* protects cached sample fields */ + unsigned long last_updated; + u32 sensor_id; + u32 sensor_type; + char name[RADXA_SVC_SENSOR_NAME_LEN]; + s32 temp_millic; + s32 voltage_mv; + s64 current_ma; + bool cache_valid; +}; + struct radxa_svc_glink { struct device *dev; struct rpmsg_device *rpdev; struct device *hwmon_dev; + struct radxa_svc_sensor *sensors; + u32 num_sensors; =20 struct mutex xfer_lock; /* serializes request/response transactions */ struct mutex fan_lock; /* serializes multi-request fan configuration */ @@ -428,6 +496,403 @@ static int radxa_svc_rpmsg_callback(struct rpmsg_devi= ce *rpdev, void *data, return 0; } =20 +static u32 radxa_svc_sensor_expected_mask(u32 sensor_type) +{ + switch (sensor_type) { + case RADXA_SVC_SENSOR_TEMP: + return RADXA_SVC_SENSOR_VALID_TEMP; + case RADXA_SVC_SENSOR_VOLTAGE: + return RADXA_SVC_SENSOR_VALID_VOLTAGE; + case RADXA_SVC_SENSOR_CURRENT: + return RADXA_SVC_SENSOR_VALID_CURRENT; + case RADXA_SVC_SENSOR_VOLTAGE_CURRENT: + return RADXA_SVC_SENSOR_VALID_VOLTAGE | + RADXA_SVC_SENSOR_VALID_CURRENT; + default: + return 0; + } +} + +static int radxa_svc_sensor_update(struct radxa_svc_sensor *sensor) +{ + struct radxa_svc_sensor_read_req req; + struct radxa_svc_sensor_read_resp resp =3D {}; + unsigned long cache_expires; + u32 expected_mask; + u32 valid_mask; + size_t len =3D sizeof(resp); + int ret; + + mutex_lock(&sensor->cache_lock); + + cache_expires =3D sensor->last_updated + RADXA_SVC_SENSOR_CACHE_TIME; + if (sensor->cache_valid && time_before(jiffies, cache_expires)) { + ret =3D 0; + goto out_unlock; + } + + req.sensor_id =3D cpu_to_le32(sensor->sensor_id); + ret =3D radxa_svc_request(sensor->svc, RADXA_SVC_OP_SENSOR_READ, + &req, sizeof(req), &resp, &len); + if (ret) + goto out_unlock; + + if (len !=3D sizeof(resp) || + le32_to_cpu(resp.sensor_id) !=3D sensor->sensor_id) { + ret =3D -EPROTO; + goto out_unlock; + } + + valid_mask =3D le32_to_cpu(resp.valid_mask); + expected_mask =3D radxa_svc_sensor_expected_mask(sensor->sensor_type); + if ((valid_mask & RADXA_SVC_SENSOR_VALID_MASK) !=3D expected_mask || + valid_mask & ~RADXA_SVC_SENSOR_VALID_MASK) { + ret =3D -EPROTO; + goto out_unlock; + } + + sensor->temp_millic =3D (s32)le32_to_cpu(resp.temp_millic); + sensor->voltage_mv =3D (s32)le32_to_cpu(resp.voltage_mv); + sensor->current_ma =3D (s32)le32_to_cpu(resp.current_ma); + sensor->last_updated =3D jiffies; + sensor->cache_valid =3D true; + ret =3D 0; + +out_unlock: + mutex_unlock(&sensor->cache_lock); + return ret; +} + +static int radxa_svc_sensor_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct radxa_svc_sensor *sensor =3D dev_get_drvdata(dev); + s64 power; + int ret; + + if (channel) + return -EOPNOTSUPP; + + ret =3D radxa_svc_sensor_update(sensor); + if (ret) + return ret; + + switch (type) { + case hwmon_temp: + if (attr !=3D hwmon_temp_input || + sensor->sensor_type !=3D RADXA_SVC_SENSOR_TEMP) + return -EOPNOTSUPP; + *val =3D sensor->temp_millic; + return 0; + case hwmon_in: + if (attr !=3D hwmon_in_input || + !(radxa_svc_sensor_expected_mask(sensor->sensor_type) & + RADXA_SVC_SENSOR_VALID_VOLTAGE)) + return -EOPNOTSUPP; + *val =3D sensor->voltage_mv; + return 0; + case hwmon_curr: + if (attr !=3D hwmon_curr_input || + !(radxa_svc_sensor_expected_mask(sensor->sensor_type) & + RADXA_SVC_SENSOR_VALID_CURRENT)) + return -EOPNOTSUPP; + if (sensor->current_ma > LONG_MAX || + sensor->current_ma < LONG_MIN) + return -ERANGE; + *val =3D sensor->current_ma; + return 0; + case hwmon_power: + if (attr !=3D hwmon_power_input || + sensor->sensor_type !=3D RADXA_SVC_SENSOR_VOLTAGE_CURRENT) + return -EOPNOTSUPP; + + power =3D (s64)sensor->voltage_mv * sensor->current_ma; + if (power > LONG_MAX || power < LONG_MIN) + return -ERANGE; + *val =3D power; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int radxa_svc_sensor_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + if (channel) + return -EOPNOTSUPP; + + switch (type) { + case hwmon_temp: + if (attr =3D=3D hwmon_temp_label) + *str =3D "Temperature"; + else + return -EOPNOTSUPP; + break; + case hwmon_in: + if (attr =3D=3D hwmon_in_label) + *str =3D "Voltage"; + else + return -EOPNOTSUPP; + break; + case hwmon_curr: + if (attr =3D=3D hwmon_curr_label) + *str =3D "Current"; + else + return -EOPNOTSUPP; + break; + case hwmon_power: + if (attr =3D=3D hwmon_power_label) + *str =3D "Power"; + else + return -EOPNOTSUPP; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static umode_t radxa_svc_sensor_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct radxa_svc_sensor *sensor =3D data; + + if (channel) + return 0; + + switch (type) { + case hwmon_temp: + if (sensor->sensor_type =3D=3D RADXA_SVC_SENSOR_TEMP && + (attr =3D=3D hwmon_temp_input || attr =3D=3D hwmon_temp_label)) + return 0444; + break; + case hwmon_in: + if ((radxa_svc_sensor_expected_mask(sensor->sensor_type) & + RADXA_SVC_SENSOR_VALID_VOLTAGE) && + (attr =3D=3D hwmon_in_input || attr =3D=3D hwmon_in_label)) + return 0444; + break; + case hwmon_curr: + if ((radxa_svc_sensor_expected_mask(sensor->sensor_type) & + RADXA_SVC_SENSOR_VALID_CURRENT) && + (attr =3D=3D hwmon_curr_input || attr =3D=3D hwmon_curr_label)) + return 0444; + break; + case hwmon_power: + if (sensor->sensor_type =3D=3D RADXA_SVC_SENSOR_VOLTAGE_CURRENT && + (attr =3D=3D hwmon_power_input || attr =3D=3D hwmon_power_label)) + return 0444; + break; + default: + break; + } + + return 0; +} + +static const struct hwmon_ops radxa_svc_sensor_hwmon_ops =3D { + .is_visible =3D radxa_svc_sensor_hwmon_is_visible, + .read =3D radxa_svc_sensor_hwmon_read, + .read_string =3D radxa_svc_sensor_hwmon_read_string, +}; + +static const struct hwmon_channel_info * const radxa_svc_temp_info[] =3D { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_channel_info * const radxa_svc_voltage_info[] = =3D { + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), + NULL +}; + +static const struct hwmon_channel_info * const radxa_svc_current_info[] = =3D { + HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL), + NULL +}; + +static const struct hwmon_channel_info * const radxa_svc_voltage_current_i= nfo[] =3D { + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL), + HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL), + NULL +}; + +static const struct hwmon_chip_info radxa_svc_temp_chip_info =3D { + .ops =3D &radxa_svc_sensor_hwmon_ops, + .info =3D radxa_svc_temp_info, +}; + +static const struct hwmon_chip_info radxa_svc_voltage_chip_info =3D { + .ops =3D &radxa_svc_sensor_hwmon_ops, + .info =3D radxa_svc_voltage_info, +}; + +static const struct hwmon_chip_info radxa_svc_current_chip_info =3D { + .ops =3D &radxa_svc_sensor_hwmon_ops, + .info =3D radxa_svc_current_info, +}; + +static const struct hwmon_chip_info radxa_svc_voltage_current_chip_info = =3D { + .ops =3D &radxa_svc_sensor_hwmon_ops, + .info =3D radxa_svc_voltage_current_info, +}; + +static const struct hwmon_chip_info *radxa_svc_sensor_chip_info(u32 sensor= _type) +{ + switch (sensor_type) { + case RADXA_SVC_SENSOR_TEMP: + return &radxa_svc_temp_chip_info; + case RADXA_SVC_SENSOR_VOLTAGE: + return &radxa_svc_voltage_chip_info; + case RADXA_SVC_SENSOR_CURRENT: + return &radxa_svc_current_chip_info; + case RADXA_SVC_SENSOR_VOLTAGE_CURRENT: + return &radxa_svc_voltage_current_chip_info; + default: + return NULL; + } +} + +static int radxa_svc_sensor_validate_desc(struct radxa_svc_glink *svc, + const struct radxa_svc_sensor_desc *desc, + u32 index) +{ + const char *nul; + u32 sensor_id =3D le32_to_cpu(desc->sensor_id); + u32 sensor_type =3D le32_to_cpu(desc->sensor_type); + u32 i; + + nul =3D memchr(desc->name, '\0', sizeof(desc->name)); + if (!nul || nul =3D=3D desc->name || !radxa_svc_sensor_chip_info(sensor_t= ype)) + return -EPROTO; + + for (i =3D 0; i < index; i++) { + if (svc->sensors[i].sensor_id =3D=3D sensor_id || + !strcmp(svc->sensors[i].name, desc->name)) + return -EPROTO; + } + + svc->sensors[index].svc =3D svc; + svc->sensors[index].sensor_id =3D sensor_id; + svc->sensors[index].sensor_type =3D sensor_type; + strscpy(svc->sensors[index].name, desc->name, + sizeof(svc->sensors[index].name)); + mutex_init(&svc->sensors[index].cache_lock); + + return 0; +} + +static int radxa_svc_sensor_hwmon_init(struct radxa_svc_glink *svc) +{ + struct radxa_svc_sensor_list_resp *resp; + struct radxa_svc_sensor_list_req req; + const struct hwmon_chip_info *chip_info; + char *hwmon_name; + size_t max_len; + size_t len; + u32 returned; + u32 total =3D 0; + u32 start =3D 0; + u32 i; + int ret; + + if (!(svc->caps & RADXA_SVC_CAP_SENSORS)) + return 0; + + max_len =3D struct_size(resp, entries, RADXA_SVC_SENSOR_PAGE_MAX); + resp =3D kzalloc(max_len, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + do { + req.start_index =3D cpu_to_le32(start); + req.max_entries =3D cpu_to_le32(RADXA_SVC_SENSOR_PAGE_MAX); + len =3D max_len; + ret =3D radxa_svc_request(svc, RADXA_SVC_OP_SENSOR_LIST, + &req, sizeof(req), resp, &len); + if (ret) + goto out_free; + + if (len < sizeof(*resp)) { + ret =3D -EPROTO; + goto out_free; + } + + returned =3D le32_to_cpu(resp->returned_count); + if (!start) { + total =3D le32_to_cpu(resp->total_count); + if (total > RADXA_SVC_SENSOR_MAX_SENSORS) { + ret =3D -EOVERFLOW; + goto out_free; + } + + if (total) { + svc->sensors =3D + devm_kcalloc(svc->dev, total, + sizeof(*svc->sensors), GFP_KERNEL); + if (!svc->sensors) { + ret =3D -ENOMEM; + goto out_free; + } + } + } else if (le32_to_cpu(resp->total_count) !=3D total) { + ret =3D -EPROTO; + goto out_free; + } + + if (returned > RADXA_SVC_SENSOR_PAGE_MAX || returned > total - start || + len !=3D struct_size(resp, entries, returned) || + (start < total && !returned)) { + ret =3D -EPROTO; + goto out_free; + } + + for (i =3D 0; i < returned; i++) { + ret =3D radxa_svc_sensor_validate_desc(svc, &resp->entries[i], + start + i); + if (ret) + goto out_free; + } + + start +=3D returned; + } while (start < total); + + svc->num_sensors =3D total; + for (i =3D 0; i < svc->num_sensors; i++) { + struct radxa_svc_sensor *sensor =3D &svc->sensors[i]; + + chip_info =3D radxa_svc_sensor_chip_info(sensor->sensor_type); + hwmon_name =3D devm_hwmon_sanitize_name(svc->dev, sensor->name); + if (IS_ERR(hwmon_name)) { + ret =3D PTR_ERR(hwmon_name); + goto out_free; + } + + sensor->hwmon_dev =3D + devm_hwmon_device_register_with_info(svc->dev, hwmon_name, + sensor, chip_info, NULL); + if (IS_ERR(sensor->hwmon_dev)) { + ret =3D PTR_ERR(sensor->hwmon_dev); + dev_err_probe(svc->dev, ret, + "failed to register sensor %s hwmon\n", + sensor->name); + goto out_free; + } + } + + ret =3D 0; + +out_free: + kfree(resp); + return ret; +} + static int radxa_svc_fan_get_pwm_mode(struct radxa_svc_glink *svc, long *mode) { @@ -694,6 +1159,11 @@ static int radxa_svc_rpmsg_probe(struct rpmsg_device = *rpdev) return dev_err_probe(&rpdev->dev, ret, "failed to register hwmon\n"); =20 + ret =3D radxa_svc_sensor_hwmon_init(svc); + if (ret) + return dev_err_probe(&rpdev->dev, ret, + "failed to register sensor hwmon devices\n"); + return 0; } =20 --=20 2.55.0