1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * INT3406 thermal driver for display participant device 4 * 5 * Copyright (C) 2016, Intel Corporation 6 * Authors: Aaron Lu <aaron.lu@intel.com> 7 */ 8 9 #include <linux/module.h> 10 #include <linux/platform_device.h> 11 #include <linux/acpi.h> 12 #include <linux/backlight.h> 13 #include <linux/thermal.h> 14 #include <acpi/video.h> 15 16 #define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80 17 18 struct int3406_thermal_data { 19 int upper_limit; 20 int lower_limit; 21 acpi_handle handle; 22 struct acpi_video_device_brightness *br; 23 struct backlight_device *raw_bd; 24 struct thermal_cooling_device *cooling_dev; 25 }; 26 27 /* 28 * According to the ACPI spec, 29 * "Each brightness level is represented by a number between 0 and 100, 30 * and can be thought of as a percentage. For example, 50 can be 50% 31 * power consumption or 50% brightness, as defined by the OEM." 32 * 33 * As int3406 device uses this value to communicate with the native 34 * graphics driver, we make the assumption that it represents 35 * the percentage of brightness only 36 */ 37 #define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100) 38 #define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness) 39 40 static int 41 int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev, 42 unsigned long *state) 43 { 44 struct int3406_thermal_data *d = cooling_dev->devdata; 45 46 *state = d->upper_limit - d->lower_limit; 47 return 0; 48 } 49 50 static int 51 int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev, 52 unsigned long state) 53 { 54 struct int3406_thermal_data *d = cooling_dev->devdata; 55 int acpi_level, raw_level; 56 57 if (state > d->upper_limit - d->lower_limit) 58 return -EINVAL; 59 60 acpi_level = d->br->levels[d->upper_limit - state]; 61 62 raw_level = ACPI_TO_RAW(acpi_level, d); 63 64 return backlight_device_set_brightness(d->raw_bd, raw_level); 65 } 66 67 static int 68 int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev, 69 unsigned long *state) 70 { 71 struct int3406_thermal_data *d = cooling_dev->devdata; 72 int acpi_level; 73 int index; 74 75 acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d); 76 77 /* 78 * There is no 1:1 mapping between the firmware interface level 79 * with the raw interface level, we will have to find one that is 80 * right above it. 81 */ 82 for (index = d->lower_limit; index < d->upper_limit; index++) { 83 if (acpi_level <= d->br->levels[index]) 84 break; 85 } 86 87 *state = d->upper_limit - index; 88 return 0; 89 } 90 91 static const struct thermal_cooling_device_ops video_cooling_ops = { 92 .get_max_state = int3406_thermal_get_max_state, 93 .get_cur_state = int3406_thermal_get_cur_state, 94 .set_cur_state = int3406_thermal_set_cur_state, 95 }; 96 97 static int int3406_thermal_get_index(int *array, int nr, int value) 98 { 99 int i; 100 101 for (i = 2; i < nr; i++) { 102 if (array[i] == value) 103 break; 104 } 105 return i == nr ? -ENOENT : i; 106 } 107 108 static void int3406_thermal_get_limit(struct int3406_thermal_data *d) 109 { 110 acpi_status status; 111 unsigned long long lower_limit, upper_limit; 112 113 status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit); 114 if (ACPI_SUCCESS(status)) 115 d->lower_limit = int3406_thermal_get_index(d->br->levels, 116 d->br->count, lower_limit); 117 118 status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit); 119 if (ACPI_SUCCESS(status)) 120 d->upper_limit = int3406_thermal_get_index(d->br->levels, 121 d->br->count, upper_limit); 122 123 /* lower_limit and upper_limit should be always set */ 124 d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2; 125 d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1; 126 } 127 128 static void int3406_notify(acpi_handle handle, u32 event, void *data) 129 { 130 if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED) 131 int3406_thermal_get_limit(data); 132 } 133 134 static int int3406_thermal_probe(struct platform_device *pdev) 135 { 136 struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 137 struct int3406_thermal_data *d; 138 struct backlight_device *bd; 139 int ret; 140 141 if (!ACPI_HANDLE(&pdev->dev)) 142 return -ENODEV; 143 144 d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); 145 if (!d) 146 return -ENOMEM; 147 d->handle = ACPI_HANDLE(&pdev->dev); 148 149 bd = backlight_device_get_by_type(BACKLIGHT_RAW); 150 if (!bd) 151 return -ENODEV; 152 d->raw_bd = bd; 153 154 ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br, NULL); 155 if (ret) 156 return ret; 157 158 int3406_thermal_get_limit(d); 159 160 d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev), 161 d, &video_cooling_ops); 162 if (IS_ERR(d->cooling_dev)) 163 goto err; 164 165 ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, 166 int3406_notify, d); 167 if (ret) 168 goto err_cdev; 169 170 platform_set_drvdata(pdev, d); 171 172 return 0; 173 174 err_cdev: 175 thermal_cooling_device_unregister(d->cooling_dev); 176 err: 177 kfree(d->br); 178 return -ENODEV; 179 } 180 181 static void int3406_thermal_remove(struct platform_device *pdev) 182 { 183 struct int3406_thermal_data *d = platform_get_drvdata(pdev); 184 185 thermal_cooling_device_unregister(d->cooling_dev); 186 kfree(d->br); 187 } 188 189 static const struct acpi_device_id int3406_thermal_match[] = { 190 {"INT3406", 0}, 191 {} 192 }; 193 194 MODULE_DEVICE_TABLE(acpi, int3406_thermal_match); 195 196 static struct platform_driver int3406_thermal_driver = { 197 .probe = int3406_thermal_probe, 198 .remove = int3406_thermal_remove, 199 .driver = { 200 .name = "int3406 thermal", 201 .acpi_match_table = int3406_thermal_match, 202 }, 203 }; 204 205 module_platform_driver(int3406_thermal_driver); 206 207 MODULE_DESCRIPTION("INT3406 Thermal driver"); 208 MODULE_LICENSE("GPL v2"); 209