162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * INT3406 thermal driver for display participant device 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016, Intel Corporation 662306a36Sopenharmony_ci * Authors: Aaron Lu <aaron.lu@intel.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/platform_device.h> 1162306a36Sopenharmony_ci#include <linux/acpi.h> 1262306a36Sopenharmony_ci#include <linux/backlight.h> 1362306a36Sopenharmony_ci#include <linux/thermal.h> 1462306a36Sopenharmony_ci#include <acpi/video.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistruct int3406_thermal_data { 1962306a36Sopenharmony_ci int upper_limit; 2062306a36Sopenharmony_ci int lower_limit; 2162306a36Sopenharmony_ci acpi_handle handle; 2262306a36Sopenharmony_ci struct acpi_video_device_brightness *br; 2362306a36Sopenharmony_ci struct backlight_device *raw_bd; 2462306a36Sopenharmony_ci struct thermal_cooling_device *cooling_dev; 2562306a36Sopenharmony_ci}; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/* 2862306a36Sopenharmony_ci * According to the ACPI spec, 2962306a36Sopenharmony_ci * "Each brightness level is represented by a number between 0 and 100, 3062306a36Sopenharmony_ci * and can be thought of as a percentage. For example, 50 can be 50% 3162306a36Sopenharmony_ci * power consumption or 50% brightness, as defined by the OEM." 3262306a36Sopenharmony_ci * 3362306a36Sopenharmony_ci * As int3406 device uses this value to communicate with the native 3462306a36Sopenharmony_ci * graphics driver, we make the assumption that it represents 3562306a36Sopenharmony_ci * the percentage of brightness only 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_ci#define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100) 3862306a36Sopenharmony_ci#define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness) 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic int 4162306a36Sopenharmony_ciint3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev, 4262306a36Sopenharmony_ci unsigned long *state) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci struct int3406_thermal_data *d = cooling_dev->devdata; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci *state = d->upper_limit - d->lower_limit; 4762306a36Sopenharmony_ci return 0; 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic int 5162306a36Sopenharmony_ciint3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev, 5262306a36Sopenharmony_ci unsigned long state) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct int3406_thermal_data *d = cooling_dev->devdata; 5562306a36Sopenharmony_ci int acpi_level, raw_level; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (state > d->upper_limit - d->lower_limit) 5862306a36Sopenharmony_ci return -EINVAL; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci acpi_level = d->br->levels[d->upper_limit - state]; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci raw_level = ACPI_TO_RAW(acpi_level, d); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return backlight_device_set_brightness(d->raw_bd, raw_level); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic int 6862306a36Sopenharmony_ciint3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev, 6962306a36Sopenharmony_ci unsigned long *state) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci struct int3406_thermal_data *d = cooling_dev->devdata; 7262306a36Sopenharmony_ci int acpi_level; 7362306a36Sopenharmony_ci int index; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci /* 7862306a36Sopenharmony_ci * There is no 1:1 mapping between the firmware interface level 7962306a36Sopenharmony_ci * with the raw interface level, we will have to find one that is 8062306a36Sopenharmony_ci * right above it. 8162306a36Sopenharmony_ci */ 8262306a36Sopenharmony_ci for (index = d->lower_limit; index < d->upper_limit; index++) { 8362306a36Sopenharmony_ci if (acpi_level <= d->br->levels[index]) 8462306a36Sopenharmony_ci break; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci *state = d->upper_limit - index; 8862306a36Sopenharmony_ci return 0; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic const struct thermal_cooling_device_ops video_cooling_ops = { 9262306a36Sopenharmony_ci .get_max_state = int3406_thermal_get_max_state, 9362306a36Sopenharmony_ci .get_cur_state = int3406_thermal_get_cur_state, 9462306a36Sopenharmony_ci .set_cur_state = int3406_thermal_set_cur_state, 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic int int3406_thermal_get_index(int *array, int nr, int value) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci int i; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci for (i = 2; i < nr; i++) { 10262306a36Sopenharmony_ci if (array[i] == value) 10362306a36Sopenharmony_ci break; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci return i == nr ? -ENOENT : i; 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic void int3406_thermal_get_limit(struct int3406_thermal_data *d) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci acpi_status status; 11162306a36Sopenharmony_ci unsigned long long lower_limit, upper_limit; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit); 11462306a36Sopenharmony_ci if (ACPI_SUCCESS(status)) 11562306a36Sopenharmony_ci d->lower_limit = int3406_thermal_get_index(d->br->levels, 11662306a36Sopenharmony_ci d->br->count, lower_limit); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit); 11962306a36Sopenharmony_ci if (ACPI_SUCCESS(status)) 12062306a36Sopenharmony_ci d->upper_limit = int3406_thermal_get_index(d->br->levels, 12162306a36Sopenharmony_ci d->br->count, upper_limit); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci /* lower_limit and upper_limit should be always set */ 12462306a36Sopenharmony_ci d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2; 12562306a36Sopenharmony_ci d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic void int3406_notify(acpi_handle handle, u32 event, void *data) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED) 13162306a36Sopenharmony_ci int3406_thermal_get_limit(data); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic int int3406_thermal_probe(struct platform_device *pdev) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 13762306a36Sopenharmony_ci struct int3406_thermal_data *d; 13862306a36Sopenharmony_ci struct backlight_device *bd; 13962306a36Sopenharmony_ci int ret; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (!ACPI_HANDLE(&pdev->dev)) 14262306a36Sopenharmony_ci return -ENODEV; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); 14562306a36Sopenharmony_ci if (!d) 14662306a36Sopenharmony_ci return -ENOMEM; 14762306a36Sopenharmony_ci d->handle = ACPI_HANDLE(&pdev->dev); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci bd = backlight_device_get_by_type(BACKLIGHT_RAW); 15062306a36Sopenharmony_ci if (!bd) 15162306a36Sopenharmony_ci return -ENODEV; 15262306a36Sopenharmony_ci d->raw_bd = bd; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br, NULL); 15562306a36Sopenharmony_ci if (ret) 15662306a36Sopenharmony_ci return ret; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci int3406_thermal_get_limit(d); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev), 16162306a36Sopenharmony_ci d, &video_cooling_ops); 16262306a36Sopenharmony_ci if (IS_ERR(d->cooling_dev)) 16362306a36Sopenharmony_ci goto err; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, 16662306a36Sopenharmony_ci int3406_notify, d); 16762306a36Sopenharmony_ci if (ret) 16862306a36Sopenharmony_ci goto err_cdev; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci platform_set_drvdata(pdev, d); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci return 0; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cierr_cdev: 17562306a36Sopenharmony_ci thermal_cooling_device_unregister(d->cooling_dev); 17662306a36Sopenharmony_cierr: 17762306a36Sopenharmony_ci kfree(d->br); 17862306a36Sopenharmony_ci return -ENODEV; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic int int3406_thermal_remove(struct platform_device *pdev) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci struct int3406_thermal_data *d = platform_get_drvdata(pdev); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci thermal_cooling_device_unregister(d->cooling_dev); 18662306a36Sopenharmony_ci kfree(d->br); 18762306a36Sopenharmony_ci return 0; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_cistatic const struct acpi_device_id int3406_thermal_match[] = { 19162306a36Sopenharmony_ci {"INT3406", 0}, 19262306a36Sopenharmony_ci {} 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, int3406_thermal_match); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic struct platform_driver int3406_thermal_driver = { 19862306a36Sopenharmony_ci .probe = int3406_thermal_probe, 19962306a36Sopenharmony_ci .remove = int3406_thermal_remove, 20062306a36Sopenharmony_ci .driver = { 20162306a36Sopenharmony_ci .name = "int3406 thermal", 20262306a36Sopenharmony_ci .acpi_match_table = int3406_thermal_match, 20362306a36Sopenharmony_ci }, 20462306a36Sopenharmony_ci}; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cimodule_platform_driver(int3406_thermal_driver); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ciMODULE_DESCRIPTION("INT3406 Thermal driver"); 20962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 210