File Index
Files and directories
Sign in
FILE

alienware-wmi-base.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/dell/alienware-wmi-base.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Alienware special feature control
  4 *
  5 * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com>
  6 * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com>
  7 */
  8
  9#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 10
 11#include <linux/acpi.h>
 12#include <linux/cleanup.h>
 13#include <linux/module.h>
 14#include <linux/platform_device.h>
 15#include <linux/dmi.h>
 16#include <linux/leds.h>
 17#include "alienware-wmi.h"
 18
 19MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
 20MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>");
 21MODULE_DESCRIPTION("Alienware special feature control");
 22MODULE_LICENSE("GPL");
 23
 24struct alienfx_quirks *alienfx;
 25
 26static struct alienfx_quirks quirk_inspiron5675 = {
 27	.num_zones = 2,
 28	.hdmi_mux = false,
 29	.amplifier = false,
 30	.deepslp = false,
 31};
 32
 33static struct alienfx_quirks quirk_unknown = {
 34	.num_zones = 2,
 35	.hdmi_mux = false,
 36	.amplifier = false,
 37	.deepslp = false,
 38};
 39
 40static struct alienfx_quirks quirk_x51_r1_r2 = {
 41	.num_zones = 3,
 42	.hdmi_mux = false,
 43	.amplifier = false,
 44	.deepslp = false,
 45};
 46
 47static struct alienfx_quirks quirk_x51_r3 = {
 48	.num_zones = 4,
 49	.hdmi_mux = false,
 50	.amplifier = true,
 51	.deepslp = false,
 52};
 53
 54static struct alienfx_quirks quirk_asm100 = {
 55	.num_zones = 2,
 56	.hdmi_mux = true,
 57	.amplifier = false,
 58	.deepslp = false,
 59};
 60
 61static struct alienfx_quirks quirk_asm200 = {
 62	.num_zones = 2,
 63	.hdmi_mux = true,
 64	.amplifier = false,
 65	.deepslp = true,
 66};
 67
 68static struct alienfx_quirks quirk_asm201 = {
 69	.num_zones = 2,
 70	.hdmi_mux = true,
 71	.amplifier = true,
 72	.deepslp = true,
 73};
 74
 75static int __init dmi_matched(const struct dmi_system_id *dmi)
 76{
 77	alienfx = dmi->driver_data;
 78	return 1;
 79}
 80
 81static const struct dmi_system_id alienware_quirks[] __initconst = {
 82	{
 83		.callback = dmi_matched,
 84		.ident = "Alienware ASM100",
 85		.matches = {
 86			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
 87			DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
 88		},
 89		.driver_data = &quirk_asm100,
 90	},
 91	{
 92		.callback = dmi_matched,
 93		.ident = "Alienware ASM200",
 94		.matches = {
 95			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
 96			DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),
 97		},
 98		.driver_data = &quirk_asm200,
 99	},
