brintos

brintos / linux-shallow public Read only

0
0
Text · 16.9 KiB · b0c0f0f Raw
722 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * INT3400 thermal driver4 *5 * Copyright (C) 2014, Intel Corporation6 * Authors: Zhang Rui <rui.zhang@intel.com>7 */8 9#include <linux/module.h>10#include <linux/platform_device.h>11#include <linux/acpi.h>12#include <linux/thermal.h>13#include "acpi_thermal_rel.h"14 15#define INT3400_THERMAL_TABLE_CHANGED 0x8316#define INT3400_ODVP_CHANGED 0x8817#define INT3400_KEEP_ALIVE 0xA018#define INT3400_FAKE_TEMP (20 * 1000) /* faked temp sensor with 20C */19 20enum int3400_thermal_uuid {21	INT3400_THERMAL_ACTIVE = 0,22	INT3400_THERMAL_PASSIVE_1,23	INT3400_THERMAL_CRITICAL,24	INT3400_THERMAL_ADAPTIVE_PERFORMANCE,25	INT3400_THERMAL_EMERGENCY_CALL_MODE,26	INT3400_THERMAL_PASSIVE_2,27	INT3400_THERMAL_POWER_BOSS,28	INT3400_THERMAL_VIRTUAL_SENSOR,29	INT3400_THERMAL_COOLING_MODE,30	INT3400_THERMAL_HARDWARE_DUTY_CYCLING,31	INT3400_THERMAL_MAXIMUM_UUID,32};33 34static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {35	"3A95C389-E4B8-4629-A526-C52C88626BAE",36	"42A441D6-AE6A-462b-A84B-4A8CE79027D3",37	"97C68AE7-15FA-499c-B8C9-5DA81D606E0A",38	"63BE270F-1C11-48FD-A6F7-3AF253FF3E2D",39	"5349962F-71E6-431D-9AE8-0A635B710AEE",40	"9E04115A-AE87-4D1C-9500-0F3E340BFE75",41	"F5A35014-C209-46A4-993A-EB56DE7530A1",42	"6ED722A7-9240-48A5-B479-31EEF723D7CF",43	"16CAF1B7-DD38-40ED-B1C1-1B8A1913D531",44	"BE84BABF-C4D4-403D-B495-3128FD44dAC1",45};46 47struct odvp_attr;48 49struct int3400_thermal_priv {50	struct acpi_device *adev;51	struct platform_device *pdev;52	struct thermal_zone_device *thermal;53	int art_count;54	struct art *arts;55	int trt_count;56	struct trt *trts;57	u32 uuid_bitmap;58	int rel_misc_dev_res;59	int current_uuid_index;60	char *data_vault;61	int odvp_count;62	int *odvp;63	u32 os_uuid_mask;64	int production_mode;65	struct odvp_attr *odvp_attrs;66};67 68static int evaluate_odvp(struct int3400_thermal_priv *priv);69 70struct odvp_attr {71	int odvp;72	struct int3400_thermal_priv *priv;73	struct device_attribute attr;74};75 76static BIN_ATTR_SIMPLE_RO(data_vault);77 78static struct bin_attribute *data_attributes[] = {79	&bin_attr_data_vault,80	NULL,81};82 83static ssize_t imok_store(struct device *dev, struct device_attribute *attr,84			  const char *buf, size_t count)85{86	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);87	acpi_status status;88	int input, ret;89 90	ret = kstrtouint(buf, 10, &input);91	if (ret)92		return ret;93	status = acpi_execute_simple_method(priv->adev->handle, "IMOK", input);94	if (ACPI_FAILURE(status))95		return -EIO;96 97	return count;98}99 100static DEVICE_ATTR_WO(imok);101 102static struct attribute *imok_attr[] = {103	&dev_attr_imok.attr,104	NULL105};106 107static const struct attribute_group imok_attribute_group = {108	.attrs = imok_attr,109};110 111static const struct attribute_group data_attribute_group = {112	.bin_attrs = data_attributes,113};114 115static ssize_t available_uuids_show(struct device *dev,116				    struct device_attribute *attr,117				    char *buf)118{119	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);120	int i;121	int length = 0;122 123	if (!priv->uuid_bitmap)124		return sprintf(buf, "UNKNOWN\n");125 126	for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {127		if (priv->uuid_bitmap & (1 << i))128			length += sysfs_emit_at(buf, length, "%s\n", int3400_thermal_uuids[i]);129	}130 131	return length;132}133 134static ssize_t current_uuid_show(struct device *dev,135				 struct device_attribute *devattr, char *buf)136{137	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);138	int i, length = 0;139 140	if (priv->current_uuid_index > 0)141		return sprintf(buf, "%s\n",142			       int3400_thermal_uuids[priv->current_uuid_index]);143 144	for (i = 0; i <= INT3400_THERMAL_CRITICAL; i++) {145		if (priv->os_uuid_mask & BIT(i))146			length += sysfs_emit_at(buf, length, "%s\n", int3400_thermal_uuids[i]);147	}148 149	if (length)150		return length;151 152	return sprintf(buf, "INVALID\n");153}154 155static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enable)156{157	u32 ret, buf[2];158	acpi_status status;159	int result = 0;160	struct acpi_osc_context context = {161		.uuid_str = uuid_str,162		.rev = 1,163		.cap.length = 8,164		.cap.pointer = buf,165	};166 167	buf[OSC_QUERY_DWORD] = 0;168	buf[OSC_SUPPORT_DWORD] = *enable;169 170	status = acpi_run_osc(handle, &context);171	if (ACPI_SUCCESS(status)) {172		ret = *((u32 *)(context.ret.pointer + 4));173		if (ret != *enable)174			result = -EPERM;175 176		kfree(context.ret.pointer);177	} else178		result = -EPERM;179 180	return result;181}182 183static int set_os_uuid_mask(struct int3400_thermal_priv *priv, u32 mask)184{185	int cap = 0;186 187	/*188	 * Capability bits:189	 * Bit 0: set to 1 to indicate DPTF is active190	 * Bi1 1: set to 1 to active cooling is supported by user space daemon191	 * Bit 2: set to 1 to passive cooling is supported by user space daemon192	 * Bit 3: set to 1 to critical trip is handled by user space daemon193	 */194	if (mask)195		cap = (priv->os_uuid_mask << 1) | 0x01;196 197	return int3400_thermal_run_osc(priv->adev->handle,198				       "b23ba85d-c8b7-3542-88de-8de2ffcfd698",199				       &cap);200}201 202static ssize_t current_uuid_store(struct device *dev,203				  struct device_attribute *attr,204				  const char *buf, size_t count)205{206	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);207	int ret, i;208 209	for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {210		if (!strncmp(buf, int3400_thermal_uuids[i],211			     sizeof(int3400_thermal_uuids[i]) - 1)) {212			/*213			 * If we have a list of supported UUIDs, make sure214			 * this one is supported.215			 */216			if (priv->uuid_bitmap & BIT(i)) {217				priv->current_uuid_index = i;218				return count;219			}220 221			/*222			 * There is support of only 3 policies via the new223			 * _OSC to inform OS capability:224			 * INT3400_THERMAL_ACTIVE225			 * INT3400_THERMAL_PASSIVE_1226			 * INT3400_THERMAL_CRITICAL227			 */228 229			if (i > INT3400_THERMAL_CRITICAL)230				return -EINVAL;231 232			priv->os_uuid_mask |= BIT(i);233 234			break;235		}236	}237 238	if (priv->os_uuid_mask) {239		ret = set_os_uuid_mask(priv, priv->os_uuid_mask);240		if (ret)241			return ret;242	}243 244	return count;245}246 247static DEVICE_ATTR_RW(current_uuid);248static DEVICE_ATTR_RO(available_uuids);249static struct attribute *uuid_attrs[] = {250	&dev_attr_available_uuids.attr,251	&dev_attr_current_uuid.attr,252	NULL253};254 255static const struct attribute_group uuid_attribute_group = {256	.attrs = uuid_attrs,257	.name = "uuids"258};259 260static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)261{262	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL};263	union acpi_object *obja, *objb;264	int i, j;265	int result = 0;266	acpi_status status;267 268	status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf);269	if (ACPI_FAILURE(status))270		return -ENODEV;271 272	obja = (union acpi_object *)buf.pointer;273	if (obja->type != ACPI_TYPE_PACKAGE) {274		result = -EINVAL;275		goto end;276	}277 278	for (i = 0; i < obja->package.count; i++) {279		objb = &obja->package.elements[i];280		if (objb->type != ACPI_TYPE_BUFFER) {281			result = -EINVAL;282			goto end;283		}284 285		/* UUID must be 16 bytes */286		if (objb->buffer.length != 16) {287			result = -EINVAL;288			goto end;289		}290 291		for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) {292			guid_t guid;293 294			guid_parse(int3400_thermal_uuids[j], &guid);295			if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) {296				priv->uuid_bitmap |= (1 << j);297				break;298			}299		}300	}301 302end:303	kfree(buf.pointer);304	return result;305}306 307static ssize_t production_mode_show(struct device *dev, struct device_attribute *attr,308				     char *buf)309{310	struct int3400_thermal_priv *priv = dev_get_drvdata(dev);311 312	return sysfs_emit(buf, "%d\n", priv->production_mode);313}314 315static DEVICE_ATTR_RO(production_mode);316 317static int production_mode_init(struct int3400_thermal_priv *priv)318{319	unsigned long long mode;320	acpi_status status;321	int ret;322 323	priv->production_mode = -1;324 325	status = acpi_evaluate_integer(priv->adev->handle, "DCFG", NULL, &mode);326	/* If the method is not present, this is not an error */327	if (ACPI_FAILURE(status))328		return 0;329 330	ret = sysfs_create_file(&priv->pdev->dev.kobj, &dev_attr_production_mode.attr);331	if (ret)332		return ret;333 334	priv->production_mode = mode;335 336	return 0;337}338 339static void production_mode_exit(struct int3400_thermal_priv *priv)340{341	if (priv->production_mode >= 0)342		sysfs_remove_file(&priv->pdev->dev.kobj, &dev_attr_production_mode.attr);343}344 345static ssize_t odvp_show(struct device *dev, struct device_attribute *attr,346			 char *buf)347{348	struct odvp_attr *odvp_attr;349 350	odvp_attr = container_of(attr, struct odvp_attr, attr);351 352	return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);353}354 355static void cleanup_odvp(struct int3400_thermal_priv *priv)356{357	int i;358 359	if (priv->odvp_attrs) {360		for (i = 0; i < priv->odvp_count; i++) {361			sysfs_remove_file(&priv->pdev->dev.kobj,362					  &priv->odvp_attrs[i].attr.attr);363			kfree(priv->odvp_attrs[i].attr.attr.name);364		}365		kfree(priv->odvp_attrs);366	}367	kfree(priv->odvp);368	priv->odvp_count = 0;369}370 371static int evaluate_odvp(struct int3400_thermal_priv *priv)372{373	struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL };374	union acpi_object *obj = NULL;375	acpi_status status;376	int i, ret;377 378	status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp);379	if (ACPI_FAILURE(status)) {380		ret = -EINVAL;381		goto out_err;382	}383 384	obj = odvp.pointer;385	if (obj->type != ACPI_TYPE_PACKAGE) {386		ret = -EINVAL;387		goto out_err;388	}389 390	if (priv->odvp == NULL) {391		priv->odvp_count = obj->package.count;392		priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int),393				     GFP_KERNEL);394		if (!priv->odvp) {395			ret = -ENOMEM;396			goto out_err;397		}398	}399 400	if (priv->odvp_attrs == NULL) {401		priv->odvp_attrs = kcalloc(priv->odvp_count,402					   sizeof(struct odvp_attr),403					   GFP_KERNEL);404		if (!priv->odvp_attrs) {405			ret = -ENOMEM;406			goto out_err;407		}408		for (i = 0; i < priv->odvp_count; i++) {409			struct odvp_attr *odvp = &priv->odvp_attrs[i];410 411			sysfs_attr_init(&odvp->attr.attr);412			odvp->priv = priv;413			odvp->odvp = i;414			odvp->attr.attr.name = kasprintf(GFP_KERNEL,415							 "odvp%d", i);416 417			if (!odvp->attr.attr.name) {418				ret = -ENOMEM;419				goto out_err;420			}421			odvp->attr.attr.mode = 0444;422			odvp->attr.show = odvp_show;423			odvp->attr.store = NULL;424			ret = sysfs_create_file(&priv->pdev->dev.kobj,425						&odvp->attr.attr);426			if (ret)427				goto out_err;428		}429	}430 431	for (i = 0; i < obj->package.count; i++) {432		if (obj->package.elements[i].type == ACPI_TYPE_INTEGER)433			priv->odvp[i] = obj->package.elements[i].integer.value;434	}435 436	kfree(obj);437	return 0;438 439out_err:440	cleanup_odvp(priv);441	kfree(obj);442	return ret;443}444 445static void int3400_notify(acpi_handle handle,446			u32 event,447			void *data)448{449	struct int3400_thermal_priv *priv = data;450	struct device *dev;451	char *thermal_prop[5];452	int therm_event;453 454	if (!priv)455		return;456 457	switch (event) {458	case INT3400_THERMAL_TABLE_CHANGED:459		therm_event = THERMAL_TABLE_CHANGED;460		break;461	case INT3400_KEEP_ALIVE:462		therm_event = THERMAL_EVENT_KEEP_ALIVE;463		break;464	case INT3400_ODVP_CHANGED:465		evaluate_odvp(priv);466		therm_event = THERMAL_DEVICE_POWER_CAPABILITY_CHANGED;467		break;468	default:469		/* Ignore unknown notification codes sent to INT3400 device */470		return;471	}472 473	dev = thermal_zone_device(priv->thermal);474 475	thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", thermal_zone_device_type(priv->thermal));476	thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", INT3400_FAKE_TEMP);477	thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");478	thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", therm_event);479	thermal_prop[4] = NULL;480	kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, thermal_prop);481	kfree(thermal_prop[0]);482	kfree(thermal_prop[1]);483	kfree(thermal_prop[2]);484	kfree(thermal_prop[3]);485}486 487static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,488			int *temp)489{490	*temp = INT3400_FAKE_TEMP;491	return 0;492}493 494static int int3400_thermal_change_mode(struct thermal_zone_device *thermal,495				       enum thermal_device_mode mode)496{497	struct int3400_thermal_priv *priv = thermal_zone_device_priv(thermal);498	int result = 0;499	int enabled;500 501	if (!priv)502		return -EINVAL;503 504	enabled = mode == THERMAL_DEVICE_ENABLED;505 506	if (priv->os_uuid_mask) {507		if (!enabled) {508			priv->os_uuid_mask = 0;509			result = set_os_uuid_mask(priv, priv->os_uuid_mask);510		}511		goto eval_odvp;512	}513 514	if (priv->current_uuid_index < 0 ||515	    priv->current_uuid_index >= INT3400_THERMAL_MAXIMUM_UUID)516		return -EINVAL;517 518	result = int3400_thermal_run_osc(priv->adev->handle,519					 int3400_thermal_uuids[priv->current_uuid_index],520					 &enabled);521eval_odvp:522	evaluate_odvp(priv);523 524	return result;525}526 527static struct thermal_zone_device_ops int3400_thermal_ops = {528	.get_temp = int3400_thermal_get_temp,529	.change_mode = int3400_thermal_change_mode,530};531 532static struct thermal_zone_params int3400_thermal_params = {533	.governor_name = "user_space",534	.no_hwmon = true,535};536 537static void int3400_setup_gddv(struct int3400_thermal_priv *priv)538{539	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };540	union acpi_object *obj;541	acpi_status status;542 543	status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL,544				      &buffer);545	if (ACPI_FAILURE(status) || !buffer.length)546		return;547 548	obj = buffer.pointer;549	if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1550	    || obj->package.elements[0].type != ACPI_TYPE_BUFFER)551		goto out_free;552 553	priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,554				   obj->package.elements[0].buffer.length,555				   GFP_KERNEL);556	if (ZERO_OR_NULL_PTR(priv->data_vault))557		goto out_free;558 559	bin_attr_data_vault.private = priv->data_vault;560	bin_attr_data_vault.size = obj->package.elements[0].buffer.length;561out_free:562	kfree(buffer.pointer);563}564 565static int int3400_thermal_probe(struct platform_device *pdev)566{567	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);568	struct int3400_thermal_priv *priv;569	int result;570 571	if (!adev)572		return -ENODEV;573 574	priv = kzalloc(sizeof(*priv), GFP_KERNEL);575	if (!priv)576		return -ENOMEM;577 578	priv->pdev = pdev;579	priv->adev = adev;580 581	result = int3400_thermal_get_uuids(priv);582 583	/* Missing IDSP isn't fatal */584	if (result && result != -ENODEV)585		goto free_priv;586 587	priv->current_uuid_index = -1;588 589	result = acpi_parse_art(priv->adev->handle, &priv->art_count,590				&priv->arts, true);591	if (result)592		dev_dbg(&pdev->dev, "_ART table parsing error\n");593 594	result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,595				&priv->trts, true);596	if (result)597		dev_dbg(&pdev->dev, "_TRT table parsing error\n");598 599	platform_set_drvdata(pdev, priv);600 601	int3400_setup_gddv(priv);602 603	evaluate_odvp(priv);604 605	priv->thermal = thermal_tripless_zone_device_register("INT3400 Thermal", priv,606							      &int3400_thermal_ops,607							      &int3400_thermal_params);608	if (IS_ERR(priv->thermal)) {609		result = PTR_ERR(priv->thermal);610		goto free_art_trt;611	}612 613	priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(614							priv->adev->handle);615 616	result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);617	if (result)618		goto free_rel_misc;619 620	if (acpi_has_method(priv->adev->handle, "IMOK")) {621		result = sysfs_create_group(&pdev->dev.kobj, &imok_attribute_group);622		if (result)623			goto free_imok;624	}625 626	if (!ZERO_OR_NULL_PTR(priv->data_vault)) {627		result = sysfs_create_group(&pdev->dev.kobj,628					    &data_attribute_group);629		if (result)630			goto free_uuid;631	}632 633	result = acpi_install_notify_handler(634			priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,635			(void *)priv);636	if (result)637		goto free_sysfs;638 639	result = production_mode_init(priv);640	if (result)641		goto free_notify;642 643	return 0;644 645free_notify:646	acpi_remove_notify_handler(priv->adev->handle, ACPI_DEVICE_NOTIFY,647				   int3400_notify);648free_sysfs:649	cleanup_odvp(priv);650	if (!ZERO_OR_NULL_PTR(priv->data_vault)) {651		sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);652		kfree(priv->data_vault);653	}654free_uuid:655	sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);656free_imok:657	sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);658free_rel_misc:659	if (!priv->rel_misc_dev_res)660		acpi_thermal_rel_misc_device_remove(priv->adev->handle);661	thermal_zone_device_unregister(priv->thermal);662free_art_trt:663	kfree(priv->trts);664	kfree(priv->arts);665free_priv:666	kfree(priv);667	return result;668}669 670static void int3400_thermal_remove(struct platform_device *pdev)671{672	struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);673 674	production_mode_exit(priv);675 676	acpi_remove_notify_handler(677			priv->adev->handle, ACPI_DEVICE_NOTIFY,678			int3400_notify);679 680	cleanup_odvp(priv);681 682	if (!priv->rel_misc_dev_res)683		acpi_thermal_rel_misc_device_remove(priv->adev->handle);684 685	if (!ZERO_OR_NULL_PTR(priv->data_vault))686		sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);687	sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);688	sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);689	thermal_zone_device_unregister(priv->thermal);690	kfree(priv->data_vault);691	kfree(priv->trts);692	kfree(priv->arts);693	kfree(priv);694}695 696static const struct acpi_device_id int3400_thermal_match[] = {697	{"INT3400", 0},698	{"INTC1040", 0},699	{"INTC1041", 0},700	{"INTC1042", 0},701	{"INTC1068", 0},702	{"INTC10A0", 0},703	{}704};705 706MODULE_DEVICE_TABLE(acpi, int3400_thermal_match);707 708static struct platform_driver int3400_thermal_driver = {709	.probe = int3400_thermal_probe,710	.remove_new = int3400_thermal_remove,711	.driver = {712		   .name = "int3400 thermal",713		   .acpi_match_table = ACPI_PTR(int3400_thermal_match),714		   },715};716 717module_platform_driver(int3400_thermal_driver);718 719MODULE_DESCRIPTION("INT3400 Thermal driver");720MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");721MODULE_LICENSE("GPL");722