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