100	{
101		.callback = dmi_matched,
102		.ident = "Alienware ASM201",
103		.matches = {
104			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
105			DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),
106		},
107		.driver_data = &quirk_asm201,
108	},
109	{
110		.callback = dmi_matched,
111		.ident = "Alienware X51 R1",
112		.matches = {
113			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
114			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
115		},
116		.driver_data = &quirk_x51_r1_r2,
117	},
118	{
119		.callback = dmi_matched,
120		.ident = "Alienware X51 R2",
121		.matches = {
122			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
123			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
124		},
125		.driver_data = &quirk_x51_r1_r2,
126	},
127	{
128		.callback = dmi_matched,
129		.ident = "Alienware X51 R3",
130		.matches = {
131			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
132			DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
133		},
134		.driver_data = &quirk_x51_r3,
135	},
136	{
137		.callback = dmi_matched,
138		.ident = "Dell Inc. Inspiron 5675",
139		.matches = {
140			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
141			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"),
142		},
143		.driver_data = &quirk_inspiron5675,
144	},
145	{}
146};
147
148u8 alienware_interface;
149
150int alienware_wmi_command(struct wmi_device *wdev, u32 method_id,
151			  void *in_args, size_t in_size, u32 *out_data)
152{
153	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
154	struct acpi_buffer in = {in_size, in_args};
155	acpi_status ret;
156
157	ret = wmidev_evaluate_method(wdev, 0, method_id, &in, out_data ? &out : NULL);
158	if (ACPI_FAILURE(ret))
159		return -EIO;
160
161	union acpi_object *obj __free(kfree) = out.pointer;
162
163	if (out_data) {
164		if (obj && obj->type == ACPI_TYPE_INTEGER)
165			*out_data = (u32)obj->integer.value;
166		else
167			return -ENOMSG;
168	}
169
170	return 0;
171}
172
173/*
174 * Helpers used for zone control
175 */
176static int parse_rgb(const char *buf, struct color_platform *colors)
177{
178	long unsigned int rgb;
179	int ret;
180	union color_union {
181		struct color_platform cp;
182		int package;
183	} repackager;
184
185	ret = kstrtoul(buf, 16, &rgb);
186	if (ret)
187		return ret;
188
189	/* RGB triplet notation is 24-bit hexadecimal */
190	if (rgb > 0xFFFFFF)
191		return -EINVAL;
192
193	repackager.package = rgb & 0x0f0f0f0f;
194	pr_debug("alienware-wmi: r: %d g:%d b: %d\n",
195		 repackager.cp.red, repackager.cp.green, repackager.cp.blue);
196	*colors = repackager.cp;
197	return 0;
198}
199
200/*
201 * Individual RGB zone control
202 */
203static ssize_t zone_show(struct device *dev, struct device_attribute *attr,
204			 char *buf, u8 location)
205{
206	struct alienfx_priv *priv = dev_get_drvdata(dev);
207	struct color_platform *colors = &priv->colors[location];
208
209	return sprintf(buf, "red: %d, green: %d, blue: %d\n",
210		       colors->red, colors->green, colors->blue);
211
212}
213
214static ssize_t zone_store(struct device *dev, struct device_attribute *attr,
215			  const char *buf, size_t count, u8 location)
216{
217	struct alienfx_priv *priv = dev_get_drvdata(dev);
218	struct color_platform *colors = &priv->colors[location];
219	struct alienfx_platdata *pdata = dev_get_platdata(dev);
220	int ret;
221
222	ret = parse_rgb(buf, colors);
223	if (ret)
224		return ret;
225
226	ret = pdata->ops.upd_led(priv, pdata->wdev, location);
227
228	return ret ? ret : count;
229}
230
231static ssize_t zone00_show(struct device *dev, struct device_attribute *attr,
232			   char *buf)
233{
234	return zone_show(dev, attr, buf, 0);
235}
236
237static ssize_t zone00_store(struct device *dev, struct device_attribute *attr,
238			    const char *buf, size_t count)
239{
240	return zone_store(dev, attr, buf, count, 0);
241}
242
243static DEVICE_ATTR_RW(zone00);
244
245static ssize_t zone01_show(struct device *dev, struct device_attribute *attr,
246			   char *buf)
247{
248	return zone_show(dev, attr, buf, 1);
249}
250
251static ssize_t zone01_store(struct device *dev, struct device_attribute *attr,
252			    const char *buf, size_t count)
253{
254	return zone_store(dev, attr, buf, count, 1);
255}
256
257static DEVICE_ATTR_RW(zone01);
258
259static ssize_t zone02_show(struct device *dev, struct device_attribute *attr,
260			   char *buf)
261{
262	return zone_show(dev, attr, buf, 2);
263}
264
265static ssize_t zone02_store(struct device *dev, struct device_attribute *attr,
266			    const char *buf, size_t count)
267{
268	return zone_store(dev, attr, buf, count, 2);
269}
270
271static DEVICE_ATTR_RW(zone02);
272
273static ssize_t zone03_show(struct device *dev, struct device_attribute *attr,
274			   char *buf)
275{
276	return zone_show(dev, attr, buf, 3);
277}
278
279static ssize_t zone03_store(struct device *dev, struct device_attribute *attr,
280			    const char *buf, size_t count)
281{
282	return zone_store(dev, attr, buf, count, 3);
283}
284
285static DEVICE_ATTR_RW(zone03);
286
287/*
288 * Lighting control state device attribute (Global)
289 */
290static ssize_t lighting_control_state_show(struct device *dev,
291					   struct device_attribute *attr,
292					   char *buf)
293{
294	struct alienfx_priv *priv = dev_get_drvdata(dev);
295
296	if (priv->lighting_control_state == LEGACY_BOOTING)
297		return sysfs_emit(buf, "[booting] running suspend\n");
298	else if (priv->lighting_control_state == LEGACY_SUSPEND)
299		return sysfs_emit(buf, "booting running [suspend]\n");
300
301	return sysfs_emit(buf, "booting [running] suspend\n");
302}
303
304static ssize_t lighting_control_state_store(struct device *dev,
305					    struct device_attribute *attr,
306					    const char *buf, size_t count)
307{
308	struct alienfx_priv *priv = dev_get_drvdata(dev);
309	u8 val;
310
311	if (strcmp(buf, "booting\n") == 0)
312		val = LEGACY_BOOTING;
313	else if (strcmp(buf, "suspend\n") == 0)
314		val = LEGACY_SUSPEND;
315	else if (alienware_interface == LEGACY)
316		val = LEGACY_RUNNING;
317	else
318		val = WMAX_RUNNING;
319
320	priv->lighting_control_state = val;
321	pr_debug("alienware-wmi: updated control state to %d\n",
322		 priv->lighting_control_state);
323
324	return count;
325}
326
327static DEVICE_ATTR_RW(lighting_control_state);
328
329static umode_t zone_attr_visible(struct kobject *kobj,
330				 struct attribute *attr, int n)
331{
332	if (n < alienfx->num_zones + 1)
333		return attr->mode;
334
335	return 0;
336}
337
338static bool zone_group_visible(struct kobject *kobj)
339{
340	return alienfx->num_zones > 0;
341}
342DEFINE_SYSFS_GROUP_VISIBLE(zone);
343
344static struct attribute *zone_attrs[] = {
345	&dev_attr_lighting_control_state.attr,
346	&dev_attr_zone00.attr,
347	&dev_attr_zone01.attr,
348	&dev_attr_zone02.attr,
349	&dev_attr_zone03.attr,
350	NULL
351};
352
353static struct attribute_group zone_attribute_group = {
354	.name = "rgb_zones",
355	.is_visible = SYSFS_GROUP_VISIBLE(zone),
356	.attrs = zone_attrs,
357};
358
359/*
360 * LED Brightness (Global)
361 */
362static void global_led_set(struct led_classdev *led_cdev,
363			   enum led_brightness brightness)
364{
365	struct alienfx_priv *priv = container_of(led_cdev, struct alienfx_priv,
366						 global_led);
367	struct alienfx_platdata *pdata = dev_get_platdata(&priv->pdev->dev);
368	int ret;
369
370	priv->global_brightness = brightness;
371
372	ret = pdata->ops.upd_brightness(priv, pdata->wdev, brightness);
373	if (ret)
374		pr_err("LED brightness update failed\n");
375}
376
377static enum led_brightness global_led_get(struct led_classdev *led_cdev)
378{
379	struct alienfx_priv *priv = container_of(led_cdev, struct alienfx_priv,
380						 global_led);
381
382	return priv->global_brightness;
383}
384
385/*
386 * Platform Driver
387 */
388static int alienfx_probe(struct platform_device *pdev)
389{
390	struct alienfx_priv *priv;
391
392	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
393	if (!priv)
394		return -ENOMEM;
395
396	if (alienware_interface == WMAX)
397		priv->lighting_control_state = WMAX_RUNNING;
398	else
399		priv->lighting_control_state = LEGACY_RUNNING;
400
401	priv->pdev = pdev;
402	priv->global_led.name = "alienware::global_brightness";
403	priv->global_led.brightness_set = global_led_set;
404	priv->global_led.brightness_get = global_led_get;
405	priv->global_led.max_brightness = 0x0F;
406	priv->global_brightness = priv->global_led.max_brightness;
407	platform_set_drvdata(pdev, priv);
408
409	return devm_led_classdev_register(&pdev->dev, &priv->global_led);
410}
411
412static const struct attribute_group *alienfx_groups[] = {
413	&zone_attribute_group,
414	WMAX_DEV_GROUPS
415	NULL
416};
417
418static struct platform_driver platform_driver = {
419	.driver = {
420		.name = "alienware-wmi",
421		.dev_groups = alienfx_groups,
422	},
423	.probe = alienfx_probe,
424};
425
426static void alienware_alienfx_remove(void *data)
427{
428	struct platform_device *pdev = data;
429
430	platform_device_unregister(pdev);
431}
432
433int alienware_alienfx_setup(struct alienfx_platdata *pdata)
434{
435	struct device *dev = &pdata->wdev->dev;
436	struct platform_device *pdev;
437	int ret;
438
439	pdev = platform_device_register_data(NULL, "alienware-wmi",
440					     PLATFORM_DEVID_NONE, pdata,
441					     sizeof(*pdata));
442	if (IS_ERR(pdev))
443		return PTR_ERR(pdev);
444
445	dev_set_drvdata(dev, pdev);
446	ret = devm_add_action_or_reset(dev, alienware_alienfx_remove, pdev);
447	if (ret)
448		return ret;
449
450	return 0;
451}
452
453static int __init alienware_wmi_init(void)
454{
455	int ret;
456
457	dmi_check_system(alienware_quirks);
458	if (!alienfx)
459		alienfx = &quirk_unknown;
460
461	ret = platform_driver_register(&platform_driver);
462	if (ret < 0)
463		return ret;
464
465	if (wmi_has_guid(WMAX_CONTROL_GUID)) {
466		alienware_interface = WMAX;
467		ret = alienware_wmax_wmi_init();
468	} else {
469		alienware_interface = LEGACY;
470		ret = alienware_legacy_wmi_init();
471	}
472
473	if (ret < 0)
474		platform_driver_unregister(&platform_driver);
475
476	return ret;
477}
478
479module_init(alienware_wmi_init);
480
481static void __exit alienware_wmi_exit(void)
482{
483	if (alienware_interface == WMAX)
484		alienware_wmax_wmi_exit();
485	else
486		alienware_legacy_wmi_exit();
487
488	platform_driver_unregister(&platform_driver);
489}
490
491module_exit(alienware_wmi_exit);
~/ Read-only file preview.