18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Windfarm PowerMac thermal control. SMU based controls
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
68c2ecf20Sopenharmony_ci *                    <benh@kernel.crashing.org>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/types.h>
108c2ecf20Sopenharmony_ci#include <linux/errno.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/delay.h>
138c2ecf20Sopenharmony_ci#include <linux/slab.h>
148c2ecf20Sopenharmony_ci#include <linux/init.h>
158c2ecf20Sopenharmony_ci#include <linux/wait.h>
168c2ecf20Sopenharmony_ci#include <linux/completion.h>
178c2ecf20Sopenharmony_ci#include <asm/prom.h>
188c2ecf20Sopenharmony_ci#include <asm/machdep.h>
198c2ecf20Sopenharmony_ci#include <asm/io.h>
208c2ecf20Sopenharmony_ci#include <asm/sections.h>
218c2ecf20Sopenharmony_ci#include <asm/smu.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include "windfarm.h"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define VERSION "0.4"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#undef DEBUG
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#ifdef DEBUG
308c2ecf20Sopenharmony_ci#define DBG(args...)	printk(args)
318c2ecf20Sopenharmony_ci#else
328c2ecf20Sopenharmony_ci#define DBG(args...)	do { } while(0)
338c2ecf20Sopenharmony_ci#endif
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic int smu_supports_new_fans_ops = 1;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/*
388c2ecf20Sopenharmony_ci * SMU fans control object
398c2ecf20Sopenharmony_ci */
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic LIST_HEAD(smu_fans);
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistruct smu_fan_control {
448c2ecf20Sopenharmony_ci	struct list_head	link;
458c2ecf20Sopenharmony_ci	int    			fan_type;	/* 0 = rpm, 1 = pwm */
468c2ecf20Sopenharmony_ci	u32			reg;		/* index in SMU */
478c2ecf20Sopenharmony_ci	s32			value;		/* current value */
488c2ecf20Sopenharmony_ci	s32			min, max;	/* min/max values */
498c2ecf20Sopenharmony_ci	struct wf_control	ctrl;
508c2ecf20Sopenharmony_ci};
518c2ecf20Sopenharmony_ci#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl)
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic int smu_set_fan(int pwm, u8 id, u16 value)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	struct smu_cmd cmd;
568c2ecf20Sopenharmony_ci	u8 buffer[16];
578c2ecf20Sopenharmony_ci	DECLARE_COMPLETION_ONSTACK(comp);
588c2ecf20Sopenharmony_ci	int rc;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	/* Fill SMU command structure */
618c2ecf20Sopenharmony_ci	cmd.cmd = SMU_CMD_FAN_COMMAND;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	/* The SMU has an "old" and a "new" way of setting the fan speed
648c2ecf20Sopenharmony_ci	 * Unfortunately, I found no reliable way to know which one works
658c2ecf20Sopenharmony_ci	 * on a given machine model. After some investigations it appears
668c2ecf20Sopenharmony_ci	 * that MacOS X just tries the new one, and if it fails fallbacks
678c2ecf20Sopenharmony_ci	 * to the old ones ... Ugh.
688c2ecf20Sopenharmony_ci	 */
698c2ecf20Sopenharmony_ci retry:
708c2ecf20Sopenharmony_ci	if (smu_supports_new_fans_ops) {
718c2ecf20Sopenharmony_ci		buffer[0] = 0x30;
728c2ecf20Sopenharmony_ci		buffer[1] = id;
738c2ecf20Sopenharmony_ci		*((u16 *)(&buffer[2])) = value;
748c2ecf20Sopenharmony_ci		cmd.data_len = 4;
758c2ecf20Sopenharmony_ci	} else {
768c2ecf20Sopenharmony_ci		if (id > 7)
778c2ecf20Sopenharmony_ci			return -EINVAL;
788c2ecf20Sopenharmony_ci		/* Fill argument buffer */
798c2ecf20Sopenharmony_ci		memset(buffer, 0, 16);
808c2ecf20Sopenharmony_ci		buffer[0] = pwm ? 0x10 : 0x00;
818c2ecf20Sopenharmony_ci		buffer[1] = 0x01 << id;
828c2ecf20Sopenharmony_ci		*((u16 *)&buffer[2 + id * 2]) = value;
838c2ecf20Sopenharmony_ci		cmd.data_len = 14;
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	cmd.reply_len = 16;
878c2ecf20Sopenharmony_ci	cmd.data_buf = cmd.reply_buf = buffer;
888c2ecf20Sopenharmony_ci	cmd.status = 0;
898c2ecf20Sopenharmony_ci	cmd.done = smu_done_complete;
908c2ecf20Sopenharmony_ci	cmd.misc = &comp;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	rc = smu_queue_cmd(&cmd);
938c2ecf20Sopenharmony_ci	if (rc)
948c2ecf20Sopenharmony_ci		return rc;
958c2ecf20Sopenharmony_ci	wait_for_completion(&comp);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	/* Handle fallback (see coment above) */
988c2ecf20Sopenharmony_ci	if (cmd.status != 0 && smu_supports_new_fans_ops) {
998c2ecf20Sopenharmony_ci		printk(KERN_WARNING "windfarm: SMU failed new fan command "
1008c2ecf20Sopenharmony_ci		       "falling back to old method\n");
1018c2ecf20Sopenharmony_ci		smu_supports_new_fans_ops = 0;
1028c2ecf20Sopenharmony_ci		goto retry;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return cmd.status;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic void smu_fan_release(struct wf_control *ct)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	kfree(fct);
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic int smu_fan_set(struct wf_control *ct, s32 value)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	if (value < fct->min)
1208c2ecf20Sopenharmony_ci		value = fct->min;
1218c2ecf20Sopenharmony_ci	if (value > fct->max)
1228c2ecf20Sopenharmony_ci		value = fct->max;
1238c2ecf20Sopenharmony_ci	fct->value = value;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	return smu_set_fan(fct->fan_type, fct->reg, value);
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic int smu_fan_get(struct wf_control *ct, s32 *value)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
1318c2ecf20Sopenharmony_ci	*value = fct->value; /* todo: read from SMU */
1328c2ecf20Sopenharmony_ci	return 0;
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic s32 smu_fan_min(struct wf_control *ct)
1368c2ecf20Sopenharmony_ci{
1378c2ecf20Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
1388c2ecf20Sopenharmony_ci	return fct->min;
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic s32 smu_fan_max(struct wf_control *ct)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	struct smu_fan_control *fct = to_smu_fan(ct);
1448c2ecf20Sopenharmony_ci	return fct->max;
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_cistatic const struct wf_control_ops smu_fan_ops = {
1488c2ecf20Sopenharmony_ci	.set_value	= smu_fan_set,
1498c2ecf20Sopenharmony_ci	.get_value	= smu_fan_get,
1508c2ecf20Sopenharmony_ci	.get_min	= smu_fan_min,
1518c2ecf20Sopenharmony_ci	.get_max	= smu_fan_max,
1528c2ecf20Sopenharmony_ci	.release	= smu_fan_release,
1538c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
1548c2ecf20Sopenharmony_ci};
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic struct smu_fan_control *smu_fan_create(struct device_node *node,
1578c2ecf20Sopenharmony_ci					      int pwm_fan)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	struct smu_fan_control *fct;
1608c2ecf20Sopenharmony_ci	const s32 *v;
1618c2ecf20Sopenharmony_ci	const u32 *reg;
1628c2ecf20Sopenharmony_ci	const char *l;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL);
1658c2ecf20Sopenharmony_ci	if (fct == NULL)
1668c2ecf20Sopenharmony_ci		return NULL;
1678c2ecf20Sopenharmony_ci	fct->ctrl.ops = &smu_fan_ops;
1688c2ecf20Sopenharmony_ci	l = of_get_property(node, "location", NULL);
1698c2ecf20Sopenharmony_ci	if (l == NULL)
1708c2ecf20Sopenharmony_ci		goto fail;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	fct->fan_type = pwm_fan;
1738c2ecf20Sopenharmony_ci	fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	/* We use the name & location here the same way we do for SMU sensors,
1768c2ecf20Sopenharmony_ci	 * see the comment in windfarm_smu_sensors.c. The locations are a bit
1778c2ecf20Sopenharmony_ci	 * less consistent here between the iMac and the desktop models, but
1788c2ecf20Sopenharmony_ci	 * that is good enough for our needs for now at least.
1798c2ecf20Sopenharmony_ci	 *
1808c2ecf20Sopenharmony_ci	 * One problem though is that Apple seem to be inconsistent with case
1818c2ecf20Sopenharmony_ci	 * and the kernel doesn't have strcasecmp =P
1828c2ecf20Sopenharmony_ci	 */
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	fct->ctrl.name = NULL;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	/* Names used on desktop models */
1878c2ecf20Sopenharmony_ci	if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") ||
1888c2ecf20Sopenharmony_ci	    !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan") ||
1898c2ecf20Sopenharmony_ci	    !strcmp(l, "CPU A EXHAUST"))
1908c2ecf20Sopenharmony_ci		fct->ctrl.name = "cpu-rear-fan-0";
1918c2ecf20Sopenharmony_ci	else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1") ||
1928c2ecf20Sopenharmony_ci		 !strcmp(l, "CPU B EXHAUST"))
1938c2ecf20Sopenharmony_ci		fct->ctrl.name = "cpu-rear-fan-1";
1948c2ecf20Sopenharmony_ci	else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") ||
1958c2ecf20Sopenharmony_ci		 !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan") ||
1968c2ecf20Sopenharmony_ci		 !strcmp(l, "CPU A INTAKE"))
1978c2ecf20Sopenharmony_ci		fct->ctrl.name = "cpu-front-fan-0";
1988c2ecf20Sopenharmony_ci	else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1") ||
1998c2ecf20Sopenharmony_ci		 !strcmp(l, "CPU B INTAKE"))
2008c2ecf20Sopenharmony_ci		fct->ctrl.name = "cpu-front-fan-1";
2018c2ecf20Sopenharmony_ci	else if (!strcmp(l, "CPU A PUMP"))
2028c2ecf20Sopenharmony_ci		fct->ctrl.name = "cpu-pump-0";
2038c2ecf20Sopenharmony_ci	else if (!strcmp(l, "CPU B PUMP"))
2048c2ecf20Sopenharmony_ci		fct->ctrl.name = "cpu-pump-1";
2058c2ecf20Sopenharmony_ci	else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan") ||
2068c2ecf20Sopenharmony_ci		 !strcmp(l, "EXPANSION SLOTS INTAKE"))
2078c2ecf20Sopenharmony_ci		fct->ctrl.name = "slots-fan";
2088c2ecf20Sopenharmony_ci	else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay") ||
2098c2ecf20Sopenharmony_ci		 !strcmp(l, "DRIVE BAY A INTAKE"))
2108c2ecf20Sopenharmony_ci		fct->ctrl.name = "drive-bay-fan";
2118c2ecf20Sopenharmony_ci	else if (!strcmp(l, "BACKSIDE"))
2128c2ecf20Sopenharmony_ci		fct->ctrl.name = "backside-fan";
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	/* Names used on iMac models */
2158c2ecf20Sopenharmony_ci	if (!strcmp(l, "System Fan") || !strcmp(l, "System fan"))
2168c2ecf20Sopenharmony_ci		fct->ctrl.name = "system-fan";
2178c2ecf20Sopenharmony_ci	else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan"))
2188c2ecf20Sopenharmony_ci		fct->ctrl.name = "cpu-fan";
2198c2ecf20Sopenharmony_ci	else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive"))
2208c2ecf20Sopenharmony_ci		fct->ctrl.name = "drive-bay-fan";
2218c2ecf20Sopenharmony_ci	else if (!strcmp(l, "HDD Fan")) /* seen on iMac G5 iSight */
2228c2ecf20Sopenharmony_ci		fct->ctrl.name = "hard-drive-fan";
2238c2ecf20Sopenharmony_ci	else if (!strcmp(l, "ODD Fan")) /* same */
2248c2ecf20Sopenharmony_ci		fct->ctrl.name = "optical-drive-fan";
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	/* Unrecognized fan, bail out */
2278c2ecf20Sopenharmony_ci	if (fct->ctrl.name == NULL)
2288c2ecf20Sopenharmony_ci		goto fail;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	/* Get min & max values*/
2318c2ecf20Sopenharmony_ci	v = of_get_property(node, "min-value", NULL);
2328c2ecf20Sopenharmony_ci	if (v == NULL)
2338c2ecf20Sopenharmony_ci		goto fail;
2348c2ecf20Sopenharmony_ci	fct->min = *v;
2358c2ecf20Sopenharmony_ci	v = of_get_property(node, "max-value", NULL);
2368c2ecf20Sopenharmony_ci	if (v == NULL)
2378c2ecf20Sopenharmony_ci		goto fail;
2388c2ecf20Sopenharmony_ci	fct->max = *v;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	/* Get "reg" value */
2418c2ecf20Sopenharmony_ci	reg = of_get_property(node, "reg", NULL);
2428c2ecf20Sopenharmony_ci	if (reg == NULL)
2438c2ecf20Sopenharmony_ci		goto fail;
2448c2ecf20Sopenharmony_ci	fct->reg = *reg;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	if (wf_register_control(&fct->ctrl))
2478c2ecf20Sopenharmony_ci		goto fail;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	return fct;
2508c2ecf20Sopenharmony_ci fail:
2518c2ecf20Sopenharmony_ci	kfree(fct);
2528c2ecf20Sopenharmony_ci	return NULL;
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_cistatic int __init smu_controls_init(void)
2578c2ecf20Sopenharmony_ci{
2588c2ecf20Sopenharmony_ci	struct device_node *smu, *fans, *fan;
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	if (!smu_present())
2618c2ecf20Sopenharmony_ci		return -ENODEV;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	smu = of_find_node_by_type(NULL, "smu");
2648c2ecf20Sopenharmony_ci	if (smu == NULL)
2658c2ecf20Sopenharmony_ci		return -ENODEV;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	/* Look for RPM fans */
2688c2ecf20Sopenharmony_ci	for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
2698c2ecf20Sopenharmony_ci		if (of_node_name_eq(fans, "rpm-fans") ||
2708c2ecf20Sopenharmony_ci		    of_device_is_compatible(fans, "smu-rpm-fans"))
2718c2ecf20Sopenharmony_ci			break;
2728c2ecf20Sopenharmony_ci	for (fan = NULL;
2738c2ecf20Sopenharmony_ci	     fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
2748c2ecf20Sopenharmony_ci		struct smu_fan_control *fct;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci		fct = smu_fan_create(fan, 0);
2778c2ecf20Sopenharmony_ci		if (fct == NULL) {
2788c2ecf20Sopenharmony_ci			printk(KERN_WARNING "windfarm: Failed to create SMU "
2798c2ecf20Sopenharmony_ci			       "RPM fan %pOFn\n", fan);
2808c2ecf20Sopenharmony_ci			continue;
2818c2ecf20Sopenharmony_ci		}
2828c2ecf20Sopenharmony_ci		list_add(&fct->link, &smu_fans);
2838c2ecf20Sopenharmony_ci	}
2848c2ecf20Sopenharmony_ci	of_node_put(fans);
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	/* Look for PWM fans */
2888c2ecf20Sopenharmony_ci	for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
2898c2ecf20Sopenharmony_ci		if (of_node_name_eq(fans, "pwm-fans"))
2908c2ecf20Sopenharmony_ci			break;
2918c2ecf20Sopenharmony_ci	for (fan = NULL;
2928c2ecf20Sopenharmony_ci	     fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
2938c2ecf20Sopenharmony_ci		struct smu_fan_control *fct;
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci		fct = smu_fan_create(fan, 1);
2968c2ecf20Sopenharmony_ci		if (fct == NULL) {
2978c2ecf20Sopenharmony_ci			printk(KERN_WARNING "windfarm: Failed to create SMU "
2988c2ecf20Sopenharmony_ci			       "PWM fan %pOFn\n", fan);
2998c2ecf20Sopenharmony_ci			continue;
3008c2ecf20Sopenharmony_ci		}
3018c2ecf20Sopenharmony_ci		list_add(&fct->link, &smu_fans);
3028c2ecf20Sopenharmony_ci	}
3038c2ecf20Sopenharmony_ci	of_node_put(fans);
3048c2ecf20Sopenharmony_ci	of_node_put(smu);
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	return 0;
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_cistatic void __exit smu_controls_exit(void)
3108c2ecf20Sopenharmony_ci{
3118c2ecf20Sopenharmony_ci	struct smu_fan_control *fct;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	while (!list_empty(&smu_fans)) {
3148c2ecf20Sopenharmony_ci		fct = list_entry(smu_fans.next, struct smu_fan_control, link);
3158c2ecf20Sopenharmony_ci		list_del(&fct->link);
3168c2ecf20Sopenharmony_ci		wf_unregister_control(&fct->ctrl);
3178c2ecf20Sopenharmony_ci	}
3188c2ecf20Sopenharmony_ci}
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_cimodule_init(smu_controls_init);
3228c2ecf20Sopenharmony_cimodule_exit(smu_controls_exit);
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
3258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control");
3268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
3278c2ecf20Sopenharmony_ci
328