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/module.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 25struct intel_pmic_opregion { 26 struct mutex lock; 27 struct acpi_lpat_conversion_table *lpat_table; 28 struct regmap *regmap; 29 struct intel_pmic_opregion_data *data; 30}; 31 32static int pmic_get_reg_bit(int address, struct pmic_table *table, 33 int count, int *reg, int *bit) 34{ 35 int i; 36 37 for (i = 0; i < count; i++) { 38 if (table[i].address == address) { 39 *reg = table[i].reg; 40 if (bit) 41 *bit = table[i].bit; 42 return 0; 43 } 44 } 45 return -ENOENT; 46} 47 48static acpi_status intel_pmic_power_handler(u32 function, 49 acpi_physical_address address, u32 bits, u64 *value64, 50 void *handler_context, void *region_context) 51{ 52 struct intel_pmic_opregion *opregion = region_context; 53 struct regmap *regmap = opregion->regmap; 54 struct intel_pmic_opregion_data *d = opregion->data; 55 int reg, bit, result; 56 57 if (bits != 32 || !value64) 58 return AE_BAD_PARAMETER; 59 60 if (function == ACPI_WRITE && !(*value64 == 0 || *value64 == 1)) 61 return AE_BAD_PARAMETER; 62 63 result = pmic_get_reg_bit(address, d->power_table, 64 d->power_table_count, ®, &bit); 65 if (result == -ENOENT) 66 return AE_BAD_PARAMETER; 67 68 mutex_lock(&opregion->lock); 69 70 result = function == ACPI_READ ? 71 d->get_power(regmap, reg, bit, value64) : 72 d->update_power(regmap, reg, bit, *value64 == 1); 73 74 mutex_unlock(&opregion->lock); 75 76 return result ? AE_ERROR : AE_OK; 77} 78 79static int pmic_read_temp(struct intel_pmic_opregion *opregion, 80 int reg, u64 *value) 81{ 82 int raw_temp, temp; 83 84 if (!opregion->data->get_raw_temp) 85 return -ENXIO; 86 87 raw_temp = opregion->data->get_raw_temp(opregion->regmap, reg); 88 if (raw_temp < 0) 89 return raw_temp; 90 91 if (!opregion->lpat_table) { 92 *value = raw_temp; 93 return 0; 94 } 95 96 temp = acpi_lpat_raw_to_temp(opregion->lpat_table, raw_temp); 97 if (temp < 0) 98 return temp; 99 100 *value = temp; 101 return 0; 102} 103 104static int pmic_thermal_temp(struct intel_pmic_opregion *opregion, int reg, 105 u32 function, u64 *value) 106{ 107 return function == ACPI_READ ? 108 pmic_read_temp(opregion, reg, value) : -EINVAL; 109} 110 111static int pmic_thermal_aux(struct intel_pmic_opregion *opregion, int reg, 112 u32 function, u64 *value) 113{ 114 int raw_temp; 115 116 if (function == ACPI_READ) 117 return pmic_read_temp(opregion, reg, value); 118 119 if (!opregion->data->update_aux) 120 return -ENXIO; 121 122 if (opregion->lpat_table) { 123 raw_temp = acpi_lpat_temp_to_raw(opregion->lpat_table, *value); 124 if (raw_temp < 0) 125 return raw_temp; 126 } else { 127 raw_temp = *value; 128 } 129 130 return opregion->data->update_aux(opregion->regmap, reg, raw_temp); 131} 132 133static int pmic_thermal_pen(struct intel_pmic_opregion *opregion, int reg, 134 u32 function, u64 *value) 135{ 136 struct intel_pmic_opregion_data *d = opregion->data; 137 struct regmap *regmap = opregion->regmap; 138 139 if (!d->get_policy || !d->update_policy) 140 return -ENXIO; 141 142 if (function == ACPI_READ) 143 return d->get_policy(regmap, reg, value); 144 145 if (*value != 0 && *value != 1) 146 return -EINVAL; 147 148 return d->update_policy(regmap, reg, *value); 149} 150 151static bool pmic_thermal_is_temp(int address) 152{ 153 return (address <= 0x3c) && !(address % 12); 154} 155 156static bool pmic_thermal_is_aux(int address) 157{ 158 return (address >= 4 && address <= 0x40 && !((address - 4) % 12)) || 159 (address >= 8 && address <= 0x44 && !((address - 8) % 12)); 160} 161 162static bool pmic_thermal_is_pen(int address) 163{ 164 return address >= 0x48 && address <= 0x5c; 165} 166 167static acpi_status intel_pmic_thermal_handler(u32 function, 168 acpi_physical_address address, u32 bits, u64 *value64, 169 void *handler_context, void *region_context) 170{ 171 struct intel_pmic_opregion *opregion = region_context; 172 struct intel_pmic_opregion_data *d = opregion->data; 173 int reg, result; 174 175 if (bits != 32 || !value64) 176 return AE_BAD_PARAMETER; 177 178 result = pmic_get_reg_bit(address, d->thermal_table, 179 d->thermal_table_count, ®, NULL); 180 if (result == -ENOENT) 181 return AE_BAD_PARAMETER; 182 183 mutex_lock(&opregion->lock); 184 185 if (pmic_thermal_is_temp(address)) 186 result = pmic_thermal_temp(opregion, reg, function, value64); 187 else if (pmic_thermal_is_aux(address)) 188 result = pmic_thermal_aux(opregion, reg, function, value64); 189 else if (pmic_thermal_is_pen(address)) 190 result = pmic_thermal_pen(opregion, reg, function, value64); 191 else 192 result = -EINVAL; 193 194 mutex_unlock(&opregion->lock); 195 196 if (result < 0) { 197 if (result == -EINVAL) 198 return AE_BAD_PARAMETER; 199 else 200 return AE_ERROR; 201 } 202 203 return AE_OK; 204} 205 206int intel_pmic_install_opregion_handler(struct device *dev, acpi_handle handle, 207 struct regmap *regmap, 208 struct intel_pmic_opregion_data *d) 209{ 210 acpi_status status; 211 struct intel_pmic_opregion *opregion; 212 int ret; 213 214 if (!dev || !regmap || !d) 215 return -EINVAL; 216 217 if (!handle) 218 return -ENODEV; 219 220 opregion = devm_kzalloc(dev, sizeof(*opregion), GFP_KERNEL); 221 if (!opregion) 222 return -ENOMEM; 223 224 mutex_init(&opregion->lock); 225 opregion->regmap = regmap; 226 opregion->lpat_table = acpi_lpat_get_conversion_table(handle); 227 228 status = acpi_install_address_space_handler(handle, 229 PMIC_POWER_OPREGION_ID, 230 intel_pmic_power_handler, 231 NULL, opregion); 232 if (ACPI_FAILURE(status)) { 233 ret = -ENODEV; 234 goto out_error; 235 } 236 237 status = acpi_install_address_space_handler(handle, 238 PMIC_THERMAL_OPREGION_ID, 239 intel_pmic_thermal_handler, 240 NULL, opregion); 241 if (ACPI_FAILURE(status)) { 242 acpi_remove_address_space_handler(handle, PMIC_POWER_OPREGION_ID, 243 intel_pmic_power_handler); 244 ret = -ENODEV; 245 goto out_error; 246 } 247 248 opregion->data = d; 249 return 0; 250 251out_error: 252 acpi_lpat_free_conversion_table(opregion->lpat_table); 253 return ret; 254} 255EXPORT_SYMBOL_GPL(intel_pmic_install_opregion_handler); 256 257MODULE_LICENSE("GPL"); 258