root/drivers/acpi/pmic/intel_pmic.c
// 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, const 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, &reg, &bit);
        if (result == -ENOENT)
                return AE_BAD_PARAMETER;

        guard(mutex)(&opregion->lock);

        result = function == ACPI_READ ?
                d->get_power(regmap, reg, bit, value64) :
                d->update_power(regmap, reg, bit, *value64 == 1);

        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, &reg, &bit);
        if (result == -ENOENT)
                return AE_BAD_PARAMETER;

        scoped_guard(mutex, &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;
        }

        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;

        guard(mutex)(&intel_pmic_opregion->lock);

        if (d->exec_mipi_pmic_seq_element) {
                return d->exec_mipi_pmic_seq_element(intel_pmic_opregion->regmap,
                                                    i2c_address, reg_address,
                                                    value, mask);
        }

        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;
        }

        return ret;
}
EXPORT_SYMBOL_GPL(intel_soc_pmic_exec_mipi_pmic_seq_element);