18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * INT3406 thermal driver for display participant device 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016, Intel Corporation 68c2ecf20Sopenharmony_ci * Authors: Aaron Lu <aaron.lu@intel.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 118c2ecf20Sopenharmony_ci#include <linux/acpi.h> 128c2ecf20Sopenharmony_ci#include <linux/backlight.h> 138c2ecf20Sopenharmony_ci#include <linux/thermal.h> 148c2ecf20Sopenharmony_ci#include <acpi/video.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistruct int3406_thermal_data { 198c2ecf20Sopenharmony_ci int upper_limit; 208c2ecf20Sopenharmony_ci int lower_limit; 218c2ecf20Sopenharmony_ci acpi_handle handle; 228c2ecf20Sopenharmony_ci struct acpi_video_device_brightness *br; 238c2ecf20Sopenharmony_ci struct backlight_device *raw_bd; 248c2ecf20Sopenharmony_ci struct thermal_cooling_device *cooling_dev; 258c2ecf20Sopenharmony_ci}; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* 288c2ecf20Sopenharmony_ci * According to the ACPI spec, 298c2ecf20Sopenharmony_ci * "Each brightness level is represented by a number between 0 and 100, 308c2ecf20Sopenharmony_ci * and can be thought of as a percentage. For example, 50 can be 50% 318c2ecf20Sopenharmony_ci * power consumption or 50% brightness, as defined by the OEM." 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * As int3406 device uses this value to communicate with the native 348c2ecf20Sopenharmony_ci * graphics driver, we make the assumption that it represents 358c2ecf20Sopenharmony_ci * the percentage of brightness only 368c2ecf20Sopenharmony_ci */ 378c2ecf20Sopenharmony_ci#define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100) 388c2ecf20Sopenharmony_ci#define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness) 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic int 418c2ecf20Sopenharmony_ciint3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev, 428c2ecf20Sopenharmony_ci unsigned long *state) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci struct int3406_thermal_data *d = cooling_dev->devdata; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci *state = d->upper_limit - d->lower_limit; 478c2ecf20Sopenharmony_ci return 0; 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic int 518c2ecf20Sopenharmony_ciint3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev, 528c2ecf20Sopenharmony_ci unsigned long state) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct int3406_thermal_data *d = cooling_dev->devdata; 558c2ecf20Sopenharmony_ci int acpi_level, raw_level; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci if (state > d->upper_limit - d->lower_limit) 588c2ecf20Sopenharmony_ci return -EINVAL; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci acpi_level = d->br->levels[d->upper_limit - state]; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci raw_level = ACPI_TO_RAW(acpi_level, d); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci return backlight_device_set_brightness(d->raw_bd, raw_level); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic int 688c2ecf20Sopenharmony_ciint3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev, 698c2ecf20Sopenharmony_ci unsigned long *state) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci struct int3406_thermal_data *d = cooling_dev->devdata; 728c2ecf20Sopenharmony_ci int acpi_level; 738c2ecf20Sopenharmony_ci int index; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* 788c2ecf20Sopenharmony_ci * There is no 1:1 mapping between the firmware interface level 798c2ecf20Sopenharmony_ci * with the raw interface level, we will have to find one that is 808c2ecf20Sopenharmony_ci * right above it. 818c2ecf20Sopenharmony_ci */ 828c2ecf20Sopenharmony_ci for (index = d->lower_limit; index < d->upper_limit; index++) { 838c2ecf20Sopenharmony_ci if (acpi_level <= d->br->levels[index]) 848c2ecf20Sopenharmony_ci break; 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci *state = d->upper_limit - index; 888c2ecf20Sopenharmony_ci return 0; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic const struct thermal_cooling_device_ops video_cooling_ops = { 928c2ecf20Sopenharmony_ci .get_max_state = int3406_thermal_get_max_state, 938c2ecf20Sopenharmony_ci .get_cur_state = int3406_thermal_get_cur_state, 948c2ecf20Sopenharmony_ci .set_cur_state = int3406_thermal_set_cur_state, 958c2ecf20Sopenharmony_ci}; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic int int3406_thermal_get_index(int *array, int nr, int value) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci int i; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci for (i = 2; i < nr; i++) { 1028c2ecf20Sopenharmony_ci if (array[i] == value) 1038c2ecf20Sopenharmony_ci break; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci return i == nr ? -ENOENT : i; 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic void int3406_thermal_get_limit(struct int3406_thermal_data *d) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci acpi_status status; 1118c2ecf20Sopenharmony_ci unsigned long long lower_limit, upper_limit; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit); 1148c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(status)) 1158c2ecf20Sopenharmony_ci d->lower_limit = int3406_thermal_get_index(d->br->levels, 1168c2ecf20Sopenharmony_ci d->br->count, lower_limit); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit); 1198c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(status)) 1208c2ecf20Sopenharmony_ci d->upper_limit = int3406_thermal_get_index(d->br->levels, 1218c2ecf20Sopenharmony_ci d->br->count, upper_limit); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* lower_limit and upper_limit should be always set */ 1248c2ecf20Sopenharmony_ci d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2; 1258c2ecf20Sopenharmony_ci d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic void int3406_notify(acpi_handle handle, u32 event, void *data) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED) 1318c2ecf20Sopenharmony_ci int3406_thermal_get_limit(data); 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic int int3406_thermal_probe(struct platform_device *pdev) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 1378c2ecf20Sopenharmony_ci struct int3406_thermal_data *d; 1388c2ecf20Sopenharmony_ci struct backlight_device *bd; 1398c2ecf20Sopenharmony_ci int ret; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (!ACPI_HANDLE(&pdev->dev)) 1428c2ecf20Sopenharmony_ci return -ENODEV; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); 1458c2ecf20Sopenharmony_ci if (!d) 1468c2ecf20Sopenharmony_ci return -ENOMEM; 1478c2ecf20Sopenharmony_ci d->handle = ACPI_HANDLE(&pdev->dev); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci bd = backlight_device_get_by_type(BACKLIGHT_RAW); 1508c2ecf20Sopenharmony_ci if (!bd) 1518c2ecf20Sopenharmony_ci return -ENODEV; 1528c2ecf20Sopenharmony_ci d->raw_bd = bd; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br, NULL); 1558c2ecf20Sopenharmony_ci if (ret) 1568c2ecf20Sopenharmony_ci return ret; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci int3406_thermal_get_limit(d); 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev), 1618c2ecf20Sopenharmony_ci d, &video_cooling_ops); 1628c2ecf20Sopenharmony_ci if (IS_ERR(d->cooling_dev)) 1638c2ecf20Sopenharmony_ci goto err; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, 1668c2ecf20Sopenharmony_ci int3406_notify, d); 1678c2ecf20Sopenharmony_ci if (ret) 1688c2ecf20Sopenharmony_ci goto err_cdev; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, d); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci return 0; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cierr_cdev: 1758c2ecf20Sopenharmony_ci thermal_cooling_device_unregister(d->cooling_dev); 1768c2ecf20Sopenharmony_cierr: 1778c2ecf20Sopenharmony_ci kfree(d->br); 1788c2ecf20Sopenharmony_ci return -ENODEV; 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic int int3406_thermal_remove(struct platform_device *pdev) 1828c2ecf20Sopenharmony_ci{ 1838c2ecf20Sopenharmony_ci struct int3406_thermal_data *d = platform_get_drvdata(pdev); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci thermal_cooling_device_unregister(d->cooling_dev); 1868c2ecf20Sopenharmony_ci kfree(d->br); 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic const struct acpi_device_id int3406_thermal_match[] = { 1918c2ecf20Sopenharmony_ci {"INT3406", 0}, 1928c2ecf20Sopenharmony_ci {} 1938c2ecf20Sopenharmony_ci}; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, int3406_thermal_match); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic struct platform_driver int3406_thermal_driver = { 1988c2ecf20Sopenharmony_ci .probe = int3406_thermal_probe, 1998c2ecf20Sopenharmony_ci .remove = int3406_thermal_remove, 2008c2ecf20Sopenharmony_ci .driver = { 2018c2ecf20Sopenharmony_ci .name = "int3406 thermal", 2028c2ecf20Sopenharmony_ci .acpi_match_table = int3406_thermal_match, 2038c2ecf20Sopenharmony_ci }, 2048c2ecf20Sopenharmony_ci}; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cimodule_platform_driver(int3406_thermal_driver); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("INT3406 Thermal driver"); 2098c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 210