xref: /openbmc/linux/drivers/hwmon/oxp-sensors.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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 
lock_global_acpi_lock(void)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 
unlock_global_acpi_lock(void)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 = {
824dbbaf8fSJerrod Frost 			DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
834dbbaf8fSJerrod Frost 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"),
844dbbaf8fSJerrod Frost 		},
854dbbaf8fSJerrod Frost 		.driver_data = (void *)aok_zoe_a1,
864dbbaf8fSJerrod Frost 	},
874dbbaf8fSJerrod Frost 	{
884dbbaf8fSJerrod Frost 		.matches = {
89ebd4bfeeSDerek J. Clark 			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
90f415cb6cSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
91f415cb6cSJoaquín Ignacio Aramendía 		},
925d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aya_neo_2,
93f415cb6cSJoaquín Ignacio Aramendía 	},
94f415cb6cSJoaquín Ignacio Aramendía 	{
95f415cb6cSJoaquín Ignacio Aramendía 		.matches = {
96f415cb6cSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
97ebd4bfeeSDerek J. Clark 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
98ebd4bfeeSDerek J. Clark 		},
995d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aya_neo_air,
100ebd4bfeeSDerek J. Clark 	},
101ebd4bfeeSDerek J. Clark 	{
102ebd4bfeeSDerek J. Clark 		.matches = {
103ebd4bfeeSDerek J. Clark 			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
104ebd4bfeeSDerek J. Clark 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
105ebd4bfeeSDerek J. Clark 		},
1065d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aya_neo_air_pro,
107ebd4bfeeSDerek J. Clark 	},
108ebd4bfeeSDerek J. Clark 	{
109ebd4bfeeSDerek J. Clark 		.matches = {
110f415cb6cSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
111f415cb6cSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "GEEK"),
112f415cb6cSJoaquín Ignacio Aramendía 		},
1135d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)aya_neo_geek,
114f415cb6cSJoaquín Ignacio Aramendía 	},
115f415cb6cSJoaquín Ignacio Aramendía 	{
116f415cb6cSJoaquín Ignacio Aramendía 		.matches = {
117ed264e8aSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
118ed264e8aSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
119ed264e8aSJoaquín Ignacio Aramendía 		},
1205d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)oxp_mini_amd,
1213ca0f12aSJoaquín Ignacio Aramendía 	},
1223ca0f12aSJoaquín Ignacio Aramendía 	{
1233ca0f12aSJoaquín Ignacio Aramendía 		.matches = {
1243ca0f12aSJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
1257d0c2c61SJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),
1267d0c2c61SJoaquín Ignacio Aramendía 		},
127be144ee4SJoaquín Ignacio Aramendía 		.driver_data = (void *)oxp_mini_amd_a07,
1287d0c2c61SJoaquín Ignacio Aramendía 	},
1297d0c2c61SJoaquín Ignacio Aramendía 	{
1307d0c2c61SJoaquín Ignacio Aramendía 		.matches = {
1317d0c2c61SJoaquín Ignacio Aramendía 			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
1323ca0f12aSJoaquín Ignacio Aramendía 			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
1333ca0f12aSJoaquín Ignacio Aramendía 		},
1345d06ec42SJoaquín Ignacio Aramendía 		.driver_data = (void *)oxp_mini_amd_pro,
135ed264e8aSJoaquín Ignacio Aramendía 	},
136ed264e8aSJoaquín Ignacio Aramendía 	{},
137ed264e8aSJoaquín Ignacio Aramendía };
138ed264e8aSJoaquín Ignacio Aramendía 
139ed264e8aSJoaquín Ignacio Aramendía /* Helper functions to handle EC read/write */
read_from_ec(u8 reg,int size,long * val)140ed264e8aSJoaquín Ignacio Aramendía static int read_from_ec(u8 reg, int size, long *val)
141ed264e8aSJoaquín Ignacio Aramendía {
142ed264e8aSJoaquín Ignacio Aramendía 	int i;
143ed264e8aSJoaquín Ignacio Aramendía 	int ret;
144ed264e8aSJoaquín Ignacio Aramendía 	u8 buffer;
145ed264e8aSJoaquín Ignacio Aramendía 
146ed264e8aSJoaquín Ignacio Aramendía 	if (!lock_global_acpi_lock())
147ed264e8aSJoaquín Ignacio Aramendía 		return -EBUSY;
148ed264e8aSJoaquín Ignacio Aramendía 
149ed264e8aSJoaquín Ignacio Aramendía 	*val = 0;
150ed264e8aSJoaquín Ignacio Aramendía 	for (i = 0; i < size; i++) {
151ed264e8aSJoaquín Ignacio Aramendía 		ret = ec_read(reg + i, &buffer);
152ed264e8aSJoaquín Ignacio Aramendía 		if (ret)
153ed264e8aSJoaquín Ignacio Aramendía 			return ret;
154ed264e8aSJoaquín Ignacio Aramendía 		*val <<= i * 8;
155ed264e8aSJoaquín Ignacio Aramendía 		*val += buffer;
156ed264e8aSJoaquín Ignacio Aramendía 	}
157ed264e8aSJoaquín Ignacio Aramendía 
158ed264e8aSJoaquín Ignacio Aramendía 	if (!unlock_global_acpi_lock())
159ed264e8aSJoaquín Ignacio Aramendía 		return -EBUSY;
160ed264e8aSJoaquín Ignacio Aramendía 
161ed264e8aSJoaquín Ignacio Aramendía 	return 0;
162ed264e8aSJoaquín Ignacio Aramendía }
163ed264e8aSJoaquín Ignacio Aramendía 
write_to_ec(u8 reg,u8 value)1647590e659SJoaquín Ignacio Aramendía static int write_to_ec(u8 reg, u8 value)
165ed264e8aSJoaquín Ignacio Aramendía {
166ed264e8aSJoaquín Ignacio Aramendía 	int ret;
167ed264e8aSJoaquín Ignacio Aramendía 
168ed264e8aSJoaquín Ignacio Aramendía 	if (!lock_global_acpi_lock())
169ed264e8aSJoaquín Ignacio Aramendía 		return -EBUSY;
170ed264e8aSJoaquín Ignacio Aramendía 
171ed264e8aSJoaquín Ignacio Aramendía 	ret = ec_write(reg, value);
172ed264e8aSJoaquín Ignacio Aramendía 
173ed264e8aSJoaquín Ignacio Aramendía 	if (!unlock_global_acpi_lock())
174ed264e8aSJoaquín Ignacio Aramendía 		return -EBUSY;
175ed264e8aSJoaquín Ignacio Aramendía 
176ed264e8aSJoaquín Ignacio Aramendía 	return ret;
177ed264e8aSJoaquín Ignacio Aramendía }
178ed264e8aSJoaquín Ignacio Aramendía 
179be144ee4SJoaquín Ignacio Aramendía /* Turbo button toggle functions */
tt_toggle_enable(void)180be144ee4SJoaquín Ignacio Aramendía static int tt_toggle_enable(void)
181be144ee4SJoaquín Ignacio Aramendía {
182be144ee4SJoaquín Ignacio Aramendía 	u8 reg;
183be144ee4SJoaquín Ignacio Aramendía 	u8 val;
184be144ee4SJoaquín Ignacio Aramendía 
185be144ee4SJoaquín Ignacio Aramendía 	switch (board) {
186be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_a07:
187be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_OLD_TURBO_SWITCH_REG;
188be144ee4SJoaquín Ignacio Aramendía 		val = OXP_OLD_TURBO_TAKE_VAL;
189be144ee4SJoaquín Ignacio Aramendía 		break;
190be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_pro:
191be144ee4SJoaquín Ignacio Aramendía 	case aok_zoe_a1:
192be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_TURBO_SWITCH_REG;
193be144ee4SJoaquín Ignacio Aramendía 		val = OXP_TURBO_TAKE_VAL;
194be144ee4SJoaquín Ignacio Aramendía 		break;
195be144ee4SJoaquín Ignacio Aramendía 	default:
196be144ee4SJoaquín Ignacio Aramendía 		return -EINVAL;
197be144ee4SJoaquín Ignacio Aramendía 	}
198be144ee4SJoaquín Ignacio Aramendía 	return write_to_ec(reg, val);
199be144ee4SJoaquín Ignacio Aramendía }
200be144ee4SJoaquín Ignacio Aramendía 
tt_toggle_disable(void)201be144ee4SJoaquín Ignacio Aramendía static int tt_toggle_disable(void)
202be144ee4SJoaquín Ignacio Aramendía {
203be144ee4SJoaquín Ignacio Aramendía 	u8 reg;
204be144ee4SJoaquín Ignacio Aramendía 	u8 val;
205be144ee4SJoaquín Ignacio Aramendía 
206be144ee4SJoaquín Ignacio Aramendía 	switch (board) {
207be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_a07:
208be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_OLD_TURBO_SWITCH_REG;
209be144ee4SJoaquín Ignacio Aramendía 		val = OXP_OLD_TURBO_RETURN_VAL;
210be144ee4SJoaquín Ignacio Aramendía 		break;
211be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_pro:
212be144ee4SJoaquín Ignacio Aramendía 	case aok_zoe_a1:
213be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_TURBO_SWITCH_REG;
214be144ee4SJoaquín Ignacio Aramendía 		val = OXP_TURBO_RETURN_VAL;
215be144ee4SJoaquín Ignacio Aramendía 		break;
216be144ee4SJoaquín Ignacio Aramendía 	default:
217be144ee4SJoaquín Ignacio Aramendía 		return -EINVAL;
218be144ee4SJoaquín Ignacio Aramendía 	}
219be144ee4SJoaquín Ignacio Aramendía 	return write_to_ec(reg, val);
220be144ee4SJoaquín Ignacio Aramendía }
221be144ee4SJoaquín Ignacio Aramendía 
222be144ee4SJoaquín Ignacio Aramendía /* Callbacks for turbo toggle attribute */
tt_toggle_is_visible(struct kobject * kobj,struct attribute * attr,int n)223957961b6SJoaquín Ignacio Aramendía static umode_t tt_toggle_is_visible(struct kobject *kobj,
224957961b6SJoaquín Ignacio Aramendía 				    struct attribute *attr, int n)
225957961b6SJoaquín Ignacio Aramendía {
226957961b6SJoaquín Ignacio Aramendía 	switch (board) {
227957961b6SJoaquín Ignacio Aramendía 	case aok_zoe_a1:
228957961b6SJoaquín Ignacio Aramendía 	case oxp_mini_amd_a07:
229957961b6SJoaquín Ignacio Aramendía 	case oxp_mini_amd_pro:
230957961b6SJoaquín Ignacio Aramendía 		return attr->mode;
231957961b6SJoaquín Ignacio Aramendía 	default:
232957961b6SJoaquín Ignacio Aramendía 		break;
233957961b6SJoaquín Ignacio Aramendía 	}
234957961b6SJoaquín Ignacio Aramendía 	return 0;
235957961b6SJoaquín Ignacio Aramendía }
236957961b6SJoaquín Ignacio Aramendía 
tt_toggle_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)237be144ee4SJoaquín Ignacio Aramendía static ssize_t tt_toggle_store(struct device *dev,
238be144ee4SJoaquín Ignacio Aramendía 			       struct device_attribute *attr, const char *buf,
239be144ee4SJoaquín Ignacio Aramendía 			       size_t count)
240be144ee4SJoaquín Ignacio Aramendía {
241be144ee4SJoaquín Ignacio Aramendía 	int rval;
242be144ee4SJoaquín Ignacio Aramendía 	bool value;
243be144ee4SJoaquín Ignacio Aramendía 
244be144ee4SJoaquín Ignacio Aramendía 	rval = kstrtobool(buf, &value);
245be144ee4SJoaquín Ignacio Aramendía 	if (rval)
246be144ee4SJoaquín Ignacio Aramendía 		return rval;
247be144ee4SJoaquín Ignacio Aramendía 
248be144ee4SJoaquín Ignacio Aramendía 	if (value) {
249be144ee4SJoaquín Ignacio Aramendía 		rval = tt_toggle_enable();
250be144ee4SJoaquín Ignacio Aramendía 	} else {
251be144ee4SJoaquín Ignacio Aramendía 		rval = tt_toggle_disable();
25237f665ffSJoaquín Ignacio Aramendía 	}
253be144ee4SJoaquín Ignacio Aramendía 	if (rval)
254be144ee4SJoaquín Ignacio Aramendía 		return rval;
25537f665ffSJoaquín Ignacio Aramendía 
256be144ee4SJoaquín Ignacio Aramendía 	return count;
257be144ee4SJoaquín Ignacio Aramendía }
258be144ee4SJoaquín Ignacio Aramendía 
tt_toggle_show(struct device * dev,struct device_attribute * attr,char * buf)259be144ee4SJoaquín Ignacio Aramendía static ssize_t tt_toggle_show(struct device *dev,
260be144ee4SJoaquín Ignacio Aramendía 			      struct device_attribute *attr, char *buf)
261be144ee4SJoaquín Ignacio Aramendía {
262be144ee4SJoaquín Ignacio Aramendía 	int retval;
263be144ee4SJoaquín Ignacio Aramendía 	u8 reg;
264be144ee4SJoaquín Ignacio Aramendía 	long val;
265be144ee4SJoaquín Ignacio Aramendía 
266be144ee4SJoaquín Ignacio Aramendía 	switch (board) {
267be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_a07:
268be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_OLD_TURBO_SWITCH_REG;
269be144ee4SJoaquín Ignacio Aramendía 		break;
270be144ee4SJoaquín Ignacio Aramendía 	case oxp_mini_amd_pro:
271be144ee4SJoaquín Ignacio Aramendía 	case aok_zoe_a1:
272be144ee4SJoaquín Ignacio Aramendía 		reg = OXP_TURBO_SWITCH_REG;
273be144ee4SJoaquín Ignacio Aramendía 		break;
274be144ee4SJoaquín Ignacio Aramendía 	default:
275be144ee4SJoaquín Ignacio Aramendía 		return -EINVAL;
276be144ee4SJoaquín Ignacio Aramendía 	}
277be144ee4SJoaquín Ignacio Aramendía 
278be144ee4SJoaquín Ignacio Aramendía 	retval = read_from_ec(reg, 1, &val);
279be144ee4SJoaquín Ignacio Aramendía 	if (retval)
280be144ee4SJoaquín Ignacio Aramendía 		return retval;
281be144ee4SJoaquín Ignacio Aramendía 
282be144ee4SJoaquín Ignacio Aramendía 	return sysfs_emit(buf, "%d\n", !!val);
283be144ee4SJoaquín Ignacio Aramendía }
284be144ee4SJoaquín Ignacio Aramendía 
285be144ee4SJoaquín Ignacio Aramendía static DEVICE_ATTR_RW(tt_toggle);
286be144ee4SJoaquín Ignacio Aramendía 
287be144ee4SJoaquín Ignacio Aramendía /* PWM enable/disable functions */
oxp_pwm_enable(void)2887590e659SJoaquín Ignacio Aramendía static int oxp_pwm_enable(void)
289ed264e8aSJoaquín Ignacio Aramendía {
2907590e659SJoaquín Ignacio Aramendía 	return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x01);
291ed264e8aSJoaquín Ignacio Aramendía }
292ed264e8aSJoaquín Ignacio Aramendía 
oxp_pwm_disable(void)2937590e659SJoaquín Ignacio Aramendía static int oxp_pwm_disable(void)
294ed264e8aSJoaquín Ignacio Aramendía {
2957590e659SJoaquín Ignacio Aramendía 	return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x00);
296ed264e8aSJoaquín Ignacio Aramendía }
297ed264e8aSJoaquín Ignacio Aramendía 
298ed264e8aSJoaquín Ignacio Aramendía /* Callbacks for hwmon interface */
oxp_ec_hwmon_is_visible(const void * drvdata,enum hwmon_sensor_types type,u32 attr,int channel)299ed264e8aSJoaquín Ignacio Aramendía static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
300ed264e8aSJoaquín Ignacio Aramendía 				       enum hwmon_sensor_types type, u32 attr, int channel)
301ed264e8aSJoaquín Ignacio Aramendía {
302ed264e8aSJoaquín Ignacio Aramendía 	switch (type) {
303ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_fan:
304ed264e8aSJoaquín Ignacio Aramendía 		return 0444;
305ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_pwm:
306ed264e8aSJoaquín Ignacio Aramendía 		return 0644;
307ed264e8aSJoaquín Ignacio Aramendía 	default:
308ed264e8aSJoaquín Ignacio Aramendía 		return 0;
309ed264e8aSJoaquín Ignacio Aramendía 	}
310ed264e8aSJoaquín Ignacio Aramendía }
311ed264e8aSJoaquín Ignacio Aramendía 
oxp_platform_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)312ed264e8aSJoaquín Ignacio Aramendía static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
313ed264e8aSJoaquín Ignacio Aramendía 			     u32 attr, int channel, long *val)
314ed264e8aSJoaquín Ignacio Aramendía {
315ed264e8aSJoaquín Ignacio Aramendía 	int ret;
316ed264e8aSJoaquín Ignacio Aramendía 
317ed264e8aSJoaquín Ignacio Aramendía 	switch (type) {
318ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_fan:
319ed264e8aSJoaquín Ignacio Aramendía 		switch (attr) {
320ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_fan_input:
321ed264e8aSJoaquín Ignacio Aramendía 			return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
322ed264e8aSJoaquín Ignacio Aramendía 		default:
323ed264e8aSJoaquín Ignacio Aramendía 			break;
324ed264e8aSJoaquín Ignacio Aramendía 		}
325ed264e8aSJoaquín Ignacio Aramendía 		break;
326ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_pwm:
327ed264e8aSJoaquín Ignacio Aramendía 		switch (attr) {
328ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_pwm_input:
3290cd3ba68SJoaquín Ignacio Aramendía 			ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
330ed264e8aSJoaquín Ignacio Aramendía 			if (ret)
331ed264e8aSJoaquín Ignacio Aramendía 				return ret;
332ebd4bfeeSDerek J. Clark 			switch (board) {
333f415cb6cSJoaquín Ignacio Aramendía 			case aya_neo_2:
334ebd4bfeeSDerek J. Clark 			case aya_neo_air:
335ebd4bfeeSDerek J. Clark 			case aya_neo_air_pro:
336f415cb6cSJoaquín Ignacio Aramendía 			case aya_neo_geek:
337ebd4bfeeSDerek J. Clark 			case oxp_mini_amd:
338be144ee4SJoaquín Ignacio Aramendía 			case oxp_mini_amd_a07:
339ed264e8aSJoaquín Ignacio Aramendía 				*val = (*val * 255) / 100;
340ebd4bfeeSDerek J. Clark 				break;
341ebd4bfeeSDerek J. Clark 			case oxp_mini_amd_pro:
342ebd4bfeeSDerek J. Clark 			case aok_zoe_a1:
343ebd4bfeeSDerek J. Clark 			default:
344ebd4bfeeSDerek J. Clark 				break;
345ebd4bfeeSDerek J. Clark 			}
346ed264e8aSJoaquín Ignacio Aramendía 			return 0;
347ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_pwm_enable:
348ed264e8aSJoaquín Ignacio Aramendía 			return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
349ed264e8aSJoaquín Ignacio Aramendía 		default:
350ed264e8aSJoaquín Ignacio Aramendía 			break;
351ed264e8aSJoaquín Ignacio Aramendía 		}
352ed264e8aSJoaquín Ignacio Aramendía 		break;
353ed264e8aSJoaquín Ignacio Aramendía 	default:
354ed264e8aSJoaquín Ignacio Aramendía 		break;
355ed264e8aSJoaquín Ignacio Aramendía 	}
356ed264e8aSJoaquín Ignacio Aramendía 	return -EOPNOTSUPP;
357ed264e8aSJoaquín Ignacio Aramendía }
358ed264e8aSJoaquín Ignacio Aramendía 
oxp_platform_write(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long val)359ed264e8aSJoaquín Ignacio Aramendía static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
360ed264e8aSJoaquín Ignacio Aramendía 			      u32 attr, int channel, long val)
361ed264e8aSJoaquín Ignacio Aramendía {
362ed264e8aSJoaquín Ignacio Aramendía 	switch (type) {
363ed264e8aSJoaquín Ignacio Aramendía 	case hwmon_pwm:
364ed264e8aSJoaquín Ignacio Aramendía 		switch (attr) {
365ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_pwm_enable:
366ed264e8aSJoaquín Ignacio Aramendía 			if (val == 1)
3677590e659SJoaquín Ignacio Aramendía 				return oxp_pwm_enable();
368ed264e8aSJoaquín Ignacio Aramendía 			else if (val == 0)
3697590e659SJoaquín Ignacio Aramendía 				return oxp_pwm_disable();
370ed264e8aSJoaquín Ignacio Aramendía 			return -EINVAL;
371ed264e8aSJoaquín Ignacio Aramendía 		case hwmon_pwm_input:
372ed264e8aSJoaquín Ignacio Aramendía 			if (val < 0 || val > 255)
373ed264e8aSJoaquín Ignacio Aramendía 				return -EINVAL;
374ebd4bfeeSDerek J. Clark 			switch (board) {
375f415cb6cSJoaquín Ignacio Aramendía 			case aya_neo_2:
376ebd4bfeeSDerek J. Clark 			case aya_neo_air:
377ebd4bfeeSDerek J. Clark 			case aya_neo_air_pro:
378f415cb6cSJoaquín Ignacio Aramendía 			case aya_neo_geek:
379ebd4bfeeSDerek J. Clark 			case oxp_mini_amd:
380be144ee4SJoaquín Ignacio Aramendía 			case oxp_mini_amd_a07:
381ed264e8aSJoaquín Ignacio Aramendía 				val = (val * 100) / 255;
382ebd4bfeeSDerek J. Clark 				break;
383ebd4bfeeSDerek J. Clark 			case aok_zoe_a1:
384ebd4bfeeSDerek J. Clark 			case oxp_mini_amd_pro:
385ebd4bfeeSDerek J. Clark 			default:
386ebd4bfeeSDerek J. Clark 				break;
387ebd4bfeeSDerek J. Clark 			}
3887590e659SJoaquín Ignacio Aramendía 			return write_to_ec(OXP_SENSOR_PWM_REG, val);
389ed264e8aSJoaquín Ignacio Aramendía 		default:
390ed264e8aSJoaquín Ignacio Aramendía 			break;
391ed264e8aSJoaquín Ignacio Aramendía 		}
392ed264e8aSJoaquín Ignacio Aramendía 		break;
393ed264e8aSJoaquín Ignacio Aramendía 	default:
394ed264e8aSJoaquín Ignacio Aramendía 		break;
395ed264e8aSJoaquín Ignacio Aramendía 	}
396ed264e8aSJoaquín Ignacio Aramendía 	return -EOPNOTSUPP;
397ed264e8aSJoaquín Ignacio Aramendía }
398ed264e8aSJoaquín Ignacio Aramendía 
399ed264e8aSJoaquín Ignacio Aramendía /* Known sensors in the OXP EC controllers */
400195030d3SKrzysztof Kozlowski static const struct hwmon_channel_info * const oxp_platform_sensors[] = {
401ed264e8aSJoaquín Ignacio Aramendía 	HWMON_CHANNEL_INFO(fan,
402ed264e8aSJoaquín Ignacio Aramendía 			   HWMON_F_INPUT),
403ed264e8aSJoaquín Ignacio Aramendía 	HWMON_CHANNEL_INFO(pwm,
404ed264e8aSJoaquín Ignacio Aramendía 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
405ed264e8aSJoaquín Ignacio Aramendía 	NULL,
406ed264e8aSJoaquín Ignacio Aramendía };
407ed264e8aSJoaquín Ignacio Aramendía 
408be144ee4SJoaquín Ignacio Aramendía static struct attribute *oxp_ec_attrs[] = {
409be144ee4SJoaquín Ignacio Aramendía 	&dev_attr_tt_toggle.attr,
410be144ee4SJoaquín Ignacio Aramendía 	NULL
411be144ee4SJoaquín Ignacio Aramendía };
412be144ee4SJoaquín Ignacio Aramendía 
413957961b6SJoaquín Ignacio Aramendía static struct attribute_group oxp_ec_attribute_group = {
414957961b6SJoaquín Ignacio Aramendía 	.is_visible = tt_toggle_is_visible,
415957961b6SJoaquín Ignacio Aramendía 	.attrs = oxp_ec_attrs,
416957961b6SJoaquín Ignacio Aramendía };
417957961b6SJoaquín Ignacio Aramendía 
418957961b6SJoaquín Ignacio Aramendía static const struct attribute_group *oxp_ec_groups[] = {
419957961b6SJoaquín Ignacio Aramendía 	&oxp_ec_attribute_group,
420957961b6SJoaquín Ignacio Aramendía 	NULL
421957961b6SJoaquín Ignacio Aramendía };
422be144ee4SJoaquín Ignacio Aramendía 
423ed264e8aSJoaquín Ignacio Aramendía static const struct hwmon_ops oxp_ec_hwmon_ops = {
424ed264e8aSJoaquín Ignacio Aramendía 	.is_visible = oxp_ec_hwmon_is_visible,
425ed264e8aSJoaquín Ignacio Aramendía 	.read = oxp_platform_read,
426ed264e8aSJoaquín Ignacio Aramendía 	.write = oxp_platform_write,
427ed264e8aSJoaquín Ignacio Aramendía };
428ed264e8aSJoaquín Ignacio Aramendía 
429ed264e8aSJoaquín Ignacio Aramendía static const struct hwmon_chip_info oxp_ec_chip_info = {
430ed264e8aSJoaquín Ignacio Aramendía 	.ops = &oxp_ec_hwmon_ops,
431ed264e8aSJoaquín Ignacio Aramendía 	.info = oxp_platform_sensors,
432ed264e8aSJoaquín Ignacio Aramendía };
433ed264e8aSJoaquín Ignacio Aramendía 
434ed264e8aSJoaquín Ignacio Aramendía /* Initialization logic */
oxp_platform_probe(struct platform_device * pdev)435ed264e8aSJoaquín Ignacio Aramendía static int oxp_platform_probe(struct platform_device *pdev)
436ed264e8aSJoaquín Ignacio Aramendía {
437ed264e8aSJoaquín Ignacio Aramendía 	struct device *dev = &pdev->dev;
438ed264e8aSJoaquín Ignacio Aramendía 	struct device *hwdev;
439ed264e8aSJoaquí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",
449957961b6SJoaquín Ignacio Aramendía 		.dev_groups = oxp_ec_groups,
450ed264e8aSJoaquín Ignacio Aramendía 	},
451ed264e8aSJoaquín Ignacio Aramendía 	.probe = oxp_platform_probe,
452ed264e8aSJoaquín Ignacio Aramendía };
453ed264e8aSJoaquín Ignacio Aramendía 
454ed264e8aSJoaquín Ignacio Aramendía static struct platform_device *oxp_platform_device;
455ed264e8aSJoaquín Ignacio Aramendía 
oxp_platform_init(void)456ed264e8aSJoaquín Ignacio Aramendía static int __init oxp_platform_init(void)
457ed264e8aSJoaquín Ignacio Aramendía {
458*49ffb5eeSJoaquín Ignacio Aramendía 	const struct dmi_system_id *dmi_entry;
459*49ffb5eeSJoaquín Ignacio Aramendía 
460*49ffb5eeSJoaquín Ignacio Aramendía 	/*
461*49ffb5eeSJoaquín Ignacio Aramendía 	 * Have to check for AMD processor here because DMI strings are the
462*49ffb5eeSJoaquín Ignacio Aramendía 	 * same between Intel and AMD boards, the only way to tell them apart
463*49ffb5eeSJoaquín Ignacio Aramendía 	 * is the CPU.
464*49ffb5eeSJoaquín Ignacio Aramendía 	 * Intel boards seem to have different EC registers and values to
465*49ffb5eeSJoaquín Ignacio Aramendía 	 * read/write.
466*49ffb5eeSJoaquín Ignacio Aramendía 	 */
467*49ffb5eeSJoaquín Ignacio Aramendía 	dmi_entry = dmi_first_match(dmi_table);
468*49ffb5eeSJoaquín Ignacio Aramendía 	if (!dmi_entry || boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
469*49ffb5eeSJoaquín Ignacio Aramendía 		return -ENODEV;
470*49ffb5eeSJoaquín Ignacio Aramendía 
471*49ffb5eeSJoaquín Ignacio Aramendía 	board = (enum oxp_board)(unsigned long)dmi_entry->driver_data;
472*49ffb5eeSJoaquín Ignacio Aramendía 
473ed264e8aSJoaquín Ignacio Aramendía 	oxp_platform_device =
474ed264e8aSJoaquín Ignacio Aramendía 		platform_create_bundle(&oxp_platform_driver,
475ed264e8aSJoaquín Ignacio Aramendía 				       oxp_platform_probe, NULL, 0, NULL, 0);
476ed264e8aSJoaquín Ignacio Aramendía 
477ed264e8aSJoaquín Ignacio Aramendía 	return PTR_ERR_OR_ZERO(oxp_platform_device);
478ed264e8aSJoaquín Ignacio Aramendía }
479ed264e8aSJoaquín Ignacio Aramendía 
oxp_platform_exit(void)480ed264e8aSJoaquín Ignacio Aramendía static void __exit oxp_platform_exit(void)
481ed264e8aSJoaquín Ignacio Aramendía {
482ed264e8aSJoaquín Ignacio Aramendía 	platform_device_unregister(oxp_platform_device);
483ed264e8aSJoaquín Ignacio Aramendía 	platform_driver_unregister(&oxp_platform_driver);
484ed264e8aSJoaquín Ignacio Aramendía }
485ed264e8aSJoaquín Ignacio Aramendía 
486ed264e8aSJoaquín Ignacio Aramendía MODULE_DEVICE_TABLE(dmi, dmi_table);
487ed264e8aSJoaquín Ignacio Aramendía 
488ed264e8aSJoaquín Ignacio Aramendía module_init(oxp_platform_init);
489ed264e8aSJoaquín Ignacio Aramendía module_exit(oxp_platform_exit);
490ed264e8aSJoaquín Ignacio Aramendía 
491ed264e8aSJoaquín Ignacio Aramendía MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
492ed264e8aSJoaquín Ignacio Aramendía MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
493ed264e8aSJoaquín Ignacio Aramendía MODULE_LICENSE("GPL");
494