diff options
Diffstat (limited to 'drivers/acpi/pmic/intel_pmic.c')
-rw-r--r-- | drivers/acpi/pmic/intel_pmic.c | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/drivers/acpi/pmic/intel_pmic.c b/drivers/acpi/pmic/intel_pmic.c new file mode 100644 index 000000000..f20dbda1a --- /dev/null +++ b/drivers/acpi/pmic/intel_pmic.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * intel_pmic.c - Intel PMIC operation region driver + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + */ + +#include <linux/export.h> +#include <linux/acpi.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/regmap.h> +#include <acpi/acpi_lpat.h> +#include "intel_pmic.h" + +#define PMIC_POWER_OPREGION_ID 0x8d +#define PMIC_THERMAL_OPREGION_ID 0x8c +#define PMIC_REGS_OPREGION_ID 0x8f + +struct intel_pmic_regs_handler_ctx { + unsigned int val; + u16 addr; +}; + +struct intel_pmic_opregion { + struct mutex lock; + struct acpi_lpat_conversion_table *lpat_table; + struct regmap *regmap; + const struct intel_pmic_opregion_data *data; + struct intel_pmic_regs_handler_ctx ctx; +}; + +static struct intel_pmic_opregion *intel_pmic_opregion; + +static int pmic_get_reg_bit(int address, struct pmic_table *table, + int count, int *reg, int *bit) +{ + int i; + + for (i = 0; i < count; i++) { + if (table[i].address == address) { + *reg = table[i].reg; + if (bit) + *bit = table[i].bit; + return 0; + } + } + return -ENOENT; +} + +static acpi_status intel_pmic_power_handler(u32 function, + acpi_physical_address address, u32 bits, u64 *value64, + void *handler_context, void *region_context) +{ + struct intel_pmic_opregion *opregion = region_context; + struct regmap *regmap = opregion->regmap; + const struct intel_pmic_opregion_data *d = opregion->data; + int reg, bit, result; + + if (bits != 32 || !value64) + return AE_BAD_PARAMETER; + + if (function == ACPI_WRITE && !(*value64 == 0 || *value64 == 1)) + return AE_BAD_PARAMETER; + + result = pmic_get_reg_bit(address, d->power_table, + d->power_table_count, ®, &bit); + if (result == -ENOENT) + return AE_BAD_PARAMETER; + + mutex_lock(&opregion->lock); + + result = function == ACPI_READ ? + d->get_power(regmap, reg, bit, value64) : + d->update_power(regmap, reg, bit, *value64 == 1); + + mutex_unlock(&opregion->lock); + + return result ? AE_ERROR : AE_OK; +} + +static int pmic_read_temp(struct intel_pmic_opregion *opregion, + int reg, u64 *value) +{ + int raw_temp, temp; + + if (!opregion->data->get_raw_temp) + return -ENXIO; + + raw_temp = opregion->data->get_raw_temp(opregion->regmap, reg); + if (raw_temp < 0) + return raw_temp; + + if (!opregion->lpat_table) { + *value = raw_temp; + return 0; + } + + temp = opregion->data->lpat_raw_to_temp(opregion->lpat_table, raw_temp); + if (temp < 0) + return temp; + + *value = temp; + return 0; +} + +static int pmic_thermal_temp(struct intel_pmic_opregion *opregion, int reg, + u32 function, u64 *value) +{ + return function == ACPI_READ ? + pmic_read_temp(opregion, reg, value) : -EINVAL; +} + +static int pmic_thermal_aux(struct intel_pmic_opregion *opregion, int reg, + u32 function, u64 *value) +{ + int raw_temp; + + if (function == ACPI_READ) + return pmic_read_temp(opregion, reg, value); + + if (!opregion->data->update_aux) + return -ENXIO; + + if (opregion->lpat_table) { + raw_temp = acpi_lpat_temp_to_raw(opregion->lpat_table, *value); + if (raw_temp < 0) + return raw_temp; + } else { + raw_temp = *value; + } + + return opregion->data->update_aux(opregion->regmap, reg, raw_temp); +} + +static int pmic_thermal_pen(struct intel_pmic_opregion *opregion, int reg, + int bit, u32 function, u64 *value) +{ + const struct intel_pmic_opregion_data *d = opregion->data; + struct regmap *regmap = opregion->regmap; + + if (!d->get_policy || !d->update_policy) + return -ENXIO; + + if (function == ACPI_READ) + return d->get_policy(regmap, reg, bit, value); + + if (*value != 0 && *value != 1) + return -EINVAL; + + return d->update_policy(regmap, reg, bit, *value); +} + +static bool pmic_thermal_is_temp(int address) +{ + return (address <= 0x3c) && !(address % 12); +} + +static bool pmic_thermal_is_aux(int address) +{ + return (address >= 4 && address <= 0x40 && !((address - 4) % 12)) || + (address >= 8 && address <= 0x44 && !((address - 8) % 12)); +} + +static bool pmic_thermal_is_pen(int address) +{ + return address >= 0x48 && address <= 0x5c; +} + +static acpi_status intel_pmic_thermal_handler(u32 function, + acpi_physical_address address, u32 bits, u64 *value64, + void *handler_context, void *region_context) +{ + struct intel_pmic_opregion *opregion = region_context; + const struct intel_pmic_opregion_data *d = opregion->data; + int reg, bit, result; + + if (bits != 32 || !value64) + return AE_BAD_PARAMETER; + + result = pmic_get_reg_bit(address, d->thermal_table, + d->thermal_table_count, ®, &bit); + if (result == -ENOENT) + return AE_BAD_PARAMETER; + + mutex_lock(&opregion->lock); + + if (pmic_thermal_is_temp(address)) + result = pmic_thermal_temp(opregion, reg, function, value64); + else if (pmic_thermal_is_aux(address)) + result = pmic_thermal_aux(opregion, reg, function, value64); + else if (pmic_thermal_is_pen(address)) + result = pmic_thermal_pen(opregion, reg, bit, + function, value64); + else + result = -EINVAL; + + mutex_unlock(&opregion->lock); + + if (result < 0) { + if (result == -EINVAL) + return AE_BAD_PARAMETER; + else + return AE_ERROR; + } + + return AE_OK; +} + +static acpi_status intel_pmic_regs_handler(u32 function, + acpi_physical_address address, u32 bits, u64 *value64, + void *handler_context, void *region_context) +{ + struct intel_pmic_opregion *opregion = region_context; + int result = -EINVAL; + + if (function == ACPI_WRITE) { + switch (address) { + case 0: + return AE_OK; + case 1: + opregion->ctx.addr |= (*value64 & 0xff) << 8; + return AE_OK; + case 2: + opregion->ctx.addr |= *value64 & 0xff; + return AE_OK; + case 3: + opregion->ctx.val = *value64 & 0xff; + return AE_OK; + case 4: + if (*value64) { + result = regmap_write(opregion->regmap, opregion->ctx.addr, + opregion->ctx.val); + } else { + result = regmap_read(opregion->regmap, opregion->ctx.addr, + &opregion->ctx.val); + } + opregion->ctx.addr = 0; + } + } + + if (function == ACPI_READ && address == 3) { + *value64 = opregion->ctx.val; + return AE_OK; + } + + if (result < 0) { + if (result == -EINVAL) + return AE_BAD_PARAMETER; + else + return AE_ERROR; + } + + return AE_OK; +} + +int intel_pmic_install_opregion_handler(struct device *dev, acpi_handle handle, + struct regmap *regmap, + const struct intel_pmic_opregion_data *d) +{ + acpi_status status = AE_OK; + struct intel_pmic_opregion *opregion; + int ret; + + if (!dev || !regmap || !d) + return -EINVAL; + + if (!handle) + return -ENODEV; + + opregion = devm_kzalloc(dev, sizeof(*opregion), GFP_KERNEL); + if (!opregion) + return -ENOMEM; + + mutex_init(&opregion->lock); + opregion->regmap = regmap; + opregion->lpat_table = acpi_lpat_get_conversion_table(handle); + + if (d->power_table_count) + status = acpi_install_address_space_handler(handle, + PMIC_POWER_OPREGION_ID, + intel_pmic_power_handler, + NULL, opregion); + if (ACPI_FAILURE(status)) { + ret = -ENODEV; + goto out_error; + } + + if (d->thermal_table_count) + status = acpi_install_address_space_handler(handle, + PMIC_THERMAL_OPREGION_ID, + intel_pmic_thermal_handler, + NULL, opregion); + if (ACPI_FAILURE(status)) { + ret = -ENODEV; + goto out_remove_power_handler; + } + + status = acpi_install_address_space_handler(handle, + PMIC_REGS_OPREGION_ID, intel_pmic_regs_handler, NULL, + opregion); + if (ACPI_FAILURE(status)) { + ret = -ENODEV; + goto out_remove_thermal_handler; + } + + opregion->data = d; + intel_pmic_opregion = opregion; + return 0; + +out_remove_thermal_handler: + if (d->thermal_table_count) + acpi_remove_address_space_handler(handle, + PMIC_THERMAL_OPREGION_ID, + intel_pmic_thermal_handler); + +out_remove_power_handler: + if (d->power_table_count) + acpi_remove_address_space_handler(handle, + PMIC_POWER_OPREGION_ID, + intel_pmic_power_handler); + +out_error: + acpi_lpat_free_conversion_table(opregion->lpat_table); + return ret; +} +EXPORT_SYMBOL_GPL(intel_pmic_install_opregion_handler); + +/** + * intel_soc_pmic_exec_mipi_pmic_seq_element - Execute PMIC MIPI sequence + * @i2c_address: I2C client address for the PMIC + * @reg_address: PMIC register address + * @value: New value for the register bits to change + * @mask: Mask indicating which register bits to change + * + * DSI LCD panels describe an initialization sequence in the i915 VBT (Video + * BIOS Tables) using so called MIPI sequences. One possible element in these + * sequences is a PMIC specific element of 15 bytes. + * + * This function executes these PMIC specific elements sending the embedded + * commands to the PMIC. + * + * Return 0 on success, < 0 on failure. + */ +int intel_soc_pmic_exec_mipi_pmic_seq_element(u16 i2c_address, u32 reg_address, + u32 value, u32 mask) +{ + const struct intel_pmic_opregion_data *d; + int ret; + + if (!intel_pmic_opregion) { + pr_warn("%s: No PMIC registered\n", __func__); + return -ENXIO; + } + + d = intel_pmic_opregion->data; + + mutex_lock(&intel_pmic_opregion->lock); + + if (d->exec_mipi_pmic_seq_element) { + ret = d->exec_mipi_pmic_seq_element(intel_pmic_opregion->regmap, + i2c_address, reg_address, + value, mask); + } else if (d->pmic_i2c_address) { + if (i2c_address == d->pmic_i2c_address) { + ret = regmap_update_bits(intel_pmic_opregion->regmap, + reg_address, mask, value); + } else { + pr_err("%s: Unexpected i2c-addr: 0x%02x (reg-addr 0x%x value 0x%x mask 0x%x)\n", + __func__, i2c_address, reg_address, value, mask); + ret = -ENXIO; + } + } else { + pr_warn("%s: Not implemented\n", __func__); + pr_warn("%s: i2c-addr: 0x%x reg-addr 0x%x value 0x%x mask 0x%x\n", + __func__, i2c_address, reg_address, value, mask); + ret = -EOPNOTSUPP; + } + + mutex_unlock(&intel_pmic_opregion->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(intel_soc_pmic_exec_mipi_pmic_seq_element); |