From: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
To: Xilin Wu <sophon@radxa.com>
Cc: Guenter Roeck <linux@roeck-us.net>,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Hans de Goede <hansg@kernel.org>,
Bryan O'Donoghue <bryan.odonoghue@linaro.org>,
linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org,
LKML <linux-kernel@vger.kernel.org>,
linux-arm-msm@vger.kernel.org,
platform-driver-x86@vger.kernel.org
Subject: Re: [PATCH 1/2] platform: arm64: Add Radxa SVC GLINK driver
Date: Thu, 3 Sep 2026 15:55:59 +0300 (EEST) [thread overview]
Message-ID: <135c74d2-5f66-83ac-4ffe-a214c735ccec@linux.intel.com> (raw)
In-Reply-To: <20260831-radxa-svc-v1-1-7c028de6a387@radxa.com>
On Mon, 31 Aug 2026, Xilin Wu wrote:
> 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 <sophon@radxa.com>
> ---
> 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
> +=============================
> +
> +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:
> +
> +=============== ======= ======================================================
> +``pwm1`` RW Current fan PWM value. Writes are accepted in manual
> + mode and use values from 0 to 255.
> +``pwm1_enable`` RW Fan control mode, as described below.
> +=============== ======= ======================================================
> +
> +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
>
> +RADXA SVC GLINK DRIVER
> +M: Xilin Wu <sophon@radxa.com>
> +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
>
> This driver currently supports Hamoa/Purwa/Glymur reference devices.
>
> +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/Makefile
> index 7681be4a46e9..327acb7b983c 100644
> --- a/drivers/platform/arm64/Makefile
> +++ b/drivers/platform/arm64/Makefile
> @@ -10,3 +10,4 @@ obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
> obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
> obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o
> obj-$(CONFIG_EC_QCOM_HAMOA) += qcom-hamoa-ec.o
> +obj-$(CONFIG_RADXA_SVC_GLINK) += radxa_svc_glink.o
> diff --git a/drivers/platform/arm64/radxa_svc_glink.c b/drivers/platform/arm64/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 <linux/completion.h>
> +#include <linux/errno.h>
> +#include <linux/hwmon.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/rpmsg.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/string.h>
> +
> +#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)
Add include for linux/bits.h.
> +
> +#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;
Please add linux/types.h and include for __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 = sizeof(*resp);
> + int ret;
> +
> + ret = radxa_svc_request(svc, RADXA_SVC_OP_GET_VERSION, NULL, 0,
> + resp, &len);
> + if (ret)
> + return ret;
> +
> + if (len < sizeof(*resp))
Remove the empty line as this too is error handling for the call.
> + 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 = le16_to_cpu(resp->major);
> + *caps = le32_to_cpu(resp->caps);
Add include.
> +
> + if (major != RADXA_SVC_VERSION)
> + return dev_err_probe(dev, -EPROTONOSUPPORT,
> + "unsupported service major version %u\n",
> + major);
Add braces to blocks with multi-line constructs.
Add include for dev_err_probe().
> +
> + missing_caps = 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 = sizeof(resp);
> + int ret;
> +
> + ret = radxa_svc_request(svc, RADXA_SVC_OP_GET_PROFILE, NULL, 0,
> + &resp, &len);
> + if (ret)
> + return ret;
> +
> + if (len < sizeof(resp))
> + return -EIO;
> +
> + *profile = 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 = 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 = sizeof(*resp);
> + size_t base_len = offsetof(struct radxa_svc_fan_state_resp,
> + control_mode);
> + int ret;
> +
> + ret = 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 = sizeof(*resp);
> + u32 control_mode;
> + u32 manual_pwm;
> + int ret;
> +
> + ret = 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 = le32_to_cpu(resp->control_mode);
> + manual_pwm = 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 = cpu_to_le32(control_mode);
> + req.manual_pwm = 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 *value)
> +{
> + u64 pwm;
> +
> + if (!period_ns)
> + return -ENODATA;
> +
> + if (duty_ns > period_ns)
> + return -EPROTO;
> +
> + pwm = (u64)(period_ns - duty_ns) * RADXA_SVC_FAN_PWM_MAX;
> + *value = min_t(u64, DIV_ROUND_CLOSEST_ULL(pwm, period_ns),
Add include for DIV_ROUND_CLOSEST_ULL()
> + 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 = sizeof(*hdr) + req_len;
> + tx_buf = kzalloc(tx_len, GFP_KERNEL);
> + if (!tx_buf)
> + return -ENOMEM;
> +
> + mutex_lock(&svc->xfer_lock);
> +
> + if (!svc->rpdev || !svc->rpdev->ept) {
> + ret = -ENODEV;
> + goto out_unlock;
> + }
> +
> + seq = ++svc->seq;
> + if (!seq)
> + seq = ++svc->seq;
> +
> + hdr = (struct radxa_svc_hdr *)tx_buf;
> + hdr->magic = cpu_to_le32(RADXA_SVC_MAGIC);
> + hdr->version = cpu_to_le16(RADXA_SVC_VERSION);
> + hdr->header_size = cpu_to_le16(sizeof(*hdr));
> + hdr->opcode = cpu_to_le32(opcode);
> + hdr->seq = cpu_to_le32(seq);
> + hdr->status = cpu_to_le32(0);
> + hdr->payload_len = 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 = -ENODEV;
> + goto out_unlock;
> + }
> +
> + svc->pending = true;
> + svc->pending_seq = seq;
> + svc->rsp_opcode = 0;
> + svc->rsp_status = 0;
> + svc->rsp_len = 0;
> + spin_unlock_irqrestore(&svc->rsp_lock, flags);
> +
> + ret = rpmsg_send(svc->rpdev->ept, tx_buf, tx_len);
> + if (ret) {
> + spin_lock_irqsave(&svc->rsp_lock, flags);
> + svc->pending = 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 = false;
> + spin_unlock_irqrestore(&svc->rsp_lock, flags);
> + ret = -ETIMEDOUT;
> + goto out_unlock;
> + }
> +
> + spin_lock_irqsave(&svc->rsp_lock, flags);
> + if (svc->shutting_down) {
> + ret = -ENODEV;
> + } else if (svc->rsp_opcode != opcode) {
> + ret = -EIO;
> + } else {
> + ret = svc->rsp_status;
> + if (rsp_payload && rsp_len) {
> + size_t copy_len = min(*rsp_len, svc->rsp_len);
> +
> + memcpy(rsp_payload, svc->rsp_payload, copy_len);
> + if (*rsp_len < svc->rsp_len && !ret)
Doing !ret, without preceeding call nowhere in sight, is bit of a curve
ball. It would be better to name the variable differently.
> + ret = -EMSGSIZE;
Is it correct that this just proceeds?
Does this even want to do the memcpy() when it returns error? Why?
> + *rsp_len = svc->rsp_len;
> + }
> + }
> + spin_unlock_irqrestore(&svc->rsp_lock, flags);
> +
> +out_unlock:
> + mutex_unlock(&svc->xfer_lock);
> + kfree(tx_buf);
Please convert this function to use __free(), guard() and scoped_guard().
Hopefully that makes the function a bit more readable (as is, I didn't
even bother trying to follow the code flow).
> +
> + 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 = dev_get_drvdata(&rpdev->dev);
> + const struct radxa_svc_hdr *hdr = data;
> + unsigned long flags;
> + size_t header_size;
> + size_t payload_len;
> + bool do_complete = false;
> + u32 seq;
> +
> + if (len < sizeof(*hdr))
> + goto bad_msg;
> +
> + if (le32_to_cpu(hdr->magic) != RADXA_SVC_MAGIC ||
> + le16_to_cpu(hdr->version) != RADXA_SVC_VERSION)
> + goto bad_msg;
> +
> + header_size = le16_to_cpu(hdr->header_size);
> + if (header_size < sizeof(*hdr) || header_size > len)
> + goto bad_msg;
> +
> + payload_len = le32_to_cpu(hdr->payload_len);
> + if (payload_len > len - header_size ||
> + payload_len > RADXA_SVC_MAX_RX_PAYLOAD)
> + goto bad_msg;
> +
> + seq = le32_to_cpu(hdr->seq);
> +
> + spin_lock_irqsave(&svc->rsp_lock, flags);
> + if (svc->pending && seq == svc->pending_seq) {
> + svc->rsp_opcode = le32_to_cpu(hdr->opcode);
> + svc->rsp_status = (s32)le32_to_cpu(hdr->status);
> + svc->rsp_len = payload_len;
> + memcpy(svc->rsp_payload, data + header_size, payload_len);
> + svc->pending = false;
> + do_complete = 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 = {};
> + u32 control_mode;
> + u32 profile;
> + int ret;
> +
> + ret = radxa_svc_fan_get_control(svc, &control);
> + if (ret)
> + return ret;
> +
> + control_mode = le32_to_cpu(control.control_mode);
> +
> + switch (control_mode) {
> + case RADXA_SVC_FAN_CONTROL_FULL_SPEED:
> + *mode = RADXA_SVC_PWM_MODE_FULL_SPEED;
> + return 0;
> + case RADXA_SVC_FAN_CONTROL_MANUAL:
> + *mode = RADXA_SVC_PWM_MODE_MANUAL;
> + return 0;
> + case RADXA_SVC_FAN_CONTROL_AUTO:
> + break;
> + default:
> + return -EIO;
> + }
> +
> + ret = radxa_svc_get_profile(svc, &profile);
> + if (ret)
> + return ret;
> +
> + switch (profile) {
> + case RADXA_SVC_PROFILE_QUIET:
> + *mode = RADXA_SVC_PWM_MODE_AUTO_QUIET;
> + return 0;
> + case RADXA_SVC_PROFILE_PERFORMANCE:
> + *mode = 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 = {};
> + int ret;
> +
> + ret = radxa_svc_fan_get_control(svc, &control);
> + if (ret)
> + return ret;
> +
> + ret = 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 mode)
> +{
> + struct radxa_svc_fan_state_resp state = {};
> + 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 = radxa_svc_fan_get_state(svc, &state);
> + if (ret)
> + return ret;
> +
> + period_ns = le32_to_cpu(state.pwm_period_ns);
> + duty_ns = le32_to_cpu(state.current_duty_ns);
> + if (!period_ns) {
> + manual_pwm = RADXA_SVC_FAN_PWM_MAX;
> + } else {
> + ret = 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 != 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 = dev_get_drvdata(dev);
> + struct radxa_svc_fan_state_resp state = {};
> + u32 duty_ns;
> + u32 period_ns;
> + u32 pwm;
> + int ret;
> +
> + if (type != hwmon_pwm || channel)
> + return -EOPNOTSUPP;
> +
> + switch (attr) {
> + case hwmon_pwm_input:
> + ret = radxa_svc_fan_get_state(svc, &state);
> + if (ret)
> + return ret;
> +
> + period_ns = le32_to_cpu(state.pwm_period_ns);
> + duty_ns = le32_to_cpu(state.current_duty_ns);
> + ret = radxa_svc_fan_duty_to_pwm(period_ns, duty_ns, &pwm);
> + if (ret)
> + return ret;
> +
> + *val = pwm;
> + return 0;
> + case hwmon_pwm_enable:
> + mutex_lock(&svc->fan_lock);
> + ret = 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 = dev_get_drvdata(dev);
> + struct radxa_svc_fan_control_resp control = {};
> + int ret;
> +
> + if (type != hwmon_pwm || channel)
> + return -EOPNOTSUPP;
> +
> + mutex_lock(&svc->fan_lock);
Use guard() so you can return early.
> +
> + switch (attr) {
> + case hwmon_pwm_input:
> + if (val < 0 || val > RADXA_SVC_FAN_PWM_MAX) {
> + ret = -EINVAL;
> + break;
> + }
> +
> + ret = radxa_svc_fan_get_control(svc, &control);
> + if (ret)
> + break;
> +
> + if (le32_to_cpu(control.control_mode) !=
> + RADXA_SVC_FAN_CONTROL_MANUAL) {
> + ret = -EINVAL;
> + break;
> + }
> +
> + ret = radxa_svc_fan_set_control(svc,
> + RADXA_SVC_FAN_CONTROL_MANUAL,
> + val);
> + break;
> + case hwmon_pwm_enable:
> + ret = radxa_svc_fan_set_pwm_mode(svc, val);
> + break;
> + default:
> + ret = -EOPNOTSUPP;
> + break;
> + }
> +
> + mutex_unlock(&svc->fan_lock);
> +
> + return ret;
> +}
> +
> +static const struct hwmon_ops radxa_svc_hwmon_ops = {
> + .is_visible = radxa_svc_hwmon_is_visible,
> + .read = radxa_svc_hwmon_read,
> + .write = radxa_svc_hwmon_write,
> +};
> +
> +static const struct hwmon_channel_info * const radxa_svc_hwmon_info[] = {
> + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
> + NULL
> +};
> +
> +static const struct hwmon_chip_info radxa_svc_hwmon_chip_info = {
> + .ops = &radxa_svc_hwmon_ops,
> + .info = radxa_svc_hwmon_info,
> +};
> +
> +static int radxa_svc_hwmon_init(struct radxa_svc_glink *svc)
> +{
> + svc->hwmon_dev = 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 = {};
> + struct radxa_svc_glink *svc;
> + int ret;
> +
> + svc = devm_kzalloc(&rpdev->dev, sizeof(*svc), GFP_KERNEL);
> + if (!svc)
> + return -ENOMEM;
> +
> + svc->dev = &rpdev->dev;
> + svc->rpdev = rpdev;
> + mutex_init(&svc->xfer_lock);
> + mutex_init(&svc->fan_lock);
Please use devm_mutex_init() + don't forget to add error handling as it
can fail.
> + spin_lock_init(&svc->rsp_lock);
> + init_completion(&svc->rsp);
> +
> + dev_set_drvdata(&rpdev->dev, svc);
> +
> + ret = radxa_svc_get_version(svc, &version);
> + if (ret)
> + return dev_err_probe(&rpdev->dev, ret,
> + "failed to read service version\n");
> +
> + ret = radxa_svc_check_version(&rpdev->dev, &version, &svc->caps);
> + if (ret)
> + return ret;
> +
> + ret = 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 = dev_get_drvdata(&rpdev->dev);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&svc->rsp_lock, flags);
> + svc->shutting_down = true;
> + svc->pending = false;
> + spin_unlock_irqrestore(&svc->rsp_lock, flags);
> + complete_all(&svc->rsp);
> +
> + mutex_lock(&svc->xfer_lock);
> + svc->rpdev = NULL;
> + mutex_unlock(&svc->xfer_lock);
> +}
> +
> +static const struct rpmsg_device_id radxa_svc_rpmsg_id_match[] = {
> + { "RADXA_SVC_ADSP_APPS" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(rpmsg, radxa_svc_rpmsg_id_match);
> +
> +static struct rpmsg_driver radxa_svc_rpmsg_driver = {
> + .probe = radxa_svc_rpmsg_probe,
> + .remove = radxa_svc_rpmsg_remove,
> + .callback = radxa_svc_rpmsg_callback,
> + .id_table = radxa_svc_rpmsg_id_match,
> + .drv = {
> + .name = "radxa_svc_glink",
> + },
> +};
> +module_rpmsg_driver(radxa_svc_rpmsg_driver);
> +
> +MODULE_AUTHOR("Xilin Wu <sophon@radxa.com>");
> +MODULE_DESCRIPTION("Radxa SVC GLINK driver");
> +MODULE_LICENSE("GPL");
>
>
--
i.
next prev parent reply other threads:[~2026-09-03 12:56 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-31 15:09 [PATCH 0/2] platform: arm64: Add Radxa SVC GLINK support Xilin Wu
2026-08-31 15:09 ` [PATCH 1/2] platform: arm64: Add Radxa SVC GLINK driver Xilin Wu
2026-09-03 12:55 ` Ilpo Järvinen [this message]
2026-09-04 9:44 ` Konrad Dybcio
2026-08-31 15:09 ` [PATCH 2/2] platform: arm64: radxa-svc: Add hwmon sensors Xilin Wu
2026-09-03 13:06 ` Ilpo Järvinen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=135c74d2-5f66-83ac-4ffe-a214c735ccec@linux.intel.com \
--to=ilpo.jarvinen@linux.intel.com \
--cc=bryan.odonoghue@linaro.org \
--cc=corbet@lwn.net \
--cc=hansg@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=platform-driver-x86@vger.kernel.org \
--cc=rdunlap@infradead.org \
--cc=skhan@linuxfoundation.org \
--cc=sophon@radxa.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox