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