brintos

brintos / linux-shallow public Read only

0
0
Text · 11.0 KiB · 7cea449 Raw
464 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *  fan_core.c - ACPI Fan core Driver4 *5 *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>6 *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>7 *  Copyright (C) 2022 Intel Corporation. All rights reserved.8 */9 10#include <linux/kernel.h>11#include <linux/module.h>12#include <linux/init.h>13#include <linux/types.h>14#include <linux/uaccess.h>15#include <linux/thermal.h>16#include <linux/acpi.h>17#include <linux/platform_device.h>18#include <linux/sort.h>19 20#include "fan.h"21 22static const struct acpi_device_id fan_device_ids[] = {23	ACPI_FAN_DEVICE_IDS,24	{"", 0},25};26MODULE_DEVICE_TABLE(acpi, fan_device_ids);27 28/* thermal cooling device callbacks */29static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long30			     *state)31{32	struct acpi_device *device = cdev->devdata;33	struct acpi_fan *fan = acpi_driver_data(device);34 35	if (fan->acpi4) {36		if (fan->fif.fine_grain_ctrl)37			*state = 100 / fan->fif.step_size;38		else39			*state = fan->fps_count - 1;40	} else {41		*state = 1;42	}43 44	return 0;45}46 47int acpi_fan_get_fst(struct acpi_device *device, struct acpi_fan_fst *fst)48{49	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };50	union acpi_object *obj;51	acpi_status status;52	int ret = 0;53 54	status = acpi_evaluate_object(device->handle, "_FST", NULL, &buffer);55	if (ACPI_FAILURE(status)) {56		dev_err(&device->dev, "Get fan state failed\n");57		return -ENODEV;58	}59 60	obj = buffer.pointer;61	if (!obj || obj->type != ACPI_TYPE_PACKAGE ||62	    obj->package.count != 3 ||63	    obj->package.elements[1].type != ACPI_TYPE_INTEGER) {64		dev_err(&device->dev, "Invalid _FST data\n");65		ret = -EINVAL;66		goto err;67	}68 69	fst->revision = obj->package.elements[0].integer.value;70	fst->control = obj->package.elements[1].integer.value;71	fst->speed = obj->package.elements[2].integer.value;72 73err:74	kfree(obj);75	return ret;76}77 78static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)79{80	struct acpi_fan *fan = acpi_driver_data(device);81	struct acpi_fan_fst fst;82	int status, i;83 84	status = acpi_fan_get_fst(device, &fst);85	if (status)86		return status;87 88	if (fan->fif.fine_grain_ctrl) {89		/* This control should be same what we set using _FSL by spec */90		if (fst.control > 100) {91			dev_dbg(&device->dev, "Invalid control value returned\n");92			goto match_fps;93		}94 95		*state = (int) fst.control / fan->fif.step_size;96		return 0;97	}98 99match_fps:100	for (i = 0; i < fan->fps_count; i++) {101		if (fst.control == fan->fps[i].control)102			break;103	}104	if (i == fan->fps_count) {105		dev_dbg(&device->dev, "Invalid control value returned\n");106		return -EINVAL;107	}108 109	*state = i;110 111	return status;112}113 114static int fan_get_state(struct acpi_device *device, unsigned long *state)115{116	int result;117	int acpi_state = ACPI_STATE_D0;118 119	result = acpi_device_update_power(device, &acpi_state);120	if (result)121		return result;122 123	*state = acpi_state == ACPI_STATE_D3_COLD124			|| acpi_state == ACPI_STATE_D3_HOT ?125		0 : (acpi_state == ACPI_STATE_D0 ? 1 : -1);126	return 0;127}128 129static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long130			     *state)131{132	struct acpi_device *device = cdev->devdata;133	struct acpi_fan *fan = acpi_driver_data(device);134 135	if (fan->acpi4)136		return fan_get_state_acpi4(device, state);137	else138		return fan_get_state(device, state);139}140 141static int fan_set_state(struct acpi_device *device, unsigned long state)142{143	if (state != 0 && state != 1)144		return -EINVAL;145 146	return acpi_device_set_power(device,147				     state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD);148}149 150static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state)151{152	struct acpi_fan *fan = acpi_driver_data(device);153	acpi_status status;154	u64 value = state;155	int max_state;156 157	if (fan->fif.fine_grain_ctrl)158		max_state = 100 / fan->fif.step_size;159	else160		max_state = fan->fps_count - 1;161 162	if (state > max_state)163		return -EINVAL;164 165	if (fan->fif.fine_grain_ctrl) {166		value *= fan->fif.step_size;167		/* Spec allows compensate the last step only */168		if (value + fan->fif.step_size > 100)169			value = 100;170	} else {171		value = fan->fps[state].control;172	}173 174	status = acpi_execute_simple_method(device->handle, "_FSL", value);175	if (ACPI_FAILURE(status)) {176		dev_dbg(&device->dev, "Failed to set state by _FSL\n");177		return -ENODEV;178	}179 180	return 0;181}182 183static int184fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)185{186	struct acpi_device *device = cdev->devdata;187	struct acpi_fan *fan = acpi_driver_data(device);188 189	if (fan->acpi4)190		return fan_set_state_acpi4(device, state);191	else192		return fan_set_state(device, state);193}194 195static const struct thermal_cooling_device_ops fan_cooling_ops = {196	.get_max_state = fan_get_max_state,197	.get_cur_state = fan_get_cur_state,198	.set_cur_state = fan_set_cur_state,199};200 201/* --------------------------------------------------------------------------202 *                               Driver Interface203 * --------------------------------------------------------------------------204*/205 206static bool acpi_fan_is_acpi4(struct acpi_device *device)207{208	return acpi_has_method(device->handle, "_FIF") &&209	       acpi_has_method(device->handle, "_FPS") &&210	       acpi_has_method(device->handle, "_FSL") &&211	       acpi_has_method(device->handle, "_FST");212}213 214static int acpi_fan_get_fif(struct acpi_device *device)215{216	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };217	struct acpi_fan *fan = acpi_driver_data(device);218	struct acpi_buffer format = { sizeof("NNNN"), "NNNN" };219	u64 fields[4];220	struct acpi_buffer fif = { sizeof(fields), fields };221	union acpi_object *obj;222	acpi_status status;223 224	status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer);225	if (ACPI_FAILURE(status))226		return status;227 228	obj = buffer.pointer;229	if (!obj || obj->type != ACPI_TYPE_PACKAGE) {230		dev_err(&device->dev, "Invalid _FIF data\n");231		status = -EINVAL;232		goto err;233	}234 235	status = acpi_extract_package(obj, &format, &fif);236	if (ACPI_FAILURE(status)) {237		dev_err(&device->dev, "Invalid _FIF element\n");238		status = -EINVAL;239		goto err;240	}241 242	fan->fif.revision = fields[0];243	fan->fif.fine_grain_ctrl = fields[1];244	fan->fif.step_size = fields[2];245	fan->fif.low_speed_notification = fields[3];246 247	/* If there is a bug in step size and set as 0, change to 1 */248	if (!fan->fif.step_size)249		fan->fif.step_size = 1;250	/* If step size > 9, change to 9 (by spec valid values 1-9) */251	else if (fan->fif.step_size > 9)252		fan->fif.step_size = 9;253err:254	kfree(obj);255	return status;256}257 258static int acpi_fan_speed_cmp(const void *a, const void *b)259{260	const struct acpi_fan_fps *fps1 = a;261	const struct acpi_fan_fps *fps2 = b;262	return fps1->speed - fps2->speed;263}264 265static int acpi_fan_get_fps(struct acpi_device *device)266{267	struct acpi_fan *fan = acpi_driver_data(device);268	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };269	union acpi_object *obj;270	acpi_status status;271	int i;272 273	status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer);274	if (ACPI_FAILURE(status))275		return status;276 277	obj = buffer.pointer;278	if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) {279		dev_err(&device->dev, "Invalid _FPS data\n");280		status = -EINVAL;281		goto err;282	}283 284	fan->fps_count = obj->package.count - 1; /* minus revision field */285	fan->fps = devm_kcalloc(&device->dev,286				fan->fps_count, sizeof(struct acpi_fan_fps),287				GFP_KERNEL);288	if (!fan->fps) {289		dev_err(&device->dev, "Not enough memory\n");290		status = -ENOMEM;291		goto err;292	}293	for (i = 0; i < fan->fps_count; i++) {294		struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" };295		struct acpi_buffer fps = { offsetof(struct acpi_fan_fps, name),296						&fan->fps[i] };297		status = acpi_extract_package(&obj->package.elements[i + 1],298					      &format, &fps);299		if (ACPI_FAILURE(status)) {300			dev_err(&device->dev, "Invalid _FPS element\n");301			goto err;302		}303	}304 305	/* sort the state array according to fan speed in increase order */306	sort(fan->fps, fan->fps_count, sizeof(*fan->fps),307	     acpi_fan_speed_cmp, NULL);308 309err:310	kfree(obj);311	return status;312}313 314static int acpi_fan_probe(struct platform_device *pdev)315{316	int result = 0;317	struct thermal_cooling_device *cdev;318	struct acpi_fan *fan;319	struct acpi_device *device = ACPI_COMPANION(&pdev->dev);320	char *name;321 322	fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL);323	if (!fan) {324		dev_err(&device->dev, "No memory for fan\n");325		return -ENOMEM;326	}327	device->driver_data = fan;328	platform_set_drvdata(pdev, fan);329 330	if (acpi_fan_is_acpi4(device)) {331		result = acpi_fan_get_fif(device);332		if (result)333			return result;334 335		result = acpi_fan_get_fps(device);336		if (result)337			return result;338 339		result = devm_acpi_fan_create_hwmon(device);340		if (result)341			return result;342 343		result = acpi_fan_create_attributes(device);344		if (result)345			return result;346 347		fan->acpi4 = true;348	} else {349		result = acpi_device_update_power(device, NULL);350		if (result) {351			dev_err(&device->dev, "Failed to set initial power state\n");352			goto err_end;353		}354	}355 356	if (!strncmp(pdev->name, "PNP0C0B", strlen("PNP0C0B")))357		name = "Fan";358	else359		name = acpi_device_bid(device);360 361	cdev = thermal_cooling_device_register(name, device,362						&fan_cooling_ops);363	if (IS_ERR(cdev)) {364		result = PTR_ERR(cdev);365		goto err_end;366	}367 368	dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id);369 370	fan->cdev = cdev;371	result = sysfs_create_link(&pdev->dev.kobj,372				   &cdev->device.kobj,373				   "thermal_cooling");374	if (result)375		dev_err(&pdev->dev, "Failed to create sysfs link 'thermal_cooling'\n");376 377	result = sysfs_create_link(&cdev->device.kobj,378				   &pdev->dev.kobj,379				   "device");380	if (result) {381		dev_err(&pdev->dev, "Failed to create sysfs link 'device'\n");382		goto err_end;383	}384 385	return 0;386 387err_end:388	if (fan->acpi4)389		acpi_fan_delete_attributes(device);390 391	return result;392}393 394static void acpi_fan_remove(struct platform_device *pdev)395{396	struct acpi_fan *fan = platform_get_drvdata(pdev);397 398	if (fan->acpi4) {399		struct acpi_device *device = ACPI_COMPANION(&pdev->dev);400 401		acpi_fan_delete_attributes(device);402	}403	sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling");404	sysfs_remove_link(&fan->cdev->device.kobj, "device");405	thermal_cooling_device_unregister(fan->cdev);406}407 408#ifdef CONFIG_PM_SLEEP409static int acpi_fan_suspend(struct device *dev)410{411	struct acpi_fan *fan = dev_get_drvdata(dev);412	if (fan->acpi4)413		return 0;414 415	acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0);416 417	return AE_OK;418}419 420static int acpi_fan_resume(struct device *dev)421{422	int result;423	struct acpi_fan *fan = dev_get_drvdata(dev);424 425	if (fan->acpi4)426		return 0;427 428	result = acpi_device_update_power(ACPI_COMPANION(dev), NULL);429	if (result)430		dev_err(dev, "Error updating fan power state\n");431 432	return result;433}434 435static const struct dev_pm_ops acpi_fan_pm = {436	.resume = acpi_fan_resume,437	.freeze = acpi_fan_suspend,438	.thaw = acpi_fan_resume,439	.restore = acpi_fan_resume,440};441#define FAN_PM_OPS_PTR (&acpi_fan_pm)442 443#else444 445#define FAN_PM_OPS_PTR NULL446 447#endif448 449static struct platform_driver acpi_fan_driver = {450	.probe = acpi_fan_probe,451	.remove_new = acpi_fan_remove,452	.driver = {453		.name = "acpi-fan",454		.acpi_match_table = fan_device_ids,455		.pm = FAN_PM_OPS_PTR,456	},457};458 459module_platform_driver(acpi_fan_driver);460 461MODULE_AUTHOR("Paul Diefenbaugh");462MODULE_DESCRIPTION("ACPI Fan Driver");463MODULE_LICENSE("GPL");464