From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtpbg150.qq.com (smtpbg150.qq.com [18.132.163.193]) (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 176E34252A6; Mon, 31 Aug 2026 15:11:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=18.132.163.193 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788189085; cv=none; b=Pd+nacofL/LcpnXrq+zNyid4OgtM6a33lSxEJqSo7p0ZGKuKGQ2vrrSnTOrAMmMGJCqbZDqDiD9fR+yMf77fZDpVCsbqf6w+QbRfWd/K6T4hIo7MP+y09+etG+L/04nb7+/aFNwHVGqmfNLHPhCBR2kF0Yv+niAyg/k62SSaFyM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788189085; c=relaxed/simple; bh=+HY9roeovzLPLp9PfnmL3ix2ruSeghbpxMKPT8WC9Vs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=V0PfVZTUa0SGQcU4/xVucM2b8/ioKqiMcGNjoTNTkwdjMTXW5KE1XRGeijJmO63AFTuype3EdEYcaNUE7O85AoFdWBz7tIRbiPyrEm0fgBRfhi7oefu+q/Md5vf93u0+ECcVYJ75SyVf2Thv9NmNfk3brgZ3gc38fKOmfG77ltU= 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=18.132.163.193 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-doc@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit 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: MrfJYRspGRl/9fqNU+1JeKz0fEfYla6UwY6HXFkz/0tCjSWKELyf+0gl 8v5paW2/SGy2x9oxlAVLnBC/EXBUx0ljN0WdjjqKOhSxWWiuflo3QZ4a4LNE7Sh1mQ4IRyw C0tM8hfpQkulcQ07vNX04jF22AqtWu4JOeFi5fMp6TqCqMMB9I8Nmwkt/GXy0q60YKLnBwx xC/r72JQOBuUfucbGkrF6MtltuOfyxu/p5Rvc2OajrVZMyWJWa82evPc4OZ3aIzm1qnWRtt 7yTKufbct3HLXcr9z23XNRknJLUjqQXeGsnKScw4cKGyoUrJdUygy7QDC+lVGXxpkOqmzeP S/Zq3uFawzyhQi2ZeBTWktUN/znYsJscARjHPOUowqdWLZ9eUMbIVpEdzEjhIJLKzLvPmBt xhyx9/gVeDbWBUuAhEliKKbcU7d2AC0EMAUI3QI0NtGlqf7uZ04ZRd4Y8NVv03CEAemCtqx whTKo5xHU3JB47DPzrg/c0iwwN7ree6KGnYk7lOePPcCVYOa/SyFy6DBv+V5I9QB2VMFfax vVSAq3h6i+WtDWDXbgKgSnT6ly+PyhRmmLrXMir3cN9scEkRr6pkPEfew4Q3G+/kKa3MadB WP2O6EFYEH8ODp6BDxozMJyMYAHpDk4Wfheht+2GdOB9r1nxxr2iqIuCTFue5QkaVLEAo2f FcW1D7Pm4lOd86UiojlKtOs3yRnEGpxytpG53eqHzeHK0kIeQ+rNYDyqkGkyc4hNIfzSMOZ cEoaQD9BrxsqwGrmsynHyyfb7OcDMUmmXc5auGP3KvMgoi3lBKiD/g9i8b2KAxxvJFiMpQl YET3RN8TlQ26LVLk9UWM8LbZsXNbIi4+ELT+AGIOXCr/xq7PK0C2dS8Fu+l2rPvmYM9v0a6 xmholP4hT4TXmSpgHtnrZAN3+rJO41yEnJ3dURlqY+3Wk19cEjk6Mn4ZWdU05lS6EgISUfG 5Q/Z4IfpsfpRD5QBbP5qcoF0IlRonInCA01uLAH+6OFUxevyLxgYsGTZFVjyU2Lqw61jXZg LMejEetYOMAAjGUOfd8UZd8zd1k64= X-QQ-XMRINFO: NyFYKkN4Ny6FuXrnB5Ye7Aabb3ujjtK+gg== 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 +============================= + +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 +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 +#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 = 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)) + 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); + + if (major != RADXA_SVC_VERSION) + return dev_err_probe(dev, -EPROTONOSUPPORT, + "unsupported service major version %u\n", + major); + + 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), + 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) + ret = -EMSGSIZE; + *rsp_len = 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 = 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); + + 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); + 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 "); +MODULE_DESCRIPTION("Radxa SVC GLINK driver"); +MODULE_LICENSE("GPL"); -- 2.55.0