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