Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * Platform driver for OXP Handhelds that expose fan reading and control
  4 * via hwmon sysfs.
  5 *
  6 * Old boards have the same DMI strings and they are told appart by the
  7 * boot cpu vendor (Intel/AMD). Currently only AMD boards are supported
  8 * but the code is made to be simple to add other handheld boards in the
  9 * future.
 10 * Fan control is provided via pwm interface in the range [0-255].
 11 * Old AMD boards use [0-100] as range in the EC, the written value is
 12 * scaled to accommodate for that. Newer boards like the mini PRO and
 13 * AOK ZOE are not scaled but have the same EC layout.
 14 *
 15 * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
 16 */
 17
 18#include <linux/acpi.h>
 19#include <linux/dev_printk.h>
 20#include <linux/dmi.h>
 21#include <linux/hwmon.h>
 22#include <linux/init.h>
 23#include <linux/kernel.h>
 24#include <linux/module.h>
 25#include <linux/platform_device.h>
 26#include <linux/processor.h>
 27
 28/* Handle ACPI lock mechanism */
 29static u32 oxp_mutex;
 30
 31#define ACPI_LOCK_DELAY_MS	500
 32
 33static bool lock_global_acpi_lock(void)
 34{
 35	return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));
 36}
 37
 38static bool unlock_global_acpi_lock(void)
 39{
 40	return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));
 41}
 42
 43enum oxp_board {
 44	aok_zoe_a1 = 1,
 45	oxp_mini_amd,
 46	oxp_mini_amd_pro,
 47};
 48
 49static enum oxp_board board;
 50
 51#define OXP_SENSOR_FAN_REG		0x76 /* Fan reading is 2 registers long */
 52#define OXP_SENSOR_PWM_ENABLE_REG	0x4A /* PWM enable is 1 register long */
 53#define OXP_SENSOR_PWM_REG		0x4B /* PWM reading is 1 register long */
 54
 55static const struct dmi_system_id dmi_table[] = {
 56	{
 57		.matches = {
 58			DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
 59			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),
 60		},
 61		.driver_data = (void *) &(enum oxp_board) {aok_zoe_a1},
 62	},
 63	{
 64		.matches = {
 65			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
 66			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
 67		},
 68		.driver_data = (void *) &(enum oxp_board) {oxp_mini_amd},
 69	},
 70	{
 71		.matches = {
 72			DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
 73			DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
 74		},
 75		.driver_data = (void *) &(enum oxp_board) {oxp_mini_amd_pro},
 76	},
 77	{},
 78};
 79
 80/* Helper functions to handle EC read/write */
 81static int read_from_ec(u8 reg, int size, long *val)
 82{
 83	int i;
 84	int ret;
 85	u8 buffer;
 86
 87	if (!lock_global_acpi_lock())
 88		return -EBUSY;
 89
 90	*val = 0;
 91	for (i = 0; i < size; i++) {
 92		ret = ec_read(reg + i, &buffer);
 93		if (ret)
 94			return ret;
 95		*val <<= i * 8;
 96		*val += buffer;
 97	}
 98
 99	if (!unlock_global_acpi_lock())
100		return -EBUSY;
101
102	return 0;
103}
104
105static int write_to_ec(const struct device *dev, u8 reg, u8 value)
106{
107	int ret;
108
109	if (!lock_global_acpi_lock())
110		return -EBUSY;
111
112	ret = ec_write(reg, value);
113
114	if (!unlock_global_acpi_lock())
115		return -EBUSY;
116
117	return ret;
118}
119
120static int oxp_pwm_enable(const struct device *dev)
121{
122	return write_to_ec(dev, OXP_SENSOR_PWM_ENABLE_REG, 0x01);
123}
124
125static int oxp_pwm_disable(const struct device *dev)
126{
127	return write_to_ec(dev, OXP_SENSOR_PWM_ENABLE_REG, 0x00);
128}
129
130/* Callbacks for hwmon interface */
131static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
132				       enum hwmon_sensor_types type, u32 attr, int channel)
133{
134	switch (type) {
135	case hwmon_fan:
136		return 0444;
137	case hwmon_pwm:
138		return 0644;
139	default:
140		return 0;
141	}
142}
143
144static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
145			     u32 attr, int channel, long *val)
146{
147	int ret;
148
149	switch (type) {
150	case hwmon_fan:
151		switch (attr) {
152		case hwmon_fan_input:
153			return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
154		default:
155			break;
156		}
157		break;
158	case hwmon_pwm:
159		switch (attr) {
160		case hwmon_pwm_input:
161			ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
162			if (ret)
163				return ret;
164			if (board == oxp_mini_amd)
165				*val = (*val * 255) / 100;
166			return 0;
167		case hwmon_pwm_enable:
168			return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
169		default:
170			break;
171		}
172		break;
173	default:
174		break;
175	}
176	return -EOPNOTSUPP;
177}
178
179static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
180			      u32 attr, int channel, long val)
181{
182	switch (type) {
183	case hwmon_pwm:
184		switch (attr) {
185		case hwmon_pwm_enable:
186			if (val == 1)
187				return oxp_pwm_enable(dev);
188			else if (val == 0)
189				return oxp_pwm_disable(dev);
190			return -EINVAL;
191		case hwmon_pwm_input:
192			if (val < 0 || val > 255)
193				return -EINVAL;
194			if (board == oxp_mini_amd)
195				val = (val * 100) / 255;
196			return write_to_ec(dev, OXP_SENSOR_PWM_REG, val);
197		default:
198			break;
199		}
200		break;
201	default:
202		break;
203	}
204	return -EOPNOTSUPP;
205}
206
207/* Known sensors in the OXP EC controllers */
208static const struct hwmon_channel_info *oxp_platform_sensors[] = {
209	HWMON_CHANNEL_INFO(fan,
210			   HWMON_F_INPUT),
211	HWMON_CHANNEL_INFO(pwm,
212			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
213	NULL,
214};
215
216static const struct hwmon_ops oxp_ec_hwmon_ops = {
217	.is_visible = oxp_ec_hwmon_is_visible,
218	.read = oxp_platform_read,
219	.write = oxp_platform_write,
220};
221
222static const struct hwmon_chip_info oxp_ec_chip_info = {
223	.ops = &oxp_ec_hwmon_ops,
224	.info = oxp_platform_sensors,
225};
226
227/* Initialization logic */
228static int oxp_platform_probe(struct platform_device *pdev)
229{
230	const struct dmi_system_id *dmi_entry;
231	struct device *dev = &pdev->dev;
232	struct device *hwdev;
233
234	/*
235	 * Have to check for AMD processor here because DMI strings are the
236	 * same between Intel and AMD boards, the only way to tell them appart
237	 * is the CPU.
238	 * Intel boards seem to have different EC registers and values to
239	 * read/write.
240	 */
241	dmi_entry = dmi_first_match(dmi_table);
242	if (!dmi_entry || boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
243		return -ENODEV;
244
245	board = *((enum oxp_board *) dmi_entry->driver_data);
246
247	hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
248						     &oxp_ec_chip_info, NULL);
249
250	return PTR_ERR_OR_ZERO(hwdev);
251}
252
253static struct platform_driver oxp_platform_driver = {
254	.driver = {
255		.name = "oxp-platform",
256	},
257	.probe = oxp_platform_probe,
258};
259
260static struct platform_device *oxp_platform_device;
261
262static int __init oxp_platform_init(void)
263{
264	oxp_platform_device =
265		platform_create_bundle(&oxp_platform_driver,
266				       oxp_platform_probe, NULL, 0, NULL, 0);
267
268	return PTR_ERR_OR_ZERO(oxp_platform_device);
269}
270
271static void __exit oxp_platform_exit(void)
272{
273	platform_device_unregister(oxp_platform_device);
274	platform_driver_unregister(&oxp_platform_driver);
275}
276
277MODULE_DEVICE_TABLE(dmi, dmi_table);
278
279module_init(oxp_platform_init);
280module_exit(oxp_platform_exit);
281
282MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
283MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
284MODULE_LICENSE("GPL");