170a4a815SAntheas Kapenekakis // SPDX-License-Identifier: GPL-2.0+
270a4a815SAntheas Kapenekakis /*
370a4a815SAntheas Kapenekakis * Platform driver for the Embedded Controller (EC) of Ayaneo devices. Handles
470a4a815SAntheas Kapenekakis * hwmon (fan speed, fan control), battery charge limits, and magic module
570a4a815SAntheas Kapenekakis * control (connected modules, controller disconnection).
670a4a815SAntheas Kapenekakis *
770a4a815SAntheas Kapenekakis * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
870a4a815SAntheas Kapenekakis */
970a4a815SAntheas Kapenekakis
10536522f0SAntheas Kapenekakis #include <linux/acpi.h>
11e921a8b4SAntheas Kapenekakis #include <linux/bits.h>
1270a4a815SAntheas Kapenekakis #include <linux/dmi.h>
1370a4a815SAntheas Kapenekakis #include <linux/err.h>
14536522f0SAntheas Kapenekakis #include <linux/hwmon.h>
1570a4a815SAntheas Kapenekakis #include <linux/init.h>
1670a4a815SAntheas Kapenekakis #include <linux/kernel.h>
1770a4a815SAntheas Kapenekakis #include <linux/module.h>
1870a4a815SAntheas Kapenekakis #include <linux/platform_device.h>
19*2643187cSAntheas Kapenekakis #include <linux/pm.h>
206d710ec3SAntheas Kapenekakis #include <linux/power_supply.h>
21e921a8b4SAntheas Kapenekakis #include <linux/sysfs.h>
226d710ec3SAntheas Kapenekakis #include <acpi/battery.h>
2370a4a815SAntheas Kapenekakis
24536522f0SAntheas Kapenekakis #define AYANEO_PWM_ENABLE_REG 0x4A
25536522f0SAntheas Kapenekakis #define AYANEO_PWM_REG 0x4B
26536522f0SAntheas Kapenekakis #define AYANEO_PWM_MODE_AUTO 0x00
27536522f0SAntheas Kapenekakis #define AYANEO_PWM_MODE_MANUAL 0x01
28536522f0SAntheas Kapenekakis
29536522f0SAntheas Kapenekakis #define AYANEO_FAN_REG 0x76
30536522f0SAntheas Kapenekakis
316d710ec3SAntheas Kapenekakis #define EC_CHARGE_CONTROL_BEHAVIOURS \
326d710ec3SAntheas Kapenekakis (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
336d710ec3SAntheas Kapenekakis BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))
346d710ec3SAntheas Kapenekakis #define AYANEO_CHARGE_REG 0x1e
356d710ec3SAntheas Kapenekakis #define AYANEO_CHARGE_VAL_AUTO 0xaa
366d710ec3SAntheas Kapenekakis #define AYANEO_CHARGE_VAL_INHIBIT 0x55
376d710ec3SAntheas Kapenekakis
38e921a8b4SAntheas Kapenekakis #define AYANEO_POWER_REG 0x2d
39e921a8b4SAntheas Kapenekakis #define AYANEO_POWER_OFF 0xfe
40e921a8b4SAntheas Kapenekakis #define AYANEO_POWER_ON 0xff
41e921a8b4SAntheas Kapenekakis #define AYANEO_MODULE_REG 0x2f
42e921a8b4SAntheas Kapenekakis #define AYANEO_MODULE_LEFT BIT(0)
43e921a8b4SAntheas Kapenekakis #define AYANEO_MODULE_RIGHT BIT(1)
44e921a8b4SAntheas Kapenekakis #define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
45e921a8b4SAntheas Kapenekakis
4670a4a815SAntheas Kapenekakis struct ayaneo_ec_quirk {
47536522f0SAntheas Kapenekakis bool has_fan_control;
486d710ec3SAntheas Kapenekakis bool has_charge_control;
49e921a8b4SAntheas Kapenekakis bool has_magic_modules;
5070a4a815SAntheas Kapenekakis };
5170a4a815SAntheas Kapenekakis
5270a4a815SAntheas Kapenekakis struct ayaneo_ec_platform_data {
5370a4a815SAntheas Kapenekakis struct platform_device *pdev;
5470a4a815SAntheas Kapenekakis struct ayaneo_ec_quirk *quirks;
556d710ec3SAntheas Kapenekakis struct acpi_battery_hook battery_hook;
56*2643187cSAntheas Kapenekakis
57*2643187cSAntheas Kapenekakis // Protects access to restore_pwm
58*2643187cSAntheas Kapenekakis struct mutex hwmon_lock;
59*2643187cSAntheas Kapenekakis bool restore_charge_limit;
60*2643187cSAntheas Kapenekakis bool restore_pwm;
6170a4a815SAntheas Kapenekakis };
6270a4a815SAntheas Kapenekakis
6302c15e3dSAntheas Kapenekakis static const struct ayaneo_ec_quirk quirk_fan = {
6402c15e3dSAntheas Kapenekakis .has_fan_control = true,
6502c15e3dSAntheas Kapenekakis };
6602c15e3dSAntheas Kapenekakis
6702c15e3dSAntheas Kapenekakis static const struct ayaneo_ec_quirk quirk_charge_limit = {
6802c15e3dSAntheas Kapenekakis .has_fan_control = true,
6902c15e3dSAntheas Kapenekakis .has_charge_control = true,
7002c15e3dSAntheas Kapenekakis };
7102c15e3dSAntheas Kapenekakis
7270a4a815SAntheas Kapenekakis static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
73536522f0SAntheas Kapenekakis .has_fan_control = true,
746d710ec3SAntheas Kapenekakis .has_charge_control = true,
75e921a8b4SAntheas Kapenekakis .has_magic_modules = true,
7670a4a815SAntheas Kapenekakis };
7770a4a815SAntheas Kapenekakis
7870a4a815SAntheas Kapenekakis static const struct dmi_system_id dmi_table[] = {
7970a4a815SAntheas Kapenekakis {
8070a4a815SAntheas Kapenekakis .matches = {
8170a4a815SAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
8202c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
8302c15e3dSAntheas Kapenekakis },
8402c15e3dSAntheas Kapenekakis .driver_data = (void *)&quirk_fan,
8502c15e3dSAntheas Kapenekakis },
8602c15e3dSAntheas Kapenekakis {
8702c15e3dSAntheas Kapenekakis .matches = {
8802c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
8902c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_NAME, "FLIP"),
9002c15e3dSAntheas Kapenekakis },
9102c15e3dSAntheas Kapenekakis .driver_data = (void *)&quirk_fan,
9202c15e3dSAntheas Kapenekakis },
9302c15e3dSAntheas Kapenekakis {
9402c15e3dSAntheas Kapenekakis .matches = {
9502c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
9602c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_NAME, "GEEK"),
9702c15e3dSAntheas Kapenekakis },
9802c15e3dSAntheas Kapenekakis .driver_data = (void *)&quirk_fan,
9902c15e3dSAntheas Kapenekakis },
10002c15e3dSAntheas Kapenekakis {
10102c15e3dSAntheas Kapenekakis .matches = {
10202c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
10302c15e3dSAntheas Kapenekakis DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
10402c15e3dSAntheas Kapenekakis },
10502c15e3dSAntheas Kapenekakis .driver_data = (void *)&quirk_charge_limit,
10602c15e3dSAntheas Kapenekakis },
10702c15e3dSAntheas Kapenekakis {
10802c15e3dSAntheas Kapenekakis .matches = {
10902c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
11002c15e3dSAntheas Kapenekakis DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),
11102c15e3dSAntheas Kapenekakis },
11202c15e3dSAntheas Kapenekakis .driver_data = (void *)&quirk_charge_limit,
11302c15e3dSAntheas Kapenekakis },
11402c15e3dSAntheas Kapenekakis {
11502c15e3dSAntheas Kapenekakis .matches = {
11602c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
11702c15e3dSAntheas Kapenekakis DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
11802c15e3dSAntheas Kapenekakis },
11902c15e3dSAntheas Kapenekakis .driver_data = (void *)&quirk_charge_limit,
12002c15e3dSAntheas Kapenekakis },
12102c15e3dSAntheas Kapenekakis {
12202c15e3dSAntheas Kapenekakis .matches = {
12302c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
12402c15e3dSAntheas Kapenekakis DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
12502c15e3dSAntheas Kapenekakis },
12602c15e3dSAntheas Kapenekakis .driver_data = (void *)&quirk_charge_limit,
12702c15e3dSAntheas Kapenekakis },
12802c15e3dSAntheas Kapenekakis {
12902c15e3dSAntheas Kapenekakis .matches = {
13002c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
13102c15e3dSAntheas Kapenekakis DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),
13202c15e3dSAntheas Kapenekakis },
13302c15e3dSAntheas Kapenekakis .driver_data = (void *)&quirk_charge_limit,
13402c15e3dSAntheas Kapenekakis },
13502c15e3dSAntheas Kapenekakis {
13602c15e3dSAntheas Kapenekakis .matches = {
13702c15e3dSAntheas Kapenekakis DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
13870a4a815SAntheas Kapenekakis DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 3"),
13970a4a815SAntheas Kapenekakis },
14070a4a815SAntheas Kapenekakis .driver_data = (void *)&quirk_ayaneo3,
14170a4a815SAntheas Kapenekakis },
14270a4a815SAntheas Kapenekakis {},
14370a4a815SAntheas Kapenekakis };
14470a4a815SAntheas Kapenekakis
145536522f0SAntheas Kapenekakis /* Callbacks for hwmon interface */
ayaneo_ec_hwmon_is_visible(const void * drvdata,enum hwmon_sensor_types type,u32 attr,int channel)146536522f0SAntheas Kapenekakis static umode_t ayaneo_ec_hwmon_is_visible(const void *drvdata,
147536522f0SAntheas Kapenekakis enum hwmon_sensor_types type, u32 attr,
148536522f0SAntheas Kapenekakis int channel)
149536522f0SAntheas Kapenekakis {
150536522f0SAntheas Kapenekakis switch (type) {
151536522f0SAntheas Kapenekakis case hwmon_fan:
152536522f0SAntheas Kapenekakis return 0444;
153536522f0SAntheas Kapenekakis case hwmon_pwm:
154536522f0SAntheas Kapenekakis return 0644;
155536522f0SAntheas Kapenekakis default:
156536522f0SAntheas Kapenekakis return 0;
157536522f0SAntheas Kapenekakis }
158536522f0SAntheas Kapenekakis }
159536522f0SAntheas Kapenekakis
ayaneo_ec_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)160536522f0SAntheas Kapenekakis static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
161536522f0SAntheas Kapenekakis u32 attr, int channel, long *val)
162536522f0SAntheas Kapenekakis {
163536522f0SAntheas Kapenekakis u8 tmp;
164536522f0SAntheas Kapenekakis int ret;
165536522f0SAntheas Kapenekakis
166536522f0SAntheas Kapenekakis switch (type) {
167536522f0SAntheas Kapenekakis case hwmon_fan:
168536522f0SAntheas Kapenekakis switch (attr) {
169536522f0SAntheas Kapenekakis case hwmon_fan_input:
170536522f0SAntheas Kapenekakis ret = ec_read(AYANEO_FAN_REG, &tmp);
171536522f0SAntheas Kapenekakis if (ret)
172536522f0SAntheas Kapenekakis return ret;
173536522f0SAntheas Kapenekakis *val = tmp << 8;
174536522f0SAntheas Kapenekakis ret = ec_read(AYANEO_FAN_REG + 1, &tmp);
175536522f0SAntheas Kapenekakis if (ret)
176536522f0SAntheas Kapenekakis return ret;
177536522f0SAntheas Kapenekakis *val |= tmp;
178536522f0SAntheas Kapenekakis return 0;
179536522f0SAntheas Kapenekakis default:
180536522f0SAntheas Kapenekakis break;
181536522f0SAntheas Kapenekakis }
182536522f0SAntheas Kapenekakis break;
183536522f0SAntheas Kapenekakis case hwmon_pwm:
184536522f0SAntheas Kapenekakis switch (attr) {
185536522f0SAntheas Kapenekakis case hwmon_pwm_input:
186536522f0SAntheas Kapenekakis ret = ec_read(AYANEO_PWM_REG, &tmp);
187536522f0SAntheas Kapenekakis if (ret)
188536522f0SAntheas Kapenekakis return ret;
189536522f0SAntheas Kapenekakis if (tmp > 100)
190536522f0SAntheas Kapenekakis return -EIO;
191536522f0SAntheas Kapenekakis *val = (255 * tmp) / 100;
192536522f0SAntheas Kapenekakis return 0;
193536522f0SAntheas Kapenekakis case hwmon_pwm_enable:
194536522f0SAntheas Kapenekakis ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
195536522f0SAntheas Kapenekakis if (ret)
196536522f0SAntheas Kapenekakis return ret;
197536522f0SAntheas Kapenekakis if (tmp == AYANEO_PWM_MODE_MANUAL)
198536522f0SAntheas Kapenekakis *val = 1;
199536522f0SAntheas Kapenekakis else if (tmp == AYANEO_PWM_MODE_AUTO)
200536522f0SAntheas Kapenekakis *val = 2;
201536522f0SAntheas Kapenekakis else
202536522f0SAntheas Kapenekakis return -EIO;
203536522f0SAntheas Kapenekakis return 0;
204536522f0SAntheas Kapenekakis default:
205536522f0SAntheas Kapenekakis break;
206536522f0SAntheas Kapenekakis }
207536522f0SAntheas Kapenekakis break;
208536522f0SAntheas Kapenekakis default:
209536522f0SAntheas Kapenekakis break;
210536522f0SAntheas Kapenekakis }
211536522f0SAntheas Kapenekakis return -EOPNOTSUPP;
212536522f0SAntheas Kapenekakis }
213536522f0SAntheas Kapenekakis
ayaneo_ec_write(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long val)214536522f0SAntheas Kapenekakis static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
215536522f0SAntheas Kapenekakis u32 attr, int channel, long val)
216536522f0SAntheas Kapenekakis {
217*2643187cSAntheas Kapenekakis struct ayaneo_ec_platform_data *data = dev_get_drvdata(dev);
218*2643187cSAntheas Kapenekakis int ret;
219*2643187cSAntheas Kapenekakis
220*2643187cSAntheas Kapenekakis guard(mutex)(&data->hwmon_lock);
221*2643187cSAntheas Kapenekakis
222536522f0SAntheas Kapenekakis switch (type) {
223536522f0SAntheas Kapenekakis case hwmon_pwm:
224536522f0SAntheas Kapenekakis switch (attr) {
225536522f0SAntheas Kapenekakis case hwmon_pwm_enable:
226*2643187cSAntheas Kapenekakis data->restore_pwm = false;
227536522f0SAntheas Kapenekakis switch (val) {
228536522f0SAntheas Kapenekakis case 1:
229536522f0SAntheas Kapenekakis return ec_write(AYANEO_PWM_ENABLE_REG,
230536522f0SAntheas Kapenekakis AYANEO_PWM_MODE_MANUAL);
231536522f0SAntheas Kapenekakis case 2:
232536522f0SAntheas Kapenekakis return ec_write(AYANEO_PWM_ENABLE_REG,
233536522f0SAntheas Kapenekakis AYANEO_PWM_MODE_AUTO);
234536522f0SAntheas Kapenekakis default:
235536522f0SAntheas Kapenekakis return -EINVAL;
236536522f0SAntheas Kapenekakis }
237536522f0SAntheas Kapenekakis case hwmon_pwm_input:
238536522f0SAntheas Kapenekakis if (val < 0 || val > 255)
239536522f0SAntheas Kapenekakis return -EINVAL;
240*2643187cSAntheas Kapenekakis if (data->restore_pwm) {
241*2643187cSAntheas Kapenekakis /*
242*2643187cSAntheas Kapenekakis * Defer restoring PWM control to after
243*2643187cSAntheas Kapenekakis * userspace resumes successfully
244*2643187cSAntheas Kapenekakis */
245*2643187cSAntheas Kapenekakis ret = ec_write(AYANEO_PWM_ENABLE_REG,
246*2643187cSAntheas Kapenekakis AYANEO_PWM_MODE_MANUAL);
247*2643187cSAntheas Kapenekakis if (ret)
248*2643187cSAntheas Kapenekakis return ret;
249*2643187cSAntheas Kapenekakis data->restore_pwm = false;
250*2643187cSAntheas Kapenekakis }
251536522f0SAntheas Kapenekakis return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
252536522f0SAntheas Kapenekakis default:
253536522f0SAntheas Kapenekakis break;
254536522f0SAntheas Kapenekakis }
255536522f0SAntheas Kapenekakis break;
256536522f0SAntheas Kapenekakis default:
257536522f0SAntheas Kapenekakis break;
258536522f0SAntheas Kapenekakis }
259536522f0SAntheas Kapenekakis return -EOPNOTSUPP;
260536522f0SAntheas Kapenekakis }
261536522f0SAntheas Kapenekakis
262536522f0SAntheas Kapenekakis static const struct hwmon_ops ayaneo_ec_hwmon_ops = {
263536522f0SAntheas Kapenekakis .is_visible = ayaneo_ec_hwmon_is_visible,
264536522f0SAntheas Kapenekakis .read = ayaneo_ec_read,
265536522f0SAntheas Kapenekakis .write = ayaneo_ec_write,
266536522f0SAntheas Kapenekakis };
267536522f0SAntheas Kapenekakis
268536522f0SAntheas Kapenekakis static const struct hwmon_channel_info *const ayaneo_ec_sensors[] = {
269536522f0SAntheas Kapenekakis HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
270536522f0SAntheas Kapenekakis HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
271536522f0SAntheas Kapenekakis NULL,
272536522f0SAntheas Kapenekakis };
273536522f0SAntheas Kapenekakis
274536522f0SAntheas Kapenekakis static const struct hwmon_chip_info ayaneo_ec_chip_info = {
275536522f0SAntheas Kapenekakis .ops = &ayaneo_ec_hwmon_ops,
276536522f0SAntheas Kapenekakis .info = ayaneo_ec_sensors,
277536522f0SAntheas Kapenekakis };
278536522f0SAntheas Kapenekakis
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)2796d710ec3SAntheas Kapenekakis static int ayaneo_psy_ext_get_prop(struct power_supply *psy,
2806d710ec3SAntheas Kapenekakis const struct power_supply_ext *ext,
2816d710ec3SAntheas Kapenekakis void *data,
2826d710ec3SAntheas Kapenekakis enum power_supply_property psp,
2836d710ec3SAntheas Kapenekakis union power_supply_propval *val)
2846d710ec3SAntheas Kapenekakis {
2856d710ec3SAntheas Kapenekakis int ret;
2866d710ec3SAntheas Kapenekakis u8 tmp;
2876d710ec3SAntheas Kapenekakis
2886d710ec3SAntheas Kapenekakis switch (psp) {
2896d710ec3SAntheas Kapenekakis case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
2906d710ec3SAntheas Kapenekakis ret = ec_read(AYANEO_CHARGE_REG, &tmp);
2916d710ec3SAntheas Kapenekakis if (ret)
2926d710ec3SAntheas Kapenekakis return ret;
2936d710ec3SAntheas Kapenekakis
2946d710ec3SAntheas Kapenekakis if (tmp == AYANEO_CHARGE_VAL_INHIBIT)
2956d710ec3SAntheas Kapenekakis val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
2966d710ec3SAntheas Kapenekakis else
2976d710ec3SAntheas Kapenekakis val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
2986d710ec3SAntheas Kapenekakis return 0;
2996d710ec3SAntheas Kapenekakis default:
3006d710ec3SAntheas Kapenekakis return -EINVAL;
3016d710ec3SAntheas Kapenekakis }
3026d710ec3SAntheas Kapenekakis }
3036d710ec3SAntheas Kapenekakis
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)3046d710ec3SAntheas Kapenekakis static int ayaneo_psy_ext_set_prop(struct power_supply *psy,
3056d710ec3SAntheas Kapenekakis const struct power_supply_ext *ext,
3066d710ec3SAntheas Kapenekakis void *data,
3076d710ec3SAntheas Kapenekakis enum power_supply_property psp,
3086d710ec3SAntheas Kapenekakis const union power_supply_propval *val)
3096d710ec3SAntheas Kapenekakis {
3106d710ec3SAntheas Kapenekakis u8 raw_val;
3116d710ec3SAntheas Kapenekakis
3126d710ec3SAntheas Kapenekakis switch (psp) {
3136d710ec3SAntheas Kapenekakis case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
3146d710ec3SAntheas Kapenekakis switch (val->intval) {
3156d710ec3SAntheas Kapenekakis case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
3166d710ec3SAntheas Kapenekakis raw_val = AYANEO_CHARGE_VAL_AUTO;
3176d710ec3SAntheas Kapenekakis break;
3186d710ec3SAntheas Kapenekakis case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
3196d710ec3SAntheas Kapenekakis raw_val = AYANEO_CHARGE_VAL_INHIBIT;
3206d710ec3SAntheas Kapenekakis break;
3216d710ec3SAntheas Kapenekakis default:
3226d710ec3SAntheas Kapenekakis return -EINVAL;
3236d710ec3SAntheas Kapenekakis }
3246d710ec3SAntheas Kapenekakis return ec_write(AYANEO_CHARGE_REG, raw_val);
3256d710ec3SAntheas Kapenekakis default:
3266d710ec3SAntheas Kapenekakis return -EINVAL;
3276d710ec3SAntheas Kapenekakis }
3286d710ec3SAntheas Kapenekakis }
3296d710ec3SAntheas Kapenekakis
ayaneo_psy_prop_is_writeable(struct power_supply * psy,const struct power_supply_ext * ext,void * data,enum power_supply_property psp)3306d710ec3SAntheas Kapenekakis static int ayaneo_psy_prop_is_writeable(struct power_supply *psy,
3316d710ec3SAntheas Kapenekakis const struct power_supply_ext *ext,
3326d710ec3SAntheas Kapenekakis void *data,
3336d710ec3SAntheas Kapenekakis enum power_supply_property psp)
3346d710ec3SAntheas Kapenekakis {
3356d710ec3SAntheas Kapenekakis return true;
3366d710ec3SAntheas Kapenekakis }
3376d710ec3SAntheas Kapenekakis
3386d710ec3SAntheas Kapenekakis static const enum power_supply_property ayaneo_psy_ext_props[] = {
3396d710ec3SAntheas Kapenekakis POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
3406d710ec3SAntheas Kapenekakis };
3416d710ec3SAntheas Kapenekakis
3426d710ec3SAntheas Kapenekakis static const struct power_supply_ext ayaneo_psy_ext = {
3436d710ec3SAntheas Kapenekakis .name = "ayaneo-charge-control",
3446d710ec3SAntheas Kapenekakis .properties = ayaneo_psy_ext_props,
3456d710ec3SAntheas Kapenekakis .num_properties = ARRAY_SIZE(ayaneo_psy_ext_props),
3466d710ec3SAntheas Kapenekakis .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS,
3476d710ec3SAntheas Kapenekakis .get_property = ayaneo_psy_ext_get_prop,
3486d710ec3SAntheas Kapenekakis .set_property = ayaneo_psy_ext_set_prop,
3496d710ec3SAntheas Kapenekakis .property_is_writeable = ayaneo_psy_prop_is_writeable,
3506d710ec3SAntheas Kapenekakis };
3516d710ec3SAntheas Kapenekakis
ayaneo_add_battery(struct power_supply * battery,struct acpi_battery_hook * hook)3526d710ec3SAntheas Kapenekakis static int ayaneo_add_battery(struct power_supply *battery,
3536d710ec3SAntheas Kapenekakis struct acpi_battery_hook *hook)
3546d710ec3SAntheas Kapenekakis {
3556d710ec3SAntheas Kapenekakis struct ayaneo_ec_platform_data *data =
3566d710ec3SAntheas Kapenekakis container_of(hook, struct ayaneo_ec_platform_data, battery_hook);
3576d710ec3SAntheas Kapenekakis
3586d710ec3SAntheas Kapenekakis return power_supply_register_extension(battery, &ayaneo_psy_ext,
3596d710ec3SAntheas Kapenekakis &data->pdev->dev, NULL);
3606d710ec3SAntheas Kapenekakis }
3616d710ec3SAntheas Kapenekakis
ayaneo_remove_battery(struct power_supply * battery,struct acpi_battery_hook * hook)3626d710ec3SAntheas Kapenekakis static int ayaneo_remove_battery(struct power_supply *battery,
3636d710ec3SAntheas Kapenekakis struct acpi_battery_hook *hook)
3646d710ec3SAntheas Kapenekakis {
3656d710ec3SAntheas Kapenekakis power_supply_unregister_extension(battery, &ayaneo_psy_ext);
3666d710ec3SAntheas Kapenekakis return 0;
3676d710ec3SAntheas Kapenekakis }
3686d710ec3SAntheas Kapenekakis
controller_power_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)369e921a8b4SAntheas Kapenekakis static ssize_t controller_power_store(struct device *dev,
370e921a8b4SAntheas Kapenekakis struct device_attribute *attr,
371e921a8b4SAntheas Kapenekakis const char *buf,
372e921a8b4SAntheas Kapenekakis size_t count)
373e921a8b4SAntheas Kapenekakis {
374e921a8b4SAntheas Kapenekakis bool value;
375e921a8b4SAntheas Kapenekakis int ret;
376e921a8b4SAntheas Kapenekakis
377e921a8b4SAntheas Kapenekakis ret = kstrtobool(buf, &value);
378e921a8b4SAntheas Kapenekakis if (ret)
379e921a8b4SAntheas Kapenekakis return ret;
380e921a8b4SAntheas Kapenekakis
381e921a8b4SAntheas Kapenekakis ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
382e921a8b4SAntheas Kapenekakis if (ret)
383e921a8b4SAntheas Kapenekakis return ret;
384e921a8b4SAntheas Kapenekakis
385e921a8b4SAntheas Kapenekakis return count;
386e921a8b4SAntheas Kapenekakis }
387e921a8b4SAntheas Kapenekakis
controller_power_show(struct device * dev,struct device_attribute * attr,char * buf)388e921a8b4SAntheas Kapenekakis static ssize_t controller_power_show(struct device *dev,
389e921a8b4SAntheas Kapenekakis struct device_attribute *attr,
390e921a8b4SAntheas Kapenekakis char *buf)
391e921a8b4SAntheas Kapenekakis {
392e921a8b4SAntheas Kapenekakis int ret;
393e921a8b4SAntheas Kapenekakis u8 val;
394e921a8b4SAntheas Kapenekakis
395e921a8b4SAntheas Kapenekakis ret = ec_read(AYANEO_POWER_REG, &val);
396e921a8b4SAntheas Kapenekakis if (ret)
397e921a8b4SAntheas Kapenekakis return ret;
398e921a8b4SAntheas Kapenekakis
399e921a8b4SAntheas Kapenekakis return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
400e921a8b4SAntheas Kapenekakis }
401e921a8b4SAntheas Kapenekakis
402e921a8b4SAntheas Kapenekakis static DEVICE_ATTR_RW(controller_power);
403e921a8b4SAntheas Kapenekakis
controller_modules_show(struct device * dev,struct device_attribute * attr,char * buf)404e921a8b4SAntheas Kapenekakis static ssize_t controller_modules_show(struct device *dev,
405e921a8b4SAntheas Kapenekakis struct device_attribute *attr, char *buf)
406e921a8b4SAntheas Kapenekakis {
407e921a8b4SAntheas Kapenekakis u8 unconnected_modules;
408e921a8b4SAntheas Kapenekakis char *out;
409e921a8b4SAntheas Kapenekakis int ret;
410e921a8b4SAntheas Kapenekakis
411e921a8b4SAntheas Kapenekakis ret = ec_read(AYANEO_MODULE_REG, &unconnected_modules);
412e921a8b4SAntheas Kapenekakis if (ret)
413e921a8b4SAntheas Kapenekakis return ret;
414e921a8b4SAntheas Kapenekakis
415e921a8b4SAntheas Kapenekakis switch (~unconnected_modules & AYANEO_MODULE_MASK) {
416e921a8b4SAntheas Kapenekakis case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
417e921a8b4SAntheas Kapenekakis out = "both";
418e921a8b4SAntheas Kapenekakis break;
419e921a8b4SAntheas Kapenekakis case AYANEO_MODULE_LEFT:
420e921a8b4SAntheas Kapenekakis out = "left";
421e921a8b4SAntheas Kapenekakis break;
422e921a8b4SAntheas Kapenekakis case AYANEO_MODULE_RIGHT:
423e921a8b4SAntheas Kapenekakis out = "right";
424e921a8b4SAntheas Kapenekakis break;
425e921a8b4SAntheas Kapenekakis default:
426e921a8b4SAntheas Kapenekakis out = "none";
427e921a8b4SAntheas Kapenekakis break;
428e921a8b4SAntheas Kapenekakis }
429e921a8b4SAntheas Kapenekakis
430e921a8b4SAntheas Kapenekakis return sysfs_emit(buf, "%s\n", out);
431e921a8b4SAntheas Kapenekakis }
432e921a8b4SAntheas Kapenekakis
433e921a8b4SAntheas Kapenekakis static DEVICE_ATTR_RO(controller_modules);
434e921a8b4SAntheas Kapenekakis
435e921a8b4SAntheas Kapenekakis static struct attribute *aya_mm_attrs[] = {
436e921a8b4SAntheas Kapenekakis &dev_attr_controller_power.attr,
437e921a8b4SAntheas Kapenekakis &dev_attr_controller_modules.attr,
438e921a8b4SAntheas Kapenekakis NULL
439e921a8b4SAntheas Kapenekakis };
440e921a8b4SAntheas Kapenekakis
aya_mm_is_visible(struct kobject * kobj,struct attribute * attr,int n)441e921a8b4SAntheas Kapenekakis static umode_t aya_mm_is_visible(struct kobject *kobj,
442e921a8b4SAntheas Kapenekakis struct attribute *attr, int n)
443e921a8b4SAntheas Kapenekakis {
444e921a8b4SAntheas Kapenekakis struct device *dev = kobj_to_dev(kobj);
445e921a8b4SAntheas Kapenekakis struct platform_device *pdev = to_platform_device(dev);
446e921a8b4SAntheas Kapenekakis struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
447e921a8b4SAntheas Kapenekakis
448e921a8b4SAntheas Kapenekakis if (data->quirks->has_magic_modules)
449e921a8b4SAntheas Kapenekakis return attr->mode;
450e921a8b4SAntheas Kapenekakis return 0;
451e921a8b4SAntheas Kapenekakis }
452e921a8b4SAntheas Kapenekakis
453e921a8b4SAntheas Kapenekakis static const struct attribute_group aya_mm_attribute_group = {
454e921a8b4SAntheas Kapenekakis .is_visible = aya_mm_is_visible,
455e921a8b4SAntheas Kapenekakis .attrs = aya_mm_attrs,
456e921a8b4SAntheas Kapenekakis };
457e921a8b4SAntheas Kapenekakis
458e921a8b4SAntheas Kapenekakis static const struct attribute_group *ayaneo_ec_groups[] = {
459e921a8b4SAntheas Kapenekakis &aya_mm_attribute_group,
460e921a8b4SAntheas Kapenekakis NULL
461e921a8b4SAntheas Kapenekakis };
462e921a8b4SAntheas Kapenekakis
ayaneo_ec_probe(struct platform_device * pdev)46370a4a815SAntheas Kapenekakis static int ayaneo_ec_probe(struct platform_device *pdev)
46470a4a815SAntheas Kapenekakis {
46570a4a815SAntheas Kapenekakis const struct dmi_system_id *dmi_entry;
46670a4a815SAntheas Kapenekakis struct ayaneo_ec_platform_data *data;
467536522f0SAntheas Kapenekakis struct device *hwdev;
4686d710ec3SAntheas Kapenekakis int ret;
46970a4a815SAntheas Kapenekakis
47070a4a815SAntheas Kapenekakis dmi_entry = dmi_first_match(dmi_table);
47170a4a815SAntheas Kapenekakis if (!dmi_entry)
47270a4a815SAntheas Kapenekakis return -ENODEV;
47370a4a815SAntheas Kapenekakis
47470a4a815SAntheas Kapenekakis data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
47570a4a815SAntheas Kapenekakis if (!data)
47670a4a815SAntheas Kapenekakis return -ENOMEM;
47770a4a815SAntheas Kapenekakis
47870a4a815SAntheas Kapenekakis data->pdev = pdev;
47970a4a815SAntheas Kapenekakis data->quirks = dmi_entry->driver_data;
480*2643187cSAntheas Kapenekakis ret = devm_mutex_init(&pdev->dev, &data->hwmon_lock);
481*2643187cSAntheas Kapenekakis if (ret)
482*2643187cSAntheas Kapenekakis return ret;
48370a4a815SAntheas Kapenekakis platform_set_drvdata(pdev, data);
48470a4a815SAntheas Kapenekakis
485536522f0SAntheas Kapenekakis if (data->quirks->has_fan_control) {
486536522f0SAntheas Kapenekakis hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
487*2643187cSAntheas Kapenekakis "ayaneo_ec", data, &ayaneo_ec_chip_info, NULL);
488536522f0SAntheas Kapenekakis if (IS_ERR(hwdev))
489536522f0SAntheas Kapenekakis return PTR_ERR(hwdev);
490536522f0SAntheas Kapenekakis }
491536522f0SAntheas Kapenekakis
4926d710ec3SAntheas Kapenekakis if (data->quirks->has_charge_control) {
4936d710ec3SAntheas Kapenekakis data->battery_hook.add_battery = ayaneo_add_battery;
4946d710ec3SAntheas Kapenekakis data->battery_hook.remove_battery = ayaneo_remove_battery;
4956d710ec3SAntheas Kapenekakis data->battery_hook.name = "Ayaneo Battery";
4966d710ec3SAntheas Kapenekakis ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook);
4976d710ec3SAntheas Kapenekakis if (ret)
4986d710ec3SAntheas Kapenekakis return ret;
4996d710ec3SAntheas Kapenekakis }
5006d710ec3SAntheas Kapenekakis
50170a4a815SAntheas Kapenekakis return 0;
50270a4a815SAntheas Kapenekakis }
50370a4a815SAntheas Kapenekakis
ayaneo_freeze(struct device * dev)504*2643187cSAntheas Kapenekakis static int ayaneo_freeze(struct device *dev)
505*2643187cSAntheas Kapenekakis {
506*2643187cSAntheas Kapenekakis struct platform_device *pdev = to_platform_device(dev);
507*2643187cSAntheas Kapenekakis struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
508*2643187cSAntheas Kapenekakis int ret;
509*2643187cSAntheas Kapenekakis u8 tmp;
510*2643187cSAntheas Kapenekakis
511*2643187cSAntheas Kapenekakis if (data->quirks->has_charge_control) {
512*2643187cSAntheas Kapenekakis ret = ec_read(AYANEO_CHARGE_REG, &tmp);
513*2643187cSAntheas Kapenekakis if (ret)
514*2643187cSAntheas Kapenekakis return ret;
515*2643187cSAntheas Kapenekakis
516*2643187cSAntheas Kapenekakis data->restore_charge_limit = tmp == AYANEO_CHARGE_VAL_INHIBIT;
517*2643187cSAntheas Kapenekakis }
518*2643187cSAntheas Kapenekakis
519*2643187cSAntheas Kapenekakis if (data->quirks->has_fan_control) {
520*2643187cSAntheas Kapenekakis ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
521*2643187cSAntheas Kapenekakis if (ret)
522*2643187cSAntheas Kapenekakis return ret;
523*2643187cSAntheas Kapenekakis
524*2643187cSAntheas Kapenekakis data->restore_pwm = tmp == AYANEO_PWM_MODE_MANUAL;
525*2643187cSAntheas Kapenekakis
526*2643187cSAntheas Kapenekakis /*
527*2643187cSAntheas Kapenekakis * Release the fan when entering hibernation to avoid
528*2643187cSAntheas Kapenekakis * overheating if hibernation fails and hangs.
529*2643187cSAntheas Kapenekakis */
530*2643187cSAntheas Kapenekakis if (data->restore_pwm) {
531*2643187cSAntheas Kapenekakis ret = ec_write(AYANEO_PWM_ENABLE_REG, AYANEO_PWM_MODE_AUTO);
532*2643187cSAntheas Kapenekakis if (ret)
533*2643187cSAntheas Kapenekakis return ret;
534*2643187cSAntheas Kapenekakis }
535*2643187cSAntheas Kapenekakis }
536*2643187cSAntheas Kapenekakis
537*2643187cSAntheas Kapenekakis return 0;
538*2643187cSAntheas Kapenekakis }
539*2643187cSAntheas Kapenekakis
ayaneo_restore(struct device * dev)540*2643187cSAntheas Kapenekakis static int ayaneo_restore(struct device *dev)
541*2643187cSAntheas Kapenekakis {
542*2643187cSAntheas Kapenekakis struct platform_device *pdev = to_platform_device(dev);
543*2643187cSAntheas Kapenekakis struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
544*2643187cSAntheas Kapenekakis int ret;
545*2643187cSAntheas Kapenekakis
546*2643187cSAntheas Kapenekakis if (data->quirks->has_charge_control && data->restore_charge_limit) {
547*2643187cSAntheas Kapenekakis ret = ec_write(AYANEO_CHARGE_REG, AYANEO_CHARGE_VAL_INHIBIT);
548*2643187cSAntheas Kapenekakis if (ret)
549*2643187cSAntheas Kapenekakis return ret;
550*2643187cSAntheas Kapenekakis }
551*2643187cSAntheas Kapenekakis
552*2643187cSAntheas Kapenekakis return 0;
553*2643187cSAntheas Kapenekakis }
554*2643187cSAntheas Kapenekakis
555*2643187cSAntheas Kapenekakis static const struct dev_pm_ops ayaneo_pm_ops = {
556*2643187cSAntheas Kapenekakis .freeze = ayaneo_freeze,
557*2643187cSAntheas Kapenekakis .restore = ayaneo_restore,
558*2643187cSAntheas Kapenekakis };
559*2643187cSAntheas Kapenekakis
56070a4a815SAntheas Kapenekakis static struct platform_driver ayaneo_platform_driver = {
56170a4a815SAntheas Kapenekakis .driver = {
56270a4a815SAntheas Kapenekakis .name = "ayaneo-ec",
563e921a8b4SAntheas Kapenekakis .dev_groups = ayaneo_ec_groups,
564*2643187cSAntheas Kapenekakis .pm = pm_sleep_ptr(&ayaneo_pm_ops),
56570a4a815SAntheas Kapenekakis },
56670a4a815SAntheas Kapenekakis .probe = ayaneo_ec_probe,
56770a4a815SAntheas Kapenekakis };
56870a4a815SAntheas Kapenekakis
56970a4a815SAntheas Kapenekakis static struct platform_device *ayaneo_platform_device;
57070a4a815SAntheas Kapenekakis
ayaneo_ec_init(void)57170a4a815SAntheas Kapenekakis static int __init ayaneo_ec_init(void)
57270a4a815SAntheas Kapenekakis {
57370a4a815SAntheas Kapenekakis ayaneo_platform_device =
57470a4a815SAntheas Kapenekakis platform_create_bundle(&ayaneo_platform_driver,
57570a4a815SAntheas Kapenekakis ayaneo_ec_probe, NULL, 0, NULL, 0);
57670a4a815SAntheas Kapenekakis
57770a4a815SAntheas Kapenekakis return PTR_ERR_OR_ZERO(ayaneo_platform_device);
57870a4a815SAntheas Kapenekakis }
57970a4a815SAntheas Kapenekakis
ayaneo_ec_exit(void)58070a4a815SAntheas Kapenekakis static void __exit ayaneo_ec_exit(void)
58170a4a815SAntheas Kapenekakis {
58270a4a815SAntheas Kapenekakis platform_device_unregister(ayaneo_platform_device);
58370a4a815SAntheas Kapenekakis platform_driver_unregister(&ayaneo_platform_driver);
58470a4a815SAntheas Kapenekakis }
58570a4a815SAntheas Kapenekakis
58670a4a815SAntheas Kapenekakis MODULE_DEVICE_TABLE(dmi, dmi_table);
58770a4a815SAntheas Kapenekakis
58870a4a815SAntheas Kapenekakis module_init(ayaneo_ec_init);
58970a4a815SAntheas Kapenekakis module_exit(ayaneo_ec_exit);
59070a4a815SAntheas Kapenekakis
59170a4a815SAntheas Kapenekakis MODULE_AUTHOR("Antheas Kapenekakis <lkml@antheas.dev>");
59270a4a815SAntheas Kapenekakis MODULE_DESCRIPTION("Ayaneo Embedded Controller (EC) platform features");
59370a4a815SAntheas Kapenekakis MODULE_LICENSE("GPL");
594