xref: /openbmc/linux/drivers/hwmon/oxp-sensors.c (revision 37f665ffa886ce49d1baaca1c3501ce93713b77e)
1ed264e8aSJoaquín Ignacio Aramendía // SPDX-License-Identifier: GPL-2.0+
2ed264e8aSJoaquín Ignacio Aramendía /*
3ebd4bfeeSDerek J. Clark  * Platform driver for OneXPlayer, AOK ZOE, and Aya Neo Handhelds that expose
4ebd4bfeeSDerek J. Clark  * fan reading and control via hwmon sysfs.
5ed264e8aSJoaquín Ignacio Aramendía  *
6ebd4bfeeSDerek J. Clark  * Old OXP boards have the same DMI strings and they are told apart by
7ebd4bfeeSDerek J. Clark  * the boot cpu vendor (Intel/AMD). Currently only AMD boards are
8ebd4bfeeSDerek J. Clark  * supported but the code is made to be simple to add other handheld
9ebd4bfeeSDerek J. Clark  * boards in the future.
103ca0f12aSJoaquín Ignacio Aramendía  * Fan control is provided via pwm interface in the range [0-255].
113ca0f12aSJoaquín Ignacio Aramendía  * Old AMD boards use [0-100] as range in the EC, the written value is
123ca0f12aSJoaquín Ignacio Aramendía  * scaled to accommodate for that. Newer boards like the mini PRO and
133ca0f12aSJoaquín Ignacio Aramendía  * AOK ZOE are not scaled but have the same EC layout.
14ed264e8aSJoaquín Ignacio Aramendía  *
15ed264e8aSJoaquín Ignacio Aramendía  * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
16ed264e8aSJoaquín Ignacio Aramendía  */
17ed264e8aSJoaquín Ignacio Aramendía 
18ed264e8aSJoaquín Ignacio Aramendía #include <linux/acpi.h>
19ed264e8aSJoaquín Ignacio Aramendía #include <linux/dmi.h>
20ed264e8aSJoaquín Ignacio Aramendía #include <linux/hwmon.h>
21ed264e8aSJoaquín Ignacio Aramendía #include <linux/init.h>
22ed264e8aSJoaquín Ignacio Aramendía #include <linux/kernel.h>
23ed264e8aSJoaquín Ignacio Aramendía #include <linux/module.h>
24ed264e8aSJoaquín Ignacio Aramendía #include <linux/platform_device.h>
25ed264e8aSJoaquín Ignacio Aramendía #include <linux/processor.h>
26ed264e8aSJoaquín Ignacio Aramendía 
27ed264e8aSJoaquín Ignacio Aramendía /* Handle ACPI lock mechanism */
28ed264e8aSJoaquín Ignacio Aramendía static u32 oxp_mutex;
29ed264e8aSJoaquín Ignacio Aramendía 
30ed264e8aSJoaquín Ignacio Aramendía #define ACPI_LOCK_DELAY_MS	500
31ed264e8aSJoaquín Ignacio Aramendía 
32ed264e8aSJoaquín Ignacio Aramendía static bool lock_global_acpi_lock(void)
33ed264e8aSJoaquín Ignacio Aramendía {
34ed264e8aSJoaquín Ignacio Aramendía 	return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));
35ed264e8aSJoaquín Ignacio Aramendía }
36ed264e8aSJoaquín Ignacio Aramendía 
37ed264e8aSJoaquín Ignacio Aramendía static bool unlock_global_acpi_lock(void)
38ed264e8aSJoaquín Ignacio Aramendía {
39ed264e8aSJoaquín Ignacio Aramendía 	return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));
40ed264e8aSJoaquín Ignacio Aramendía }
41ed264e8aSJoaquín Ignacio Aramendía 
423ca0f12aSJoaquín Ignacio Aramendía enum oxp_board {
433ca0f12aSJoaquín Ignacio Aramendía 	aok_zoe_a1 = 1,
44f415cb6cSJoaquín Ignacio Aramendía 	aya_neo_2,
45ebd4bfeeSDerek J. Clark 	aya_neo_air,
46ebd4bfeeSDerek J. Clark 	aya_neo_air_pro,
47f415cb6cSJoaquín Ignacio Aramendía 	aya_neo_geek,
483ca0f12aSJoaquín Ignacio Aramendía 	oxp_mini_amd,
49be144ee4SJoaquín Ignacio Aramendía 	oxp_mini_amd_a07,
503ca0f12aSJoaquín Ignacio Aramendía 	oxp_mini_amd_pro,
513ca0f12aSJoaquín Ignacio Aramendía };
523ca0f12aSJoaquín Ignacio Aramendía 
533ca0f12aSJoaquín Ignacio Aramendía static enum oxp_board board;
543ca0f12aSJoaquín Ignacio Aramendía 
55be144ee4SJoaquín Ignacio Aramendía /* Fan reading and PWM */
56ed264e8aSJoaquín Ignacio Aramendía #define OXP_SENSOR_FAN_REG		0x76 /* Fan reading is 2 registers long */
57ed264e8aSJoaquín Ignacio Aramendía #define OXP_SENSOR_PWM_ENABLE_REG	0x4A /* PWM enable is 1 register long */
58ed264e8aSJoaquín Ignacio Aramendía #define OXP_SENSOR_PWM_REG		0x4B /* PWM reading is 1 register long */
59ed264e8aSJoaquín Ignacio Aramendía 
60be144ee4SJoaquín Ignacio Aramendía /* Turbo button takeover function
61be144ee4SJoaquín Ignacio Aramendía  * Older boards have different values and EC registers
62be144ee4SJoaquín Ignacio Aramendía  * for the same function
63be144ee4SJoaquín Ignacio Aramendía  */
64be144ee4SJoaquín Ignacio Aramendía #define OXP_OLD_TURBO_SWITCH_REG	0x1E
65be144ee4SJoaquín Ignacio Aramendía #define OXP_OLD_TURBO_TAKE_VAL		0x01
66be144ee4SJoaquín Ignacio Aramendía #define OXP_OLD_TURBO_RETURN_VAL	0x00
67be144ee4SJoaquín Ignacio Aramendía 
68be144ee4SJoaquín Ignacio Aramendía #define OXP_TURBO_SWITCH_REG		0xF1
69be144ee4SJoaquín Ignacio Aramendía #define OXP_TURBO_TAKE_VAL		0x40
70be144ee4SJoaquín Ignacio Aramendía #define OXP_TURBO_RETURN_VAL		0x00
71be144ee4SJoaquín Ignacio Aramendía 
72ed264e8aSJoaquín Ignacio Aramendía static const struct dmi_system_id dmi_table[] = {
73ed264e8aSJoaquín Ignacio Aramendía 	{
74ed264e8aSJoaquín Ignacio Aramendía 		.matches = {
753ca0f12aSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
763ca0f12aSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),
773ca0f12aSJoaquín Ignacio Aramendía 		},
785d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aok_zoe_a1,
793ca0f12aSJoaquín Ignacio Aramendía 	},
803ca0f12aSJoaquín Ignacio Aramendía 	{
813ca0f12aSJoaquín Ignacio Aramendía 		.matches = {
82ebd4bfeeSDerek J. Clark 			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
83f415cb6cSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
84f415cb6cSJoaquín Ignacio Aramendía 		},
855d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aya_neo_2,
86f415cb6cSJoaquín Ignacio Aramendía 	},
87f415cb6cSJoaquín Ignacio Aramendía 	{
88f415cb6cSJoaquín Ignacio Aramendía 		.matches = {
89f415cb6cSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
90ebd4bfeeSDerek J. Clark 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
91ebd4bfeeSDerek J. Clark 		},
925d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aya_neo_air,
93ebd4bfeeSDerek J. Clark 	},
94ebd4bfeeSDerek J. Clark 	{
95ebd4bfeeSDerek J. Clark 		.matches = {
96ebd4bfeeSDerek J. Clark 			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
97ebd4bfeeSDerek J. Clark 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
98ebd4bfeeSDerek J. Clark 		},
995d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aya_neo_air_pro,
100ebd4bfeeSDerek J. Clark 	},
101ebd4bfeeSDerek J. Clark 	{
102ebd4bfeeSDerek J. Clark 		.matches = {
103f415cb6cSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
104f415cb6cSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "GEEK"),
105f415cb6cSJoaquín Ignacio Aramendía 		},
1065d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aya_neo_geek,
107f415cb6cSJoaquín Ignacio Aramendía 	},
108f415cb6cSJoaquín Ignacio Aramendía 	{
109f415cb6cSJoaquín Ignacio Aramendía 		.matches = {
110ed264e8aSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
111ed264e8aSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
112ed264e8aSJoaquín Ignacio Aramendía 		},
1135d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)oxp_mini_amd,
1143ca0f12aSJoaquín Ignacio Aramendía 	},
1153ca0f12aSJoaquín Ignacio Aramendía 	{
1163ca0f12aSJoaquín Ignacio Aramendía 		.matches = {
1173ca0f12aSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
1187d0c2c61SJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),
1197d0c2c61SJoaquín Ignacio Aramendía 		},
120be144ee4SJoaquín Ignacio Aramendía 		.driver_data = (void *)oxp_mini_amd_a07,
1217d0c2c61SJoaquín Ignacio Aramendía 	},
1227d0c2c61SJoaquín Ignacio Aramendía 	{
1237d0c2c61SJoaquín Ignacio Aramendía 		.matches = {
1247d0c2c61SJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
1253ca0f12aSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
1263ca0f12aSJoaquín Ignacio Aramendía 		},
1275d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)oxp_mini_amd_pro,
128ed264e8aSJoaquín Ignacio Aramendía 	},
129ed264e8aSJoaquín Ignacio Aramendía 	{},
130ed264e8aSJoaquín Ignacio Aramendía };
131ed264e8aSJoaquín Ignacio Aramendía 
132ed264e8aSJoaquín Ignacio Aramendía /* Helper functions to handle EC read/write */
133ed264e8aSJoaquín Ignacio Aramendía static int read_from_ec(u8 reg, int size, long *val)
134ed264e8aSJoaquín Ignacio Aramendía {
135ed264e8aSJoaquín Ignacio Aramendía 	int i;
136ed264e8aSJoaquín Ignacio Aramendía 	int ret;
137ed264e8aSJoaquín Ignacio Aramendía 	u8 buffer;
138ed264e8aSJoaquín Ignacio Aramendía 
139ed264e8aSJoaquín Ignacio Aramendía 	if (!lock_global_acpi_lock())
140ed264e8aSJoaquín Ignacio Aramendía 		return -EBUSY;
141ed264e8aSJoaquín Ignacio Aramendía 
142ed264e8aSJoaquín Ignacio Aramendía 	*val = 0;
143ed264e8aSJoaquín Ignacio Aramendía 	for (i = 0; i < size; i++) {
144ed264e8aSJoaquín Ignacio Aramendía 		ret = ec_read(reg + i, &buffer);
145ed264e8aSJoaquín Ignacio Aramendía 		if (ret)
146ed264e8aSJoaquín Ignacio Aramendía 			return ret;
147ed264e8aSJoaquín Ignacio Aramendía 		*val <<= i * 8;
148ed264e8aSJoaquín Ignacio Aramendía 		*val += buffer;
149ed264e8aSJoaquín Ignacio Aramendía 	}
150ed264e8aSJoaquín Ignacio Aramendía 
151ed264e8aSJoaquín Ignacio Aramendía 	if (!unlock_global_acpi_lock())
152ed264e8aSJoaquín Ignacio Aramendía 		return -EBUSY;
153ed264e8aSJoaquín Ignacio Aramendía 
154ed264e8aSJoaquín Ignacio Aramendía 	return 0;
155ed264e8aSJoaquín Ignacio Aramendía }
156ed264e8aSJoaquín Ignacio Aramendía 
1577590e659SJoaquín Ignacio Aramendía static int write_to_ec(u8 reg, u8 value)
158ed264e8aSJoaquín Ignacio Aramendía {
159ed264e8aSJoaquín Ignacio Aramendía 	int ret;
160ed264e8aSJoaquín Ignacio Aramendía 
161ed264e8aSJoaquín Ignacio Aramendía 	if (!lock_global_acpi_lock())
162ed264e8aSJoaquín Ignacio Aramendía 		return -EBUSY;
163ed264e8aSJoaquín Ignacio Aramendía 
164ed264e8aSJoaquín Ignacio Aramendía 	ret = ec_write(reg, value);
165ed264e8aSJoaquín Ignacio Aramendía 
166ed264e8aSJoaquín Ignacio Aramendía 	if (!unlock_global_acpi_lock())
167ed264e8aSJoaquín Ignacio Aramendía 		return -EBUSY;
168ed264e8aSJoaquín Ignacio Aramendía 
169ed264e8aSJoaquín Ignacio Aramendía 	return ret;
170ed264e8aSJoaquín Ignacio Aramendía }
171ed264e8aSJoaquín Ignacio Aramendía 
172be144ee4SJoaquín Ignacio Aramendía /* Turbo button toggle functions */
173be144ee4SJoaquín Ignacio Aramendía static int tt_toggle_enable(void)
174be144ee4SJoaquín Ignacio Aramendía {
175be144ee4SJoaquín Ignacio Aramendía 	u8 reg;
176be144ee4SJoaquín Ignacio Aramendía 	u8 val;
177be144ee4SJoaquín Ignacio Aramendía 
178be144ee4SJoaquín Ignacio Aramendía 	switch (board) {
179be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_a07:
180be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_OLD_TURBO_SWITCH_REG;
181be144ee4SJoaquín Ignacio Aramendía 		val = OXP_OLD_TURBO_TAKE_VAL;
182be144ee4SJoaquín Ignacio Aramendía 		break;
183be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_pro:
184be144ee4SJoaquín Ignacio Aramendía 	case aok_zoe_a1:
185be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_TURBO_SWITCH_REG;
186be144ee4SJoaquín Ignacio Aramendía 		val = OXP_TURBO_TAKE_VAL;
187be144ee4SJoaquín Ignacio Aramendía 		break;
188be144ee4SJoaquín Ignacio Aramendía 	default:
189be144ee4SJoaquín Ignacio Aramendía 		return -EINVAL;
190be144ee4SJoaquín Ignacio Aramendía 	}
191be144ee4SJoaquín Ignacio Aramendía 	return write_to_ec(reg, val);
192be144ee4SJoaquín Ignacio Aramendía }
193be144ee4SJoaquín Ignacio Aramendía 
194be144ee4SJoaquín Ignacio Aramendía static int tt_toggle_disable(void)
195be144ee4SJoaquín Ignacio Aramendía {
196be144ee4SJoaquín Ignacio Aramendía 	u8 reg;
197be144ee4SJoaquín Ignacio Aramendía 	u8 val;
198be144ee4SJoaquín Ignacio Aramendía 
199be144ee4SJoaquín Ignacio Aramendía 	switch (board) {
200be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_a07:
201be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_OLD_TURBO_SWITCH_REG;
202be144ee4SJoaquín Ignacio Aramendía 		val = OXP_OLD_TURBO_RETURN_VAL;
203be144ee4SJoaquín Ignacio Aramendía 		break;
204be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_pro:
205be144ee4SJoaquín Ignacio Aramendía 	case aok_zoe_a1:
206be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_TURBO_SWITCH_REG;
207be144ee4SJoaquín Ignacio Aramendía 		val = OXP_TURBO_RETURN_VAL;
208be144ee4SJoaquín Ignacio Aramendía 		break;
209be144ee4SJoaquín Ignacio Aramendía 	default:
210be144ee4SJoaquín Ignacio Aramendía 		return -EINVAL;
211be144ee4SJoaquín Ignacio Aramendía 	}
212be144ee4SJoaquín Ignacio Aramendía 	return write_to_ec(reg, val);
213be144ee4SJoaquín Ignacio Aramendía }
214be144ee4SJoaquín Ignacio Aramendía 
215be144ee4SJoaquín Ignacio Aramendía /* Callbacks for turbo toggle attribute */
216be144ee4SJoaquín Ignacio Aramendía static ssize_t tt_toggle_store(struct device *dev,
217be144ee4SJoaquín Ignacio Aramendía 			       struct device_attribute *attr, const char *buf,
218be144ee4SJoaquín Ignacio Aramendía 			       size_t count)
219be144ee4SJoaquín Ignacio Aramendía {
220be144ee4SJoaquín Ignacio Aramendía 	int rval;
221be144ee4SJoaquín Ignacio Aramendía 	bool value;
222be144ee4SJoaquín Ignacio Aramendía 
223be144ee4SJoaquín Ignacio Aramendía 	rval = kstrtobool(buf, &value);
224be144ee4SJoaquín Ignacio Aramendía 	if (rval)
225be144ee4SJoaquín Ignacio Aramendía 		return rval;
226be144ee4SJoaquín Ignacio Aramendía 
227be144ee4SJoaquín Ignacio Aramendía 	if (value) {
228be144ee4SJoaquín Ignacio Aramendía 		rval = tt_toggle_enable();
229be144ee4SJoaquín Ignacio Aramendía 	} else {
230be144ee4SJoaquín Ignacio Aramendía 		rval = tt_toggle_disable();
231*37f665ffSJoaquín Ignacio Aramendía 	}
232be144ee4SJoaquín Ignacio Aramendía 	if (rval)
233be144ee4SJoaquín Ignacio Aramendía 		return rval;
234*37f665ffSJoaquín Ignacio Aramendía 
235be144ee4SJoaquín Ignacio Aramendía 	return count;
236be144ee4SJoaquín Ignacio Aramendía }
237be144ee4SJoaquín Ignacio Aramendía 
238be144ee4SJoaquín Ignacio Aramendía static ssize_t tt_toggle_show(struct device *dev,
239be144ee4SJoaquín Ignacio Aramendía 			      struct device_attribute *attr, char *buf)
240be144ee4SJoaquín Ignacio Aramendía {
241be144ee4SJoaquín Ignacio Aramendía 	int retval;
242be144ee4SJoaquín Ignacio Aramendía 	u8 reg;
243be144ee4SJoaquín Ignacio Aramendía 	long val;
244be144ee4SJoaquín Ignacio Aramendía 
245be144ee4SJoaquín Ignacio Aramendía 	switch (board) {
246be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_a07:
247be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_OLD_TURBO_SWITCH_REG;
248be144ee4SJoaquín Ignacio Aramendía 		break;
249be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_pro:
250be144ee4SJoaquín Ignacio Aramendía 	case aok_zoe_a1:
251be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_TURBO_SWITCH_REG;
252be144ee4SJoaquín Ignacio Aramendía 		break;
253be144ee4SJoaquín Ignacio Aramendía 	default:
254be144ee4SJoaquín Ignacio Aramendía 		return -EINVAL;
255be144ee4SJoaquín Ignacio Aramendía 	}
256be144ee4SJoaquín Ignacio Aramendía 
257be144ee4SJoaquín Ignacio Aramendía 	retval = read_from_ec(reg, 1, &val);
258be144ee4SJoaquín Ignacio Aramendía 	if (retval)
259be144ee4SJoaquín Ignacio Aramendía 		return retval;
260be144ee4SJoaquín Ignacio Aramendía 
261be144ee4SJoaquín Ignacio Aramendía 	return sysfs_emit(buf, "%d\n", !!val);
262be144ee4SJoaquín Ignacio Aramendía }
263be144ee4SJoaquín Ignacio Aramendía 
264be144ee4SJoaquín Ignacio Aramendía static DEVICE_ATTR_RW(tt_toggle);
265be144ee4SJoaquín Ignacio Aramendía 
266be144ee4SJoaquín Ignacio Aramendía /* PWM enable/disable functions */
2677590e659SJoaquín Ignacio Aramendía static int oxp_pwm_enable(void)
268ed264e8aSJoaquín Ignacio Aramendía {
2697590e659SJoaquín Ignacio Aramendía 	return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x01);
270ed264e8aSJoaquín Ignacio Aramendía }
271ed264e8aSJoaquín Ignacio Aramendía 
2727590e659SJoaquín Ignacio Aramendía static int oxp_pwm_disable(void)
273ed264e8aSJoaquín Ignacio Aramendía {
2747590e659SJoaquín Ignacio Aramendía 	return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x00);
275ed264e8aSJoaquín Ignacio Aramendía }
276ed264e8aSJoaquín Ignacio Aramendía 
277ed264e8aSJoaquín Ignacio Aramendía /* Callbacks for hwmon interface */
278ed264e8aSJoaquín Ignacio Aramendía static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
279ed264e8aSJoaquín Ignacio Aramendía 				       enum hwmon_sensor_types type, u32 attr, int channel)
280ed264e8aSJoaquín Ignacio Aramendía {
281ed264e8aSJoaquín Ignacio Aramendía 	switch (type) {
282ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_fan:
283ed264e8aSJoaquín Ignacio Aramendía 		return 0444;
284ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_pwm:
285ed264e8aSJoaquín Ignacio Aramendía 		return 0644;
286ed264e8aSJoaquín Ignacio Aramendía 	default:
287ed264e8aSJoaquín Ignacio Aramendía 		return 0;
288ed264e8aSJoaquín Ignacio Aramendía 	}
289ed264e8aSJoaquín Ignacio Aramendía }
290ed264e8aSJoaquín Ignacio Aramendía 
291ed264e8aSJoaquín Ignacio Aramendía static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
292ed264e8aSJoaquín Ignacio Aramendía 			     u32 attr, int channel, long *val)
293ed264e8aSJoaquín Ignacio Aramendía {
294ed264e8aSJoaquín Ignacio Aramendía 	int ret;
295ed264e8aSJoaquín Ignacio Aramendía 
296ed264e8aSJoaquín Ignacio Aramendía 	switch (type) {
297ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_fan:
298ed264e8aSJoaquín Ignacio Aramendía 		switch (attr) {
299ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_fan_input:
300ed264e8aSJoaquín Ignacio Aramendía 			return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
301ed264e8aSJoaquín Ignacio Aramendía 		default:
302ed264e8aSJoaquín Ignacio Aramendía 			break;
303ed264e8aSJoaquín Ignacio Aramendía 		}
304ed264e8aSJoaquín Ignacio Aramendía 		break;
305ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_pwm:
306ed264e8aSJoaquín Ignacio Aramendía 		switch (attr) {
307ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_pwm_input:
3080cd3ba68SJoaquín Ignacio Aramendía 			ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
309ed264e8aSJoaquín Ignacio Aramendía 			if (ret)
310ed264e8aSJoaquín Ignacio Aramendía 				return ret;
311ebd4bfeeSDerek J. Clark 			switch (board) {
312f415cb6cSJoaquín Ignacio Aramendía 			case aya_neo_2:
313ebd4bfeeSDerek J. Clark 			case aya_neo_air:
314ebd4bfeeSDerek J. Clark 			case aya_neo_air_pro:
315f415cb6cSJoaquín Ignacio Aramendía 			case aya_neo_geek:
316ebd4bfeeSDerek J. Clark 			case oxp_mini_amd:
317be144ee4SJoaquín Ignacio Aramendía 			case oxp_mini_amd_a07:
318ed264e8aSJoaquín Ignacio Aramendía 				*val = (*val * 255) / 100;
319ebd4bfeeSDerek J. Clark 				break;
320ebd4bfeeSDerek J. Clark 			case oxp_mini_amd_pro:
321ebd4bfeeSDerek J. Clark 			case aok_zoe_a1:
322ebd4bfeeSDerek J. Clark 			default:
323ebd4bfeeSDerek J. Clark 				break;
324ebd4bfeeSDerek J. Clark 			}
325ed264e8aSJoaquín Ignacio Aramendía 			return 0;
326ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_pwm_enable:
327ed264e8aSJoaquín Ignacio Aramendía 			return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
328ed264e8aSJoaquín Ignacio Aramendía 		default:
329ed264e8aSJoaquín Ignacio Aramendía 			break;
330ed264e8aSJoaquín Ignacio Aramendía 		}
331ed264e8aSJoaquín Ignacio Aramendía 		break;
332ed264e8aSJoaquín Ignacio Aramendía 	default:
333ed264e8aSJoaquín Ignacio Aramendía 		break;
334ed264e8aSJoaquín Ignacio Aramendía 	}
335ed264e8aSJoaquín Ignacio Aramendía 	return -EOPNOTSUPP;
336ed264e8aSJoaquín Ignacio Aramendía }
337ed264e8aSJoaquín Ignacio Aramendía 
338ed264e8aSJoaquín Ignacio Aramendía static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
339ed264e8aSJoaquín Ignacio Aramendía 			      u32 attr, int channel, long val)
340ed264e8aSJoaquín Ignacio Aramendía {
341ed264e8aSJoaquín Ignacio Aramendía 	switch (type) {
342ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_pwm:
343ed264e8aSJoaquín Ignacio Aramendía 		switch (attr) {
344ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_pwm_enable:
345ed264e8aSJoaquín Ignacio Aramendía 			if (val == 1)
3467590e659SJoaquín Ignacio Aramendía 				return oxp_pwm_enable();
347ed264e8aSJoaquín Ignacio Aramendía 			else if (val == 0)
3487590e659SJoaquín Ignacio Aramendía 				return oxp_pwm_disable();
349ed264e8aSJoaquín Ignacio Aramendía 			return -EINVAL;
350ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_pwm_input:
351ed264e8aSJoaquín Ignacio Aramendía 			if (val < 0 || val > 255)
352ed264e8aSJoaquín Ignacio Aramendía 				return -EINVAL;
353ebd4bfeeSDerek J. Clark 			switch (board) {
354f415cb6cSJoaquín Ignacio Aramendía 			case aya_neo_2:
355ebd4bfeeSDerek J. Clark 			case aya_neo_air:
356ebd4bfeeSDerek J. Clark 			case aya_neo_air_pro:
357f415cb6cSJoaquín Ignacio Aramendía 			case aya_neo_geek:
358ebd4bfeeSDerek J. Clark 			case oxp_mini_amd:
359be144ee4SJoaquín Ignacio Aramendía 			case oxp_mini_amd_a07:
360ed264e8aSJoaquín Ignacio Aramendía 				val = (val * 100) / 255;
361ebd4bfeeSDerek J. Clark 				break;
362ebd4bfeeSDerek J. Clark 			case aok_zoe_a1:
363ebd4bfeeSDerek J. Clark 			case oxp_mini_amd_pro:
364ebd4bfeeSDerek J. Clark 			default:
365ebd4bfeeSDerek J. Clark 				break;
366ebd4bfeeSDerek J. Clark 			}
3677590e659SJoaquín Ignacio Aramendía 			return write_to_ec(OXP_SENSOR_PWM_REG, val);
368ed264e8aSJoaquín Ignacio Aramendía 		default:
369ed264e8aSJoaquín Ignacio Aramendía 			break;
370ed264e8aSJoaquín Ignacio Aramendía 		}
371ed264e8aSJoaquín Ignacio Aramendía 		break;
372ed264e8aSJoaquín Ignacio Aramendía 	default:
373ed264e8aSJoaquín Ignacio Aramendía 		break;
374ed264e8aSJoaquín Ignacio Aramendía 	}
375ed264e8aSJoaquín Ignacio Aramendía 	return -EOPNOTSUPP;
376ed264e8aSJoaquín Ignacio Aramendía }
377ed264e8aSJoaquín Ignacio Aramendía 
378ed264e8aSJoaquín Ignacio Aramendía /* Known sensors in the OXP EC controllers */
379195030d3SKrzysztof Kozlowski static const struct hwmon_channel_info * const oxp_platform_sensors[] = {
380ed264e8aSJoaquín Ignacio Aramendía 	HWMON_CHANNEL_INFO(fan,
381ed264e8aSJoaquín Ignacio Aramendía 			   HWMON_F_INPUT),
382ed264e8aSJoaquín Ignacio Aramendía 	HWMON_CHANNEL_INFO(pwm,
383ed264e8aSJoaquín Ignacio Aramendía 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
384ed264e8aSJoaquín Ignacio Aramendía 	NULL,
385ed264e8aSJoaquín Ignacio Aramendía };
386ed264e8aSJoaquín Ignacio Aramendía 
387be144ee4SJoaquín Ignacio Aramendía static struct attribute *oxp_ec_attrs[] = {
388be144ee4SJoaquín Ignacio Aramendía 	&dev_attr_tt_toggle.attr,
389be144ee4SJoaquín Ignacio Aramendía 	NULL
390be144ee4SJoaquín Ignacio Aramendía };
391be144ee4SJoaquín Ignacio Aramendía 
392be144ee4SJoaquín Ignacio Aramendía ATTRIBUTE_GROUPS(oxp_ec);
393be144ee4SJoaquín Ignacio Aramendía 
394ed264e8aSJoaquín Ignacio Aramendía static const struct hwmon_ops oxp_ec_hwmon_ops = {
395ed264e8aSJoaquín Ignacio Aramendía 	.is_visible = oxp_ec_hwmon_is_visible,
396ed264e8aSJoaquín Ignacio Aramendía 	.read = oxp_platform_read,
397ed264e8aSJoaquín Ignacio Aramendía 	.write = oxp_platform_write,
398ed264e8aSJoaquín Ignacio Aramendía };
399ed264e8aSJoaquín Ignacio Aramendía 
400ed264e8aSJoaquín Ignacio Aramendía static const struct hwmon_chip_info oxp_ec_chip_info = {
401ed264e8aSJoaquín Ignacio Aramendía 	.ops = &oxp_ec_hwmon_ops,
402ed264e8aSJoaquín Ignacio Aramendía 	.info = oxp_platform_sensors,
403ed264e8aSJoaquín Ignacio Aramendía };
404ed264e8aSJoaquín Ignacio Aramendía 
405ed264e8aSJoaquín Ignacio Aramendía /* Initialization logic */
406ed264e8aSJoaquín Ignacio Aramendía static int oxp_platform_probe(struct platform_device *pdev)
407ed264e8aSJoaquín Ignacio Aramendía {
408ed264e8aSJoaquín Ignacio Aramendía 	const struct dmi_system_id *dmi_entry;
409ed264e8aSJoaquín Ignacio Aramendía 	struct device *dev = &pdev->dev;
410ed264e8aSJoaquín Ignacio Aramendía 	struct device *hwdev;
411be144ee4SJoaquín Ignacio Aramendía 	int ret;
412ed264e8aSJoaquín Ignacio Aramendía 
413ed264e8aSJoaquín Ignacio Aramendía 	/*
414ed264e8aSJoaquín Ignacio Aramendía 	 * Have to check for AMD processor here because DMI strings are the
415ebd4bfeeSDerek J. Clark 	 * same between Intel and AMD boards, the only way to tell them apart
416ed264e8aSJoaquín Ignacio Aramendía 	 * is the CPU.
417ed264e8aSJoaquín Ignacio Aramendía 	 * Intel boards seem to have different EC registers and values to
418ed264e8aSJoaquín Ignacio Aramendía 	 * read/write.
419ed264e8aSJoaquín Ignacio Aramendía 	 */
420ed264e8aSJoaquín Ignacio Aramendía 	dmi_entry = dmi_first_match(dmi_table);
421ed264e8aSJoaquín Ignacio Aramendía 	if (!dmi_entry || boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
422ed264e8aSJoaquín Ignacio Aramendía 		return -ENODEV;
423ed264e8aSJoaquín Ignacio Aramendía 
4245d06ec42SJoaquín Ignacio Aramendía 	board = (enum oxp_board)(unsigned long)dmi_entry->driver_data;
4253ca0f12aSJoaquín Ignacio Aramendía 
426be144ee4SJoaquín Ignacio Aramendía 	switch (board) {
427be144ee4SJoaquín Ignacio Aramendía 	case aok_zoe_a1:
428be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_a07:
429be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_pro:
430be144ee4SJoaquín Ignacio Aramendía 		ret = devm_device_add_groups(dev, oxp_ec_groups);
431be144ee4SJoaquín Ignacio Aramendía 		if (ret)
432be144ee4SJoaquín Ignacio Aramendía 			return ret;
433be144ee4SJoaquín Ignacio Aramendía 		break;
434be144ee4SJoaquín Ignacio Aramendía 	default:
435be144ee4SJoaquín Ignacio Aramendía 		break;
436be144ee4SJoaquín Ignacio Aramendía 	}
437be144ee4SJoaquín Ignacio Aramendía 
438ed264e8aSJoaquín Ignacio Aramendía 	hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
439ed264e8aSJoaquín Ignacio Aramendía 						     &oxp_ec_chip_info, NULL);
440ed264e8aSJoaquín Ignacio Aramendía 
441ed264e8aSJoaquín Ignacio Aramendía 	return PTR_ERR_OR_ZERO(hwdev);
442ed264e8aSJoaquín Ignacio Aramendía }
443ed264e8aSJoaquín Ignacio Aramendía 
444ed264e8aSJoaquín Ignacio Aramendía static struct platform_driver oxp_platform_driver = {
445ed264e8aSJoaquín Ignacio Aramendía 	.driver = {
446ed264e8aSJoaquín Ignacio Aramendía 		.name = "oxp-platform",
447ed264e8aSJoaquín Ignacio Aramendía 	},
448ed264e8aSJoaquín Ignacio Aramendía 	.probe = oxp_platform_probe,
449ed264e8aSJoaquín Ignacio Aramendía };
450ed264e8aSJoaquín Ignacio Aramendía 
451ed264e8aSJoaquín Ignacio Aramendía static struct platform_device *oxp_platform_device;
452ed264e8aSJoaquín Ignacio Aramendía 
453ed264e8aSJoaquín Ignacio Aramendía static int __init oxp_platform_init(void)
454ed264e8aSJoaquín Ignacio Aramendía {
455ed264e8aSJoaquín Ignacio Aramendía 	oxp_platform_device =
456ed264e8aSJoaquín Ignacio Aramendía 		platform_create_bundle(&oxp_platform_driver,
457ed264e8aSJoaquín Ignacio Aramendía 				       oxp_platform_probe, NULL, 0, NULL, 0);
458ed264e8aSJoaquín Ignacio Aramendía 
459ed264e8aSJoaquín Ignacio Aramendía 	return PTR_ERR_OR_ZERO(oxp_platform_device);
460ed264e8aSJoaquín Ignacio Aramendía }
461ed264e8aSJoaquín Ignacio Aramendía 
462ed264e8aSJoaquín Ignacio Aramendía static void __exit oxp_platform_exit(void)
463ed264e8aSJoaquín Ignacio Aramendía {
464ed264e8aSJoaquín Ignacio Aramendía 	platform_device_unregister(oxp_platform_device);
465ed264e8aSJoaquín Ignacio Aramendía 	platform_driver_unregister(&oxp_platform_driver);
466ed264e8aSJoaquín Ignacio Aramendía }
467ed264e8aSJoaquín Ignacio Aramendía 
468ed264e8aSJoaquín Ignacio Aramendía MODULE_DEVICE_TABLE(dmi, dmi_table);
469ed264e8aSJoaquín Ignacio Aramendía 
470ed264e8aSJoaquín Ignacio Aramendía module_init(oxp_platform_init);
471ed264e8aSJoaquín Ignacio Aramendía module_exit(oxp_platform_exit);
472ed264e8aSJoaquín Ignacio Aramendía 
473ed264e8aSJoaquín Ignacio Aramendía MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
474ed264e8aSJoaquín Ignacio Aramendía MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
475ed264e8aSJoaquín Ignacio Aramendía MODULE_LICENSE("GPL");
476