xref: /linux/drivers/platform/x86/ayaneo-ec.c (revision 9d588a1140b9ae211581a7a154d0b806d8cd8238)
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