xref: /linux/drivers/platform/x86/ayaneo-ec.c (revision 9d588a1140b9ae211581a7a154d0b806d8cd8238)
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 
46 struct ayaneo_ec_quirk {
47 	bool has_fan_control;
48 	bool has_charge_control;
49 	bool has_magic_modules;
50 };
51 
52 struct 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 
63 static const struct ayaneo_ec_quirk quirk_fan = {
64 	.has_fan_control = true,
65 };
66 
67 static const struct ayaneo_ec_quirk quirk_charge_limit = {
68 	.has_fan_control = true,
69 	.has_charge_control = true,
70 };
71 
72 static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
73 	.has_fan_control = true,
74 	.has_charge_control = true,
75 	.has_magic_modules = true,
76 };
77 
78 static 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 */
ayaneo_ec_hwmon_is_visible(const void * drvdata,enum hwmon_sensor_types type,u32 attr,int channel)146 static 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 
ayaneo_ec_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)160 static 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 
ayaneo_ec_write(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long val)214 static 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 
262 static 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 
268 static 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 
274 static const struct hwmon_chip_info ayaneo_ec_chip_info = {
275 	.ops = &ayaneo_ec_hwmon_ops,
276 	.info = ayaneo_ec_sensors,
277 };
278 
ayaneo_psy_ext_get_prop(struct power_supply * psy,const struct power_supply_ext * ext,void * data,enum power_supply_property psp,union power_supply_propval * val)279 static 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 
ayaneo_psy_ext_set_prop(struct power_supply * psy,const struct power_supply_ext * ext,void * data,enum power_supply_property psp,const union power_supply_propval * val)304 static 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 
ayaneo_psy_prop_is_writeable(struct power_supply * psy,const struct power_supply_ext * ext,void * data,enum power_supply_property psp)330 static 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 
338 static const enum power_supply_property ayaneo_psy_ext_props[] = {
339 	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
340 };
341 
342 static 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 
ayaneo_add_battery(struct power_supply * battery,struct acpi_battery_hook * hook)352 static 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 
ayaneo_remove_battery(struct power_supply * battery,struct acpi_battery_hook * hook)362 static 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 
controller_power_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)369 static 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 
controller_power_show(struct device * dev,struct device_attribute * attr,char * buf)388 static 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 
402 static DEVICE_ATTR_RW(controller_power);
403 
controller_modules_show(struct device * dev,struct device_attribute * attr,char * buf)404 static 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 
433 static DEVICE_ATTR_RO(controller_modules);
434 
435 static struct attribute *aya_mm_attrs[] = {
436 	&dev_attr_controller_power.attr,
437 	&dev_attr_controller_modules.attr,
438 	NULL
439 };
440 
aya_mm_is_visible(struct kobject * kobj,struct attribute * attr,int n)441 static 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 
453 static const struct attribute_group aya_mm_attribute_group = {
454 	.is_visible = aya_mm_is_visible,
455 	.attrs = aya_mm_attrs,
456 };
457 
458 static const struct attribute_group *ayaneo_ec_groups[] = {
459 	&aya_mm_attribute_group,
460 	NULL
461 };
462 
ayaneo_ec_probe(struct platform_device * pdev)463 static 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 
ayaneo_freeze(struct device * dev)504 static 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 
ayaneo_restore(struct device * dev)540 static 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 
555 static const struct dev_pm_ops ayaneo_pm_ops = {
556 	.freeze = ayaneo_freeze,
557 	.restore = ayaneo_restore,
558 };
559 
560 static 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 
569 static struct platform_device *ayaneo_platform_device;
570 
ayaneo_ec_init(void)571 static 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 
ayaneo_ec_exit(void)580 static void __exit ayaneo_ec_exit(void)
581 {
582 	platform_device_unregister(ayaneo_platform_device);
583 	platform_driver_unregister(&ayaneo_platform_driver);
584 }
585 
586 MODULE_DEVICE_TABLE(dmi, dmi_table);
587 
588 module_init(ayaneo_ec_init);
589 module_exit(ayaneo_ec_exit);
590 
591 MODULE_AUTHOR("Antheas Kapenekakis <lkml@antheas.dev>");
592 MODULE_DESCRIPTION("Ayaneo Embedded Controller (EC) platform features");
593 MODULE_LICENSE("GPL");
594