1 /* 2 * intel_pmic.c - Intel PMIC operation region driver 3 * 4 * Copyright (C) 2014 Intel Corporation. All rights reserved. 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License version 8 * 2 as published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 */ 15 16 #include <linux/export.h> 17 #include <linux/acpi.h> 18 #include <linux/regmap.h> 19 #include <acpi/acpi_lpat.h> 20 #include "intel_pmic.h" 21 22 #define PMIC_POWER_OPREGION_ID 0x8d 23 #define PMIC_THERMAL_OPREGION_ID 0x8c 24 #define PMIC_REGS_OPREGION_ID 0x8f 25 26 struct intel_pmic_regs_handler_ctx { 27 unsigned int val; 28 u16 addr; 29 }; 30 31 struct intel_pmic_opregion { 32 struct mutex lock; 33 struct acpi_lpat_conversion_table *lpat_table; 34 struct regmap *regmap; 35 struct intel_pmic_opregion_data *data; 36 struct intel_pmic_regs_handler_ctx ctx; 37 }; 38 39 static int pmic_get_reg_bit(int address, struct pmic_table *table, 40 int count, int *reg, int *bit) 41 { 42 int i; 43 44 for (i = 0; i < count; i++) { 45 if (table[i].address == address) { 46 *reg = table[i].reg; 47 if (bit) 48 *bit = table[i].bit; 49 return 0; 50 } 51 } 52 return -ENOENT; 53 } 54 55 static acpi_status intel_pmic_power_handler(u32 function, 56 acpi_physical_address address, u32 bits, u64 *value64, 57 void *handler_context, void *region_context) 58 { 59 struct intel_pmic_opregion *opregion = region_context; 60 struct regmap *regmap = opregion->regmap; 61 struct intel_pmic_opregion_data *d = opregion->data; 62 int reg, bit, result; 63 64 if (bits != 32 || !value64) 65 return AE_BAD_PARAMETER; 66 67 if (function == ACPI_WRITE && !(*value64 == 0 || *value64 == 1)) 68 return AE_BAD_PARAMETER; 69 70 result = pmic_get_reg_bit(address, d->power_table, 71 d->power_table_count, ®, &bit); 72 if (result == -ENOENT) 73 return AE_BAD_PARAMETER; 74 75 mutex_lock(&opregion->lock); 76 77 result = function == ACPI_READ ? 78 d->get_power(regmap, reg, bit, value64) : 79 d->update_power(regmap, reg, bit, *value64 == 1); 80 81 mutex_unlock(&opregion->lock); 82 83 return result ? AE_ERROR : AE_OK; 84 } 85 86 static int pmic_read_temp(struct intel_pmic_opregion *opregion, 87 int reg, u64 *value) 88 { 89 int raw_temp, temp; 90 91 if (!opregion->data->get_raw_temp) 92 return -ENXIO; 93 94 raw_temp = opregion->data->get_raw_temp(opregion->regmap, reg); 95 if (raw_temp < 0) 96 return raw_temp; 97 98 if (!opregion->lpat_table) { 99 *value = raw_temp; 100 return 0; 101 } 102 103 temp = acpi_lpat_raw_to_temp(opregion->lpat_table, raw_temp); 104 if (temp < 0) 105 return temp; 106 107 *value = temp; 108 return 0; 109 } 110 111 static int pmic_thermal_temp(struct intel_pmic_opregion *opregion, int reg, 112 u32 function, u64 *value) 113 { 114 return function == ACPI_READ ? 115 pmic_read_temp(opregion, reg, value) : -EINVAL; 116 } 117 118 static int pmic_thermal_aux(struct intel_pmic_opregion *opregion, int reg, 119 u32 function, u64 *value) 120 { 121 int raw_temp; 122 123 if (function == ACPI_READ) 124 return pmic_read_temp(opregion, reg, value); 125 126 if (!opregion->data->update_aux) 127 return -ENXIO; 128 129 if (opregion->lpat_table) { 130 raw_temp = acpi_lpat_temp_to_raw(opregion->lpat_table, *value); 131 if (raw_temp < 0) 132 return raw_temp; 133 } else { 134 raw_temp = *value; 135 } 136 137 return opregion->data->update_aux(opregion->regmap, reg, raw_temp); 138 } 139 140 static int pmic_thermal_pen(struct intel_pmic_opregion *opregion, int reg, 141 int bit, u32 function, u64 *value) 142 { 143 struct intel_pmic_opregion_data *d = opregion->data; 144 struct regmap *regmap = opregion->regmap; 145 146 if (!d->get_policy || !d->update_policy) 147 return -ENXIO; 148 149 if (function == ACPI_READ) 150 return d->get_policy(regmap, reg, bit, value); 151 152 if (*value != 0 && *value != 1) 153 return -EINVAL; 154 155 return d->update_policy(regmap, reg, bit, *value); 156 } 157 158 static bool pmic_thermal_is_temp(int address) 159 { 160 return (address <= 0x3c) && !(address % 12); 161 } 162 163 static bool pmic_thermal_is_aux(int address) 164 { 165 return (address >= 4 && address <= 0x40 && !((address - 4) % 12)) || 166 (address >= 8 && address <= 0x44 && !((address - 8) % 12)); 167 } 168 169 static bool pmic_thermal_is_pen(int address) 170 { 171 return address >= 0x48 && address <= 0x5c; 172 } 173 174 static acpi_status intel_pmic_thermal_handler(u32 function, 175 acpi_physical_address address, u32 bits, u64 *value64, 176 void *handler_context, void *region_context) 177 { 178 struct intel_pmic_opregion *opregion = region_context; 179 struct intel_pmic_opregion_data *d = opregion->data; 180 int reg, bit, result; 181 182 if (bits != 32 || !value64) 183 return AE_BAD_PARAMETER; 184 185 result = pmic_get_reg_bit(address, d->thermal_table, 186 d->thermal_table_count, ®, &bit); 187 if (result == -ENOENT) 188 return AE_BAD_PARAMETER; 189 190 mutex_lock(&opregion->lock); 191 192 if (pmic_thermal_is_temp(address)) 193 result = pmic_thermal_temp(opregion, reg, function, value64); 194 else if (pmic_thermal_is_aux(address)) 195 result = pmic_thermal_aux(opregion, reg, function, value64); 196 else if (pmic_thermal_is_pen(address)) 197 result = pmic_thermal_pen(opregion, reg, bit, 198 function, value64); 199 else 200 result = -EINVAL; 201 202 mutex_unlock(&opregion->lock); 203 204 if (result < 0) { 205 if (result == -EINVAL) 206 return AE_BAD_PARAMETER; 207 else 208 return AE_ERROR; 209 } 210 211 return AE_OK; 212 } 213 214 static acpi_status intel_pmic_regs_handler(u32 function, 215 acpi_physical_address address, u32 bits, u64 *value64, 216 void *handler_context, void *region_context) 217 { 218 struct intel_pmic_opregion *opregion = region_context; 219 int result = 0; 220 221 switch (address) { 222 case 0: 223 return AE_OK; 224 case 1: 225 opregion->ctx.addr |= (*value64 & 0xff) << 8; 226 return AE_OK; 227 case 2: 228 opregion->ctx.addr |= *value64 & 0xff; 229 return AE_OK; 230 case 3: 231 opregion->ctx.val = *value64 & 0xff; 232 return AE_OK; 233 case 4: 234 if (*value64) { 235 result = regmap_write(opregion->regmap, opregion->ctx.addr, 236 opregion->ctx.val); 237 } else { 238 result = regmap_read(opregion->regmap, opregion->ctx.addr, 239 &opregion->ctx.val); 240 if (result == 0) 241 *value64 = opregion->ctx.val; 242 } 243 memset(&opregion->ctx, 0x00, sizeof(opregion->ctx)); 244 } 245 246 if (result < 0) { 247 if (result == -EINVAL) 248 return AE_BAD_PARAMETER; 249 else 250 return AE_ERROR; 251 } 252 253 return AE_OK; 254 } 255 256 int intel_pmic_install_opregion_handler(struct device *dev, acpi_handle handle, 257 struct regmap *regmap, 258 struct intel_pmic_opregion_data *d) 259 { 260 acpi_status status; 261 struct intel_pmic_opregion *opregion; 262 int ret; 263 264 if (!dev || !regmap || !d) 265 return -EINVAL; 266 267 if (!handle) 268 return -ENODEV; 269 270 opregion = devm_kzalloc(dev, sizeof(*opregion), GFP_KERNEL); 271 if (!opregion) 272 return -ENOMEM; 273 274 mutex_init(&opregion->lock); 275 opregion->regmap = regmap; 276 opregion->lpat_table = acpi_lpat_get_conversion_table(handle); 277 278 status = acpi_install_address_space_handler(handle, 279 PMIC_POWER_OPREGION_ID, 280 intel_pmic_power_handler, 281 NULL, opregion); 282 if (ACPI_FAILURE(status)) { 283 ret = -ENODEV; 284 goto out_error; 285 } 286 287 status = acpi_install_address_space_handler(handle, 288 PMIC_THERMAL_OPREGION_ID, 289 intel_pmic_thermal_handler, 290 NULL, opregion); 291 if (ACPI_FAILURE(status)) { 292 acpi_remove_address_space_handler(handle, PMIC_POWER_OPREGION_ID, 293 intel_pmic_power_handler); 294 ret = -ENODEV; 295 goto out_remove_power_handler; 296 } 297 298 status = acpi_install_address_space_handler(handle, 299 PMIC_REGS_OPREGION_ID, intel_pmic_regs_handler, NULL, 300 opregion); 301 if (ACPI_FAILURE(status)) { 302 ret = -ENODEV; 303 goto out_remove_thermal_handler; 304 } 305 306 opregion->data = d; 307 return 0; 308 309 out_remove_thermal_handler: 310 acpi_remove_address_space_handler(handle, PMIC_THERMAL_OPREGION_ID, 311 intel_pmic_thermal_handler); 312 313 out_remove_power_handler: 314 acpi_remove_address_space_handler(handle, PMIC_POWER_OPREGION_ID, 315 intel_pmic_power_handler); 316 317 out_error: 318 acpi_lpat_free_conversion_table(opregion->lpat_table); 319 return ret; 320 } 321 EXPORT_SYMBOL_GPL(intel_pmic_install_opregion_handler); 322