File Index
Files and directories
Sign in
FILE

ayaneo-ec.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/ayaneo-ec.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * Platform driver for the Embedded Controller (EC) of Ayaneo devices. Handles
  4 * hwmon (fan speed, fan control), battery charge limits, and magic module
  5 * control (connected modules, controller disconnection).
  6 *
  7 * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
  8 */
  9
 10#include <linux/acpi.h>
 11#include <linux/bits.h>
 12#include <linux/dmi.h>
 13#include <linux/err.h>
 14#include <linux/hwmon.h>
 15#include <linux/init.h>
 16#include <linux/kernel.h>
 17#include <linux/module.h>
 18#include <linux/platform_device.h>
 19#include <linux/pm.h>
 20#include <linux/power_supply.h>
 21#include <linux/sysfs.h>
 22#include <acpi/battery.h>
 23
 24#define AYANEO_PWM_ENABLE_REG	 0x4A
 25#define AYANEO_PWM_REG		 0x4B
 26#define AYANEO_PWM_MODE_AUTO	 0x00
 27#define AYANEO_PWM_MODE_MANUAL	 0x01
 28
 29#define AYANEO_FAN_REG		 0x76
 30
 31#define EC_CHARGE_CONTROL_BEHAVIOURS                         \
 32	(BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) |           \
 33	 BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))
 34#define AYANEO_CHARGE_REG		0x1e
 35#define AYANEO_CHARGE_VAL_AUTO		0xaa
 36#define AYANEO_CHARGE_VAL_INHIBIT	0x55
 37
 38#define AYANEO_POWER_REG	0x2d
 39#define AYANEO_POWER_OFF	0xfe
 40#define AYANEO_POWER_ON		0xff
 41#define AYANEO_MODULE_REG	0x2f
 42#define AYANEO_MODULE_LEFT	BIT(0)
 43#define AYANEO_MODULE_RIGHT	BIT(1)
 44#define AYANEO_MODULE_MASK	(AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
 45
 46struct ayaneo_ec_quirk {
 47	bool has_fan_control;
 48	bool has_charge_control;
 49	bool has_magic_modules;
 50};
 51
 52struct ayaneo_ec_platform_data {
 53	struct platform_device *pdev;
 54	struct ayaneo_ec_quirk *quirks;
 55	struct acpi_battery_hook battery_hook;
 56
 57	// Protects access to restore_pwm
 58	struct mutex hwmon_lock;
 59	bool restore_charge_limit;
 60	bool restore_pwm;
 61};
 62
 63static const struct ayaneo_ec_quirk quirk_fan = {
 64	.has_fan_control = true,
 65};
 66
 67static const struct ayaneo_ec_quirk quirk_charge_limit = {
 68	.has_fan_control = true,
 69	.has_charge_control = true,
 70};
 71
 72static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
 73	.has_fan_control = true,
 74	.has_charge_control = true,
 75	.has_magic_modules = true,
 76};
 77
 78static const struct dmi_system_id dmi_table[] = {
 79	{
 80		.matches = {
 81			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
 82			DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
 83		},
 84		.driver_data = (void *)&quirk_fan,
 85	},
 86	{
 87		.matches = {
 88			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
 89			DMI_MATCH(DMI_BOARD_NAME, "FLIP"),
 90		},
 91		.driver_data = (void *)&quirk_fan,
 92	},
 93	{
 94		.matches = {
 95			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
 96			DMI_MATCH(DMI_BOARD_NAME, "GEEK"),
 97		},
 98		.driver_data = (void *)&quirk_fan,
 99	},
100	{
101		.matches = {
102			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
103			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
104		},
105		.driver_data = (void *)&quirk_charge_limit,
106	},
107	{
108		.matches = {
109			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
110			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),
111		},
112		.driver_data = (void *)&quirk_charge_limit,
113	},
114	{
115		.matches = {
116			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
117			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
118		},
119		.driver_data = (void *)&quirk_charge_limit,
120	},
121	{
122		.matches = {
123			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
124			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
125		},
126		.driver_data = (void *)&quirk_charge_limit,
127	},
128	{
129		.matches = {
130			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
131			DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),
132		},
133		.driver_data = (void *)&quirk_charge_limit,
134	},
135	{
136		.matches = {
137			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
138			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 3"),
139		},
140		.driver_data = (void *)&quirk_ayaneo3,
141	},
142	{},
143};
144
145/* Callbacks for hwmon interface */
146static umode_t ayaneo_ec_hwmon_is_visible(const void *drvdata,
147					  enum hwmon_sensor_types type, u32 attr,
148					  int channel)
149{
150	switch (type) {
151	case hwmon_fan:
152		return 0444;
153	case hwmon_pwm:
154		return 0644;
155	default:
156		return 0;
157	}
158}
159
160static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
161			  u32 attr, int channel, long *val)
162{
163	u8 tmp;
164	int ret;
165
166	switch (type) {
167	case hwmon_fan:
168		switch (attr) {
169		case hwmon_fan_input:
170			ret = ec_read(AYANEO_FAN_REG, &tmp);
171			if (ret)
172				return ret;
173			*val = tmp << 8;
174			ret = ec_read(AYANEO_FAN_REG + 1, &tmp);
175			if (ret)
176				return ret;
177			*val |= tmp;
178			return 0;
179		default:
180			break;
181		}
182		break;
183	case hwmon_pwm:
184		switch (attr) {
185		case hwmon_pwm_input:
186			ret = ec_read(AYANEO_PWM_REG, &tmp);
187			if (ret)
188				return ret;
189			if (tmp > 100)
190				return -EIO;
191			*val = (255 * tmp) / 100;
192			return 0;
193		case hwmon_pwm_enable:
194			ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
195			if (ret)
196				return ret;
197			if (tmp == AYANEO_PWM_MODE_MANUAL)
198				*val = 1;
199			else if (tmp == AYANEO_PWM_MODE_AUTO)
200				*val = 2;
201			else
202				return -EIO;
203			return 0;
204		default:
205			break;
206		}
207		break;
208	default:
209		break;
210	}
211	return -EOPNOTSUPP;
212}
213
214static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
215			   u32 attr, int channel, long val)
216{
217	struct ayaneo_ec_platform_data *data = dev_get_drvdata(dev);
218	int ret;
219
220	guard(mutex)(&data->hwmon_lock);
221
222	switch (type) {
223	case hwmon_pwm:
224		switch (attr) {
225		case hwmon_pwm_enable:
226			data->restore_pwm = false;
227			switch (val) {
228			case 1:
229				return ec_write(AYANEO_PWM_ENABLE_REG,
230						AYANEO_PWM_MODE_MANUAL);
231			case 2:
232				return ec_write(AYANEO_PWM_ENABLE_REG,
233						AYANEO_PWM_MODE_AUTO);
234			default:
235				return -EINVAL;
236			}
237		case hwmon_pwm_input:
238			if (val < 0 || val > 255)
239				return -EINVAL;
240			if (data->restore_pwm) {
241				/*
242				 * Defer restoring PWM control to after
243				 * userspace resumes successfully
244				 */
245				ret = ec_write(AYANEO_PWM_ENABLE_REG,
246					       AYANEO_PWM_MODE_MANUAL);
247				if (ret)
248					return ret;
249				data->restore_pwm = false;
250			}
251			return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
252		default:
253			break;
254		}
255		break;
256	default:
257		break;
258	}
259	return -EOPNOTSUPP;
260}
261
262static const struct hwmon_ops ayaneo_ec_hwmon_ops = {
263	.is_visible = ayaneo_ec_hwmon_is_visible,
264	.read = ayaneo_ec_read,
265	.write = ayaneo_ec_write,
266};
267
268static const struct hwmon_channel_info *const ayaneo_ec_sensors[] = {
269	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
270	HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
271	NULL,
272};
273
274static const struct hwmon_chip_info ayaneo_ec_chip_info = {
275	.ops = &ayaneo_ec_hwmon_ops,
276	.info = ayaneo_ec_sensors,
277};
278
279static int ayaneo_psy_ext_get_prop(struct power_supply *psy,
280				   const struct power_supply_ext *ext,
281				   void *data,
282				   enum power_supply_property psp,
283				   union power_supply_propval *val)
284{
285	int ret;
286	u8 tmp;
287
288	switch (psp) {
289	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
290		ret = ec_read(AYANEO_CHARGE_REG, &tmp);
291		if (ret)
292			return ret;
293
294		if (tmp == AYANEO_CHARGE_VAL_INHIBIT)
295			val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
296		else
297			val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
298		return 0;
299	default:
300		return -EINVAL;
301	}
302}
303
304static int ayaneo_psy_ext_set_prop(struct power_supply *psy,
305				   const struct power_supply_ext *ext,
306				   void *data,
307				   enum power_supply_property psp,
308				   const union power_supply_propval *val)
309{
310	u8 raw_val;
311
312	switch (psp) {
313	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
314		switch (val->intval) {
315		case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
316			raw_val = AYANEO_CHARGE_VAL_AUTO;
317			break;
318		case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
319			raw_val = AYANEO_CHARGE_VAL_INHIBIT;
320			break;
321		default:
322			return -EINVAL;
323		}
324		return ec_write(AYANEO_CHARGE_REG, raw_val);
325	default:
326		return -EINVAL;
327	}
328}
329
330static int ayaneo_psy_prop_is_writeable(struct power_supply *psy,
331					const struct power_supply_ext *ext,
332					void *data,
333					enum power_supply_property psp)
334{
335	return true;
336}
337
338static const enum power_supply_property ayaneo_psy_ext_props[] = {
339	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
340};
341
342static const struct power_supply_ext ayaneo_psy_ext = {
343	.name			= "ayaneo-charge-control",
344	.properties		= ayaneo_psy_ext_props,
345	.num_properties		= ARRAY_SIZE(ayaneo_psy_ext_props),
346	.charge_behaviours	= EC_CHARGE_CONTROL_BEHAVIOURS,
347	.get_property		= ayaneo_psy_ext_get_prop,
348	.set_property		= ayaneo_psy_ext_set_prop,
349	.property_is_writeable	= ayaneo_psy_prop_is_writeable,
350};
351
352static int ayaneo_add_battery(struct power_supply *battery,
353			      struct acpi_battery_hook *hook)
354{
355	struct ayaneo_ec_platform_data *data =
356		container_of(hook, struct ayaneo_ec_platform_data, battery_hook);
357
358	return power_supply_register_extension(battery, &ayaneo_psy_ext,
359					       &data->pdev->dev, NULL);
360}
361
362static int ayaneo_remove_battery(struct power_supply *battery,
363				 struct acpi_battery_hook *hook)
364{
365	power_supply_unregister_extension(battery, &ayaneo_psy_ext);
366	return 0;
367}
368
369static ssize_t controller_power_store(struct device *dev,
370				      struct device_attribute *attr,
371				      const char *buf,
372				      size_t count)
373{
374	bool value;
375	int ret;
376
377	ret = kstrtobool(buf, &value);
378	if (ret)
379		return ret;
380
381	ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
382	if (ret)
383		return ret;
384
385	return count;
386}
387
388static ssize_t controller_power_show(struct device *dev,
389				     struct device_attribute *attr,
390				     char *buf)
391{
392	int ret;
393	u8 val;
394
395	ret = ec_read(AYANEO_POWER_REG, &val);
396	if (ret)
397		return ret;
398
399	return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
400}
401
402static DEVICE_ATTR_RW(controller_power);
403
404static ssize_t controller_modules_show(struct device *dev,
405				       struct device_attribute *attr, char *buf)
406{
407	u8 unconnected_modules;
408	char *out;
409	int ret;
410
411	ret = ec_read(AYANEO_MODULE_REG, &unconnected_modules);
412	if (ret)
413		return ret;
414
415	switch (~unconnected_modules & AYANEO_MODULE_MASK) {
416	case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
417		out = "both";
418		break;
419	case AYANEO_MODULE_LEFT:
420		out = "left";
421		break;
422	case AYANEO_MODULE_RIGHT:
423		out = "right";
424		break;
425	default:
426		out = "none";
427		break;
428	}
429
430	return sysfs_emit(buf, "%s\n", out);
431}
432
433static DEVICE_ATTR_RO(controller_modules);
434
435static struct attribute *aya_mm_attrs[] = {
436	&dev_attr_controller_power.attr,
437	&dev_attr_controller_modules.attr,
438	NULL
439};
440
441static umode_t aya_mm_is_visible(struct kobject *kobj,
442				 struct attribute *attr, int n)
443{
444	struct device *dev = kobj_to_dev(kobj);
445	struct platform_device *pdev = to_platform_device(dev);
446	struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
447
448	if (data->quirks->has_magic_modules)
449		return attr->mode;
450	return 0;
451}
452
453static const struct attribute_group aya_mm_attribute_group = {
454	.is_visible = aya_mm_is_visible,
455	.attrs = aya_mm_attrs,
456};
457
458static const struct attribute_group *ayaneo_ec_groups[] = {
459	&aya_mm_attribute_group,
460	NULL
461};
462
463static int ayaneo_ec_probe(struct platform_device *pdev)
464{
465	const struct dmi_system_id *dmi_entry;
466	struct ayaneo_ec_platform_data *data;
467	struct device *hwdev;
468	int ret;
469
470	dmi_entry = dmi_first_match(dmi_table);
471	if (!dmi_entry)
472		return -ENODEV;
473
474	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
475	if (!data)
476		return -ENOMEM;
477
478	data->pdev = pdev;
479	data->quirks = dmi_entry->driver_data;
480	ret = devm_mutex_init(&pdev->dev, &data->hwmon_lock);
481	if (ret)
482		return ret;
483	platform_set_drvdata(pdev, data);
484
485	if (data->quirks->has_fan_control) {
486		hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
487			"ayaneo_ec", data, &ayaneo_ec_chip_info, NULL);
488		if (IS_ERR(hwdev))
489			return PTR_ERR(hwdev);
490	}
491
492	if (data->quirks->has_charge_control) {
493		data->battery_hook.add_battery = ayaneo_add_battery;
494		data->battery_hook.remove_battery = ayaneo_remove_battery;
495		data->battery_hook.name = "Ayaneo Battery";
496		ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook);
497		if (ret)
498			return ret;
499	}
500
501	return 0;
502}
503
504static int ayaneo_freeze(struct device *dev)
505{
506	struct platform_device *pdev = to_platform_device(dev);
507	struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
508	int ret;
509	u8 tmp;
510
511	if (data->quirks->has_charge_control) {
512		ret = ec_read(AYANEO_CHARGE_REG, &tmp);
513		if (ret)
514			return ret;
515
516		data->restore_charge_limit = tmp == AYANEO_CHARGE_VAL_INHIBIT;
517	}
518
519	if (data->quirks->has_fan_control) {
520		ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
521		if (ret)
522			return ret;
523
524		data->restore_pwm = tmp == AYANEO_PWM_MODE_MANUAL;
525
526		/*
527		 * Release the fan when entering hibernation to avoid
528		 * overheating if hibernation fails and hangs.
529		 */
530		if (data->restore_pwm) {
531			ret = ec_write(AYANEO_PWM_ENABLE_REG, AYANEO_PWM_MODE_AUTO);
532			if (ret)
533				return ret;
534		}
535	}
536
537	return 0;
538}
539
540static int ayaneo_restore(struct device *dev)
541{
542	struct platform_device *pdev = to_platform_device(dev);
543	struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
544	int ret;
545
546	if (data->quirks->has_charge_control && data->restore_charge_limit) {
547		ret = ec_write(AYANEO_CHARGE_REG, AYANEO_CHARGE_VAL_INHIBIT);
548		if (ret)
549			return ret;
550	}
551
552	return 0;
553}
554
555static const struct dev_pm_ops ayaneo_pm_ops = {
556	.freeze = ayaneo_freeze,
557	.restore = ayaneo_restore,
558};
559
560static struct platform_driver ayaneo_platform_driver = {
561	.driver = {
562		.name = "ayaneo-ec",
563		.dev_groups = ayaneo_ec_groups,
564		.pm = pm_sleep_ptr(&ayaneo_pm_ops),
565	},
566	.probe = ayaneo_ec_probe,
567};
568
569static struct platform_device *ayaneo_platform_device;
570
571static int __init ayaneo_ec_init(void)
572{
573	ayaneo_platform_device =
574		platform_create_bundle(&ayaneo_platform_driver,
575				       ayaneo_ec_probe, NULL, 0, NULL, 0);
576
577	return PTR_ERR_OR_ZERO(ayaneo_platform_device);
578}
579
580static void __exit ayaneo_ec_exit(void)
581{
582	platform_device_unregister(ayaneo_platform_device);
583	platform_driver_unregister(&ayaneo_platform_driver);
584}
585
586MODULE_DEVICE_TABLE(dmi, dmi_table);
587
588module_init(ayaneo_ec_init);
589module_exit(ayaneo_ec_exit);
590
591MODULE_AUTHOR("Antheas Kapenekakis <lkml@antheas.dev>");
592MODULE_DESCRIPTION("Ayaneo Embedded Controller (EC) platform features");
593MODULE_LICENSE("GPL");
~/ Read-only file preview.