diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/acpi/pmic | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/acpi/pmic')
-rw-r--r-- | drivers/acpi/pmic/Kconfig | 67 | ||||
-rw-r--r-- | drivers/acpi/pmic/Makefile | 10 | ||||
-rw-r--r-- | drivers/acpi/pmic/intel_pmic.c | 383 | ||||
-rw-r--r-- | drivers/acpi/pmic/intel_pmic.h | 36 | ||||
-rw-r--r-- | drivers/acpi/pmic/intel_pmic_bxtwc.c | 408 | ||||
-rw-r--r-- | drivers/acpi/pmic/intel_pmic_bytcrc.c | 302 | ||||
-rw-r--r-- | drivers/acpi/pmic/intel_pmic_chtcrc.c | 45 | ||||
-rw-r--r-- | drivers/acpi/pmic/intel_pmic_chtdc_ti.c | 137 | ||||
-rw-r--r-- | drivers/acpi/pmic/intel_pmic_chtwc.c | 287 | ||||
-rw-r--r-- | drivers/acpi/pmic/intel_pmic_xpower.c | 367 | ||||
-rw-r--r-- | drivers/acpi/pmic/tps68470_pmic.c | 447 |
11 files changed, 2489 insertions, 0 deletions
diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig new file mode 100644 index 000000000..f84b8f603 --- /dev/null +++ b/drivers/acpi/pmic/Kconfig @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: GPL-2.0 + +menuconfig PMIC_OPREGION + bool "PMIC (Power Management Integrated Circuit) operation region support" + help + Select this option to enable support for ACPI operation + region of the PMIC chip. The operation region can be used + to control power rails and sensor reading/writing on the + PMIC chip. + +if PMIC_OPREGION + +config BYTCRC_PMIC_OPREGION + bool "ACPI operation region support for Bay Trail Crystal Cove PMIC" + depends on INTEL_SOC_PMIC + help + This config adds ACPI operation region support for the Bay Trail + version of the Crystal Cove PMIC. + +config CHTCRC_PMIC_OPREGION + bool "ACPI operation region support for Cherry Trail Crystal Cove PMIC" + depends on INTEL_SOC_PMIC + help + This config adds ACPI operation region support for the Cherry Trail + version of the Crystal Cove PMIC. + +config XPOWER_PMIC_OPREGION + bool "ACPI operation region support for XPower AXP288 PMIC" + depends on MFD_AXP20X_I2C && IOSF_MBI=y + help + This config adds ACPI operation region support for XPower AXP288 PMIC. + +config BXT_WC_PMIC_OPREGION + bool "ACPI operation region support for BXT WhiskeyCove PMIC" + depends on INTEL_SOC_PMIC_BXTWC + help + This config adds ACPI operation region support for BXT WhiskeyCove PMIC. + +config CHT_WC_PMIC_OPREGION + bool "ACPI operation region support for CHT Whiskey Cove PMIC" + depends on INTEL_SOC_PMIC_CHTWC + help + This config adds ACPI operation region support for CHT Whiskey Cove PMIC. + +config CHT_DC_TI_PMIC_OPREGION + bool "ACPI operation region support for Dollar Cove TI PMIC" + depends on INTEL_SOC_PMIC_CHTDC_TI + help + This config adds ACPI operation region support for Dollar Cove TI PMIC. + +endif # PMIC_OPREGION + +config TPS68470_PMIC_OPREGION + bool "ACPI operation region support for TPS68470 PMIC" + depends on INTEL_SKL_INT3472 + help + This config adds ACPI operation region support for TI TPS68470 PMIC. + TPS68470 device is an advanced power management unit that powers + a Compact Camera Module (CCM), generates clocks for image sensors, + drives a dual LED for flash and incorporates two LED drivers for + general purpose indicators. + This driver enables ACPI operation region support control voltage + regulators and clocks. + + This option is a bool as it provides an ACPI operation + region, which must be available before any of the devices + using this, are probed. diff --git a/drivers/acpi/pmic/Makefile b/drivers/acpi/pmic/Makefile new file mode 100644 index 000000000..cd072c649 --- /dev/null +++ b/drivers/acpi/pmic/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_PMIC_OPREGION) += intel_pmic.o +obj-$(CONFIG_BYTCRC_PMIC_OPREGION) += intel_pmic_bytcrc.o +obj-$(CONFIG_CHTCRC_PMIC_OPREGION) += intel_pmic_chtcrc.o +obj-$(CONFIG_XPOWER_PMIC_OPREGION) += intel_pmic_xpower.o +obj-$(CONFIG_BXT_WC_PMIC_OPREGION) += intel_pmic_bxtwc.o +obj-$(CONFIG_CHT_WC_PMIC_OPREGION) += intel_pmic_chtwc.o +obj-$(CONFIG_CHT_DC_TI_PMIC_OPREGION) += intel_pmic_chtdc_ti.o +obj-$(CONFIG_TPS68470_PMIC_OPREGION) += tps68470_pmic.o 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); diff --git a/drivers/acpi/pmic/intel_pmic.h b/drivers/acpi/pmic/intel_pmic.h new file mode 100644 index 000000000..d956b03a6 --- /dev/null +++ b/drivers/acpi/pmic/intel_pmic.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __INTEL_PMIC_H +#define __INTEL_PMIC_H + +#include <acpi/acpi_lpat.h> + +struct pmic_table { + int address; /* operation region address */ + int reg; /* corresponding thermal register */ + int bit; /* control bit for power */ +}; + +struct intel_pmic_opregion_data { + int (*get_power)(struct regmap *r, int reg, int bit, u64 *value); + int (*update_power)(struct regmap *r, int reg, int bit, bool on); + int (*get_raw_temp)(struct regmap *r, int reg); + int (*update_aux)(struct regmap *r, int reg, int raw_temp); + int (*get_policy)(struct regmap *r, int reg, int bit, u64 *value); + int (*update_policy)(struct regmap *r, int reg, int bit, int enable); + int (*exec_mipi_pmic_seq_element)(struct regmap *r, u16 i2c_address, + u32 reg_address, u32 value, u32 mask); + int (*lpat_raw_to_temp)(struct acpi_lpat_conversion_table *lpat_table, + int raw); + struct pmic_table *power_table; + int power_table_count; + struct pmic_table *thermal_table; + int thermal_table_count; + /* For generic exec_mipi_pmic_seq_element handling */ + int pmic_i2c_address; +}; + +int intel_pmic_install_opregion_handler(struct device *dev, acpi_handle handle, + struct regmap *regmap, + const struct intel_pmic_opregion_data *d); + +#endif diff --git a/drivers/acpi/pmic/intel_pmic_bxtwc.c b/drivers/acpi/pmic/intel_pmic_bxtwc.c new file mode 100644 index 000000000..e24761518 --- /dev/null +++ b/drivers/acpi/pmic/intel_pmic_bxtwc.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel BXT WhiskeyCove PMIC operation region driver + * + * Copyright (C) 2015 Intel Corporation. All rights reserved. + */ + +#include <linux/init.h> +#include <linux/acpi.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include "intel_pmic.h" + +#define WHISKEY_COVE_ALRT_HIGH_BIT_MASK 0x0F +#define WHISKEY_COVE_ADC_HIGH_BIT(x) (((x & 0x0F) << 8)) +#define WHISKEY_COVE_ADC_CURSRC(x) (((x & 0xF0) >> 4)) +#define VR_MODE_DISABLED 0 +#define VR_MODE_AUTO BIT(0) +#define VR_MODE_NORMAL BIT(1) +#define VR_MODE_SWITCH BIT(2) +#define VR_MODE_ECO (BIT(0)|BIT(1)) +#define VSWITCH2_OUTPUT BIT(5) +#define VSWITCH1_OUTPUT BIT(4) +#define VUSBPHY_CHARGE BIT(1) + +static struct pmic_table power_table[] = { + { + .address = 0x0, + .reg = 0x63, + .bit = VR_MODE_AUTO, + }, /* VDD1 -> VDD1CNT */ + { + .address = 0x04, + .reg = 0x65, + .bit = VR_MODE_AUTO, + }, /* VDD2 -> VDD2CNT */ + { + .address = 0x08, + .reg = 0x67, + .bit = VR_MODE_AUTO, + }, /* VDD3 -> VDD3CNT */ + { + .address = 0x0c, + .reg = 0x6d, + .bit = VR_MODE_AUTO, + }, /* VLFX -> VFLEXCNT */ + { + .address = 0x10, + .reg = 0x6f, + .bit = VR_MODE_NORMAL, + }, /* VP1A -> VPROG1ACNT */ + { + .address = 0x14, + .reg = 0x70, + .bit = VR_MODE_NORMAL, + }, /* VP1B -> VPROG1BCNT */ + { + .address = 0x18, + .reg = 0x71, + .bit = VR_MODE_NORMAL, + }, /* VP1C -> VPROG1CCNT */ + { + .address = 0x1c, + .reg = 0x72, + .bit = VR_MODE_NORMAL, + }, /* VP1D -> VPROG1DCNT */ + { + .address = 0x20, + .reg = 0x73, + .bit = VR_MODE_NORMAL, + }, /* VP2A -> VPROG2ACNT */ + { + .address = 0x24, + .reg = 0x74, + .bit = VR_MODE_NORMAL, + }, /* VP2B -> VPROG2BCNT */ + { + .address = 0x28, + .reg = 0x75, + .bit = VR_MODE_NORMAL, + }, /* VP2C -> VPROG2CCNT */ + { + .address = 0x2c, + .reg = 0x76, + .bit = VR_MODE_NORMAL, + }, /* VP3A -> VPROG3ACNT */ + { + .address = 0x30, + .reg = 0x77, + .bit = VR_MODE_NORMAL, + }, /* VP3B -> VPROG3BCNT */ + { + .address = 0x34, + .reg = 0x78, + .bit = VSWITCH2_OUTPUT, + }, /* VSW2 -> VLD0CNT Bit 5*/ + { + .address = 0x38, + .reg = 0x78, + .bit = VSWITCH1_OUTPUT, + }, /* VSW1 -> VLD0CNT Bit 4 */ + { + .address = 0x3c, + .reg = 0x78, + .bit = VUSBPHY_CHARGE, + }, /* VUPY -> VLDOCNT Bit 1 */ + { + .address = 0x40, + .reg = 0x7b, + .bit = VR_MODE_NORMAL, + }, /* VRSO -> VREFSOCCNT*/ + { + .address = 0x44, + .reg = 0xA0, + .bit = VR_MODE_NORMAL, + }, /* VP1E -> VPROG1ECNT */ + { + .address = 0x48, + .reg = 0xA1, + .bit = VR_MODE_NORMAL, + }, /* VP1F -> VPROG1FCNT */ + { + .address = 0x4c, + .reg = 0xA2, + .bit = VR_MODE_NORMAL, + }, /* VP2D -> VPROG2DCNT */ + { + .address = 0x50, + .reg = 0xA3, + .bit = VR_MODE_NORMAL, + }, /* VP4A -> VPROG4ACNT */ + { + .address = 0x54, + .reg = 0xA4, + .bit = VR_MODE_NORMAL, + }, /* VP4B -> VPROG4BCNT */ + { + .address = 0x58, + .reg = 0xA5, + .bit = VR_MODE_NORMAL, + }, /* VP4C -> VPROG4CCNT */ + { + .address = 0x5c, + .reg = 0xA6, + .bit = VR_MODE_NORMAL, + }, /* VP4D -> VPROG4DCNT */ + { + .address = 0x60, + .reg = 0xA7, + .bit = VR_MODE_NORMAL, + }, /* VP5A -> VPROG5ACNT */ + { + .address = 0x64, + .reg = 0xA8, + .bit = VR_MODE_NORMAL, + }, /* VP5B -> VPROG5BCNT */ + { + .address = 0x68, + .reg = 0xA9, + .bit = VR_MODE_NORMAL, + }, /* VP6A -> VPROG6ACNT */ + { + .address = 0x6c, + .reg = 0xAA, + .bit = VR_MODE_NORMAL, + }, /* VP6B -> VPROG6BCNT */ + { + .address = 0x70, + .reg = 0x36, + .bit = BIT(2), + }, /* SDWN_N -> MODEMCTRL Bit 2 */ + { + .address = 0x74, + .reg = 0x36, + .bit = BIT(0), + } /* MOFF -> MODEMCTRL Bit 0 */ +}; + +static struct pmic_table thermal_table[] = { + { + .address = 0x00, + .reg = 0x4F39 + }, + { + .address = 0x04, + .reg = 0x4F24 + }, + { + .address = 0x08, + .reg = 0x4F26 + }, + { + .address = 0x0c, + .reg = 0x4F3B + }, + { + .address = 0x10, + .reg = 0x4F28 + }, + { + .address = 0x14, + .reg = 0x4F2A + }, + { + .address = 0x18, + .reg = 0x4F3D + }, + { + .address = 0x1c, + .reg = 0x4F2C + }, + { + .address = 0x20, + .reg = 0x4F2E + }, + { + .address = 0x24, + .reg = 0x4F3F + }, + { + .address = 0x28, + .reg = 0x4F30 + }, + { + .address = 0x30, + .reg = 0x4F41 + }, + { + .address = 0x34, + .reg = 0x4F32 + }, + { + .address = 0x3c, + .reg = 0x4F43 + }, + { + .address = 0x40, + .reg = 0x4F34 + }, + { + .address = 0x48, + .reg = 0x4F6A, + .bit = 0, + }, + { + .address = 0x4C, + .reg = 0x4F6A, + .bit = 1 + }, + { + .address = 0x50, + .reg = 0x4F6A, + .bit = 2 + }, + { + .address = 0x54, + .reg = 0x4F6A, + .bit = 4 + }, + { + .address = 0x58, + .reg = 0x4F6A, + .bit = 5 + }, + { + .address = 0x5C, + .reg = 0x4F6A, + .bit = 3 + }, +}; + +static int intel_bxtwc_pmic_get_power(struct regmap *regmap, int reg, + int bit, u64 *value) +{ + int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + *value = (data & bit) ? 1 : 0; + return 0; +} + +static int intel_bxtwc_pmic_update_power(struct regmap *regmap, int reg, + int bit, bool on) +{ + u8 val, mask = bit; + + if (on) + val = 0xFF; + else + val = 0x0; + + return regmap_update_bits(regmap, reg, mask, val); +} + +static int intel_bxtwc_pmic_get_raw_temp(struct regmap *regmap, int reg) +{ + unsigned int val, adc_val, reg_val; + u8 temp_l, temp_h, cursrc; + unsigned long rlsb; + static const unsigned long rlsb_array[] = { + 0, 260420, 130210, 65100, 32550, 16280, + 8140, 4070, 2030, 0, 260420, 130210 }; + + if (regmap_read(regmap, reg, &val)) + return -EIO; + temp_l = (u8) val; + + if (regmap_read(regmap, (reg - 1), &val)) + return -EIO; + temp_h = (u8) val; + + reg_val = temp_l | WHISKEY_COVE_ADC_HIGH_BIT(temp_h); + cursrc = WHISKEY_COVE_ADC_CURSRC(temp_h); + rlsb = rlsb_array[cursrc]; + adc_val = reg_val * rlsb / 1000; + + return adc_val; +} + +static int +intel_bxtwc_pmic_update_aux(struct regmap *regmap, int reg, int raw) +{ + u32 bsr_num; + u16 resi_val, count = 0, thrsh = 0; + u8 alrt_h, alrt_l, cursel = 0; + + bsr_num = raw; + bsr_num /= (1 << 5); + + count = fls(bsr_num) - 1; + + cursel = clamp_t(s8, (count - 7), 0, 7); + thrsh = raw / (1 << (4 + cursel)); + + resi_val = (cursel << 9) | thrsh; + alrt_h = (resi_val >> 8) & WHISKEY_COVE_ALRT_HIGH_BIT_MASK; + if (regmap_update_bits(regmap, + reg - 1, + WHISKEY_COVE_ALRT_HIGH_BIT_MASK, + alrt_h)) + return -EIO; + + alrt_l = (u8)resi_val; + return regmap_write(regmap, reg, alrt_l); +} + +static int +intel_bxtwc_pmic_get_policy(struct regmap *regmap, int reg, int bit, u64 *value) +{ + u8 mask = BIT(bit); + unsigned int val; + + if (regmap_read(regmap, reg, &val)) + return -EIO; + + *value = (val & mask) >> bit; + return 0; +} + +static int +intel_bxtwc_pmic_update_policy(struct regmap *regmap, + int reg, int bit, int enable) +{ + u8 mask = BIT(bit), val = enable << bit; + + return regmap_update_bits(regmap, reg, mask, val); +} + +static const struct intel_pmic_opregion_data intel_bxtwc_pmic_opregion_data = { + .get_power = intel_bxtwc_pmic_get_power, + .update_power = intel_bxtwc_pmic_update_power, + .get_raw_temp = intel_bxtwc_pmic_get_raw_temp, + .update_aux = intel_bxtwc_pmic_update_aux, + .get_policy = intel_bxtwc_pmic_get_policy, + .update_policy = intel_bxtwc_pmic_update_policy, + .lpat_raw_to_temp = acpi_lpat_raw_to_temp, + .power_table = power_table, + .power_table_count = ARRAY_SIZE(power_table), + .thermal_table = thermal_table, + .thermal_table_count = ARRAY_SIZE(thermal_table), +}; + +static int intel_bxtwc_pmic_opregion_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + + return intel_pmic_install_opregion_handler(&pdev->dev, + ACPI_HANDLE(pdev->dev.parent), + pmic->regmap, + &intel_bxtwc_pmic_opregion_data); +} + +static const struct platform_device_id bxt_wc_opregion_id_table[] = { + { .name = "bxt_wcove_region" }, + {}, +}; + +static struct platform_driver intel_bxtwc_pmic_opregion_driver = { + .probe = intel_bxtwc_pmic_opregion_probe, + .driver = { + .name = "bxt_whiskey_cove_pmic", + }, + .id_table = bxt_wc_opregion_id_table, +}; +builtin_platform_driver(intel_bxtwc_pmic_opregion_driver); diff --git a/drivers/acpi/pmic/intel_pmic_bytcrc.c b/drivers/acpi/pmic/intel_pmic_bytcrc.c new file mode 100644 index 000000000..9ea79f210 --- /dev/null +++ b/drivers/acpi/pmic/intel_pmic_bytcrc.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Bay Trail Crystal Cove PMIC operation region driver + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + */ + +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include "intel_pmic.h" + +#define PWR_SOURCE_SELECT BIT(1) + +#define PMIC_A0LOCK_REG 0xc5 + +static struct pmic_table power_table[] = { +/* { + .address = 0x00, + .reg = ??, + .bit = ??, + }, ** VSYS */ + { + .address = 0x04, + .reg = 0x63, + .bit = 0x00, + }, /* SYSX -> VSYS_SX */ + { + .address = 0x08, + .reg = 0x62, + .bit = 0x00, + }, /* SYSU -> VSYS_U */ + { + .address = 0x0c, + .reg = 0x64, + .bit = 0x00, + }, /* SYSS -> VSYS_S */ + { + .address = 0x10, + .reg = 0x6a, + .bit = 0x00, + }, /* V50S -> V5P0S */ + { + .address = 0x14, + .reg = 0x6b, + .bit = 0x00, + }, /* HOST -> VHOST, USB2/3 host */ + { + .address = 0x18, + .reg = 0x6c, + .bit = 0x00, + }, /* VBUS -> VBUS, USB2/3 OTG */ + { + .address = 0x1c, + .reg = 0x6d, + .bit = 0x00, + }, /* HDMI -> VHDMI */ +/* { + .address = 0x20, + .reg = ??, + .bit = ??, + }, ** S285 */ + { + .address = 0x24, + .reg = 0x66, + .bit = 0x00, + }, /* X285 -> V2P85SX, camera */ +/* { + .address = 0x28, + .reg = ??, + .bit = ??, + }, ** V33A */ + { + .address = 0x2c, + .reg = 0x69, + .bit = 0x00, + }, /* V33S -> V3P3S, display/ssd/audio */ + { + .address = 0x30, + .reg = 0x68, + .bit = 0x00, + }, /* V33U -> V3P3U, SDIO wifi&bt */ +/* { + .address = 0x34 .. 0x40, + .reg = ??, + .bit = ??, + }, ** V33I, V18A, REFQ, V12A */ + { + .address = 0x44, + .reg = 0x5c, + .bit = 0x00, + }, /* V18S -> V1P8S, SOC/USB PHY/SIM */ + { + .address = 0x48, + .reg = 0x5d, + .bit = 0x00, + }, /* V18X -> V1P8SX, eMMC/camara/audio */ + { + .address = 0x4c, + .reg = 0x5b, + .bit = 0x00, + }, /* V18U -> V1P8U, LPDDR */ + { + .address = 0x50, + .reg = 0x61, + .bit = 0x00, + }, /* V12X -> V1P2SX, SOC SFR */ + { + .address = 0x54, + .reg = 0x60, + .bit = 0x00, + }, /* V12S -> V1P2S, MIPI */ +/* { + .address = 0x58, + .reg = ??, + .bit = ??, + }, ** V10A */ + { + .address = 0x5c, + .reg = 0x56, + .bit = 0x00, + }, /* V10S -> V1P0S, SOC GFX */ + { + .address = 0x60, + .reg = 0x57, + .bit = 0x00, + }, /* V10X -> V1P0SX, SOC display/DDR IO/PCIe */ + { + .address = 0x64, + .reg = 0x59, + .bit = 0x00, + }, /* V105 -> V1P05S, L2 SRAM */ +}; + +static struct pmic_table thermal_table[] = { + { + .address = 0x00, + .reg = 0x75 + }, + { + .address = 0x04, + .reg = 0x95 + }, + { + .address = 0x08, + .reg = 0x97 + }, + { + .address = 0x0c, + .reg = 0x77 + }, + { + .address = 0x10, + .reg = 0x9a + }, + { + .address = 0x14, + .reg = 0x9c + }, + { + .address = 0x18, + .reg = 0x79 + }, + { + .address = 0x1c, + .reg = 0x9f + }, + { + .address = 0x20, + .reg = 0xa1 + }, + { + .address = 0x48, + .reg = 0x94 + }, + { + .address = 0x4c, + .reg = 0x99 + }, + { + .address = 0x50, + .reg = 0x9e + }, +}; + +static int intel_crc_pmic_get_power(struct regmap *regmap, int reg, + int bit, u64 *value) +{ + int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + *value = (data & PWR_SOURCE_SELECT) && (data & BIT(bit)) ? 1 : 0; + return 0; +} + +static int intel_crc_pmic_update_power(struct regmap *regmap, int reg, + int bit, bool on) +{ + int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + if (on) { + data |= PWR_SOURCE_SELECT | BIT(bit); + } else { + data &= ~BIT(bit); + data |= PWR_SOURCE_SELECT; + } + + if (regmap_write(regmap, reg, data)) + return -EIO; + return 0; +} + +static int intel_crc_pmic_get_raw_temp(struct regmap *regmap, int reg) +{ + int temp_l, temp_h; + + /* + * Raw temperature value is 10bits: 8bits in reg + * and 2bits in reg-1: bit0,1 + */ + if (regmap_read(regmap, reg, &temp_l) || + regmap_read(regmap, reg - 1, &temp_h)) + return -EIO; + + return temp_l | (temp_h & 0x3) << 8; +} + +static int intel_crc_pmic_update_aux(struct regmap *regmap, int reg, int raw) +{ + return regmap_write(regmap, reg, raw) || + regmap_update_bits(regmap, reg - 1, 0x3, raw >> 8) ? -EIO : 0; +} + +static int intel_crc_pmic_get_policy(struct regmap *regmap, + int reg, int bit, u64 *value) +{ + int pen; + + if (regmap_read(regmap, reg, &pen)) + return -EIO; + *value = pen >> 7; + return 0; +} + +static int intel_crc_pmic_update_policy(struct regmap *regmap, + int reg, int bit, int enable) +{ + int alert0; + + /* Update to policy enable bit requires unlocking a0lock */ + if (regmap_read(regmap, PMIC_A0LOCK_REG, &alert0)) + return -EIO; + + if (regmap_update_bits(regmap, PMIC_A0LOCK_REG, 0x01, 0)) + return -EIO; + + if (regmap_update_bits(regmap, reg, 0x80, enable << 7)) + return -EIO; + + /* restore alert0 */ + if (regmap_write(regmap, PMIC_A0LOCK_REG, alert0)) + return -EIO; + + return 0; +} + +static const struct intel_pmic_opregion_data intel_crc_pmic_opregion_data = { + .get_power = intel_crc_pmic_get_power, + .update_power = intel_crc_pmic_update_power, + .get_raw_temp = intel_crc_pmic_get_raw_temp, + .update_aux = intel_crc_pmic_update_aux, + .get_policy = intel_crc_pmic_get_policy, + .update_policy = intel_crc_pmic_update_policy, + .lpat_raw_to_temp = acpi_lpat_raw_to_temp, + .power_table = power_table, + .power_table_count= ARRAY_SIZE(power_table), + .thermal_table = thermal_table, + .thermal_table_count = ARRAY_SIZE(thermal_table), +}; + +static int intel_crc_pmic_opregion_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + return intel_pmic_install_opregion_handler(&pdev->dev, + ACPI_HANDLE(pdev->dev.parent), pmic->regmap, + &intel_crc_pmic_opregion_data); +} + +static struct platform_driver intel_crc_pmic_opregion_driver = { + .probe = intel_crc_pmic_opregion_probe, + .driver = { + .name = "byt_crystal_cove_pmic", + }, +}; +builtin_platform_driver(intel_crc_pmic_opregion_driver); diff --git a/drivers/acpi/pmic/intel_pmic_chtcrc.c b/drivers/acpi/pmic/intel_pmic_chtcrc.c new file mode 100644 index 000000000..f9301c6f0 --- /dev/null +++ b/drivers/acpi/pmic/intel_pmic_chtcrc.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Cherry Trail Crystal Cove PMIC operation region driver + * + * Copyright (C) 2019 Hans de Goede <hdegoede@redhat.com> + */ + +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include "intel_pmic.h" + +/* + * We have no docs for the CHT Crystal Cove PMIC. The Asus Zenfone-2 kernel + * code has 2 Crystal Cove regulator drivers, one calls the PMIC a "Crystal + * Cove Plus" PMIC and talks about Cherry Trail, so presumably that one + * could be used to get register info for the regulators if we need to + * implement regulator support in the future. + * + * For now the sole purpose of this driver is to make + * intel_soc_pmic_exec_mipi_pmic_seq_element work on devices with a + * CHT Crystal Cove PMIC. + */ +static const struct intel_pmic_opregion_data intel_chtcrc_pmic_opregion_data = { + .lpat_raw_to_temp = acpi_lpat_raw_to_temp, + .pmic_i2c_address = 0x6e, +}; + +static int intel_chtcrc_pmic_opregion_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + return intel_pmic_install_opregion_handler(&pdev->dev, + ACPI_HANDLE(pdev->dev.parent), pmic->regmap, + &intel_chtcrc_pmic_opregion_data); +} + +static struct platform_driver intel_chtcrc_pmic_opregion_driver = { + .probe = intel_chtcrc_pmic_opregion_probe, + .driver = { + .name = "cht_crystal_cove_pmic", + }, +}; +builtin_platform_driver(intel_chtcrc_pmic_opregion_driver); diff --git a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c new file mode 100644 index 000000000..418eec523 --- /dev/null +++ b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Dollar Cove TI PMIC operation region driver + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * Rewritten and cleaned up + * Copyright (C) 2017 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/platform_device.h> +#include "intel_pmic.h" + +/* registers stored in 16bit BE (high:low, total 10bit) */ +#define CHTDC_TI_VBAT 0x54 +#define CHTDC_TI_DIETEMP 0x56 +#define CHTDC_TI_BPTHERM 0x58 +#define CHTDC_TI_GPADC 0x5a + +static struct pmic_table chtdc_ti_power_table[] = { + { .address = 0x00, .reg = 0x41 }, + { .address = 0x04, .reg = 0x42 }, + { .address = 0x08, .reg = 0x43 }, + { .address = 0x0c, .reg = 0x45 }, + { .address = 0x10, .reg = 0x46 }, + { .address = 0x14, .reg = 0x47 }, + { .address = 0x18, .reg = 0x48 }, + { .address = 0x1c, .reg = 0x49 }, + { .address = 0x20, .reg = 0x4a }, + { .address = 0x24, .reg = 0x4b }, + { .address = 0x28, .reg = 0x4c }, + { .address = 0x2c, .reg = 0x4d }, + { .address = 0x30, .reg = 0x4e }, +}; + +static struct pmic_table chtdc_ti_thermal_table[] = { + { + .address = 0x00, + .reg = CHTDC_TI_GPADC + }, + { + .address = 0x0c, + .reg = CHTDC_TI_GPADC + }, + /* TMP2 -> SYSTEMP */ + { + .address = 0x18, + .reg = CHTDC_TI_GPADC + }, + /* TMP3 -> BPTHERM */ + { + .address = 0x24, + .reg = CHTDC_TI_BPTHERM + }, + { + .address = 0x30, + .reg = CHTDC_TI_GPADC + }, + /* TMP5 -> DIETEMP */ + { + .address = 0x3c, + .reg = CHTDC_TI_DIETEMP + }, +}; + +static int chtdc_ti_pmic_get_power(struct regmap *regmap, int reg, int bit, + u64 *value) +{ + int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + *value = data & 1; + return 0; +} + +static int chtdc_ti_pmic_update_power(struct regmap *regmap, int reg, int bit, + bool on) +{ + return regmap_update_bits(regmap, reg, 1, on); +} + +static int chtdc_ti_pmic_get_raw_temp(struct regmap *regmap, int reg) +{ + u8 buf[2]; + + if (regmap_bulk_read(regmap, reg, buf, 2)) + return -EIO; + + /* stored in big-endian */ + return ((buf[0] & 0x03) << 8) | buf[1]; +} + +static const struct intel_pmic_opregion_data chtdc_ti_pmic_opregion_data = { + .get_power = chtdc_ti_pmic_get_power, + .update_power = chtdc_ti_pmic_update_power, + .get_raw_temp = chtdc_ti_pmic_get_raw_temp, + .lpat_raw_to_temp = acpi_lpat_raw_to_temp, + .power_table = chtdc_ti_power_table, + .power_table_count = ARRAY_SIZE(chtdc_ti_power_table), + .thermal_table = chtdc_ti_thermal_table, + .thermal_table_count = ARRAY_SIZE(chtdc_ti_thermal_table), + .pmic_i2c_address = 0x5e, +}; + +static int chtdc_ti_pmic_opregion_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + int err; + + err = intel_pmic_install_opregion_handler(&pdev->dev, + ACPI_HANDLE(pdev->dev.parent), pmic->regmap, + &chtdc_ti_pmic_opregion_data); + if (err < 0) + return err; + + /* Re-enumerate devices depending on PMIC */ + acpi_dev_clear_dependencies(ACPI_COMPANION(pdev->dev.parent)); + return 0; +} + +static const struct platform_device_id chtdc_ti_pmic_opregion_id_table[] = { + { .name = "chtdc_ti_region" }, + {}, +}; + +static struct platform_driver chtdc_ti_pmic_opregion_driver = { + .probe = chtdc_ti_pmic_opregion_probe, + .driver = { + .name = "cht_dollar_cove_ti_pmic", + }, + .id_table = chtdc_ti_pmic_opregion_id_table, +}; +builtin_platform_driver(chtdc_ti_pmic_opregion_driver); diff --git a/drivers/acpi/pmic/intel_pmic_chtwc.c b/drivers/acpi/pmic/intel_pmic_chtwc.c new file mode 100644 index 000000000..f2c42f4c7 --- /dev/null +++ b/drivers/acpi/pmic/intel_pmic_chtwc.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel CHT Whiskey Cove PMIC operation region driver + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> + * + * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: + * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. + */ + +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include "intel_pmic.h" + +#define CHT_WC_V1P05A_CTRL 0x6e3b +#define CHT_WC_V1P15_CTRL 0x6e3c +#define CHT_WC_V1P05A_VSEL 0x6e3d +#define CHT_WC_V1P15_VSEL 0x6e3e +#define CHT_WC_V1P8A_CTRL 0x6e56 +#define CHT_WC_V1P8SX_CTRL 0x6e57 +#define CHT_WC_VDDQ_CTRL 0x6e58 +#define CHT_WC_V1P2A_CTRL 0x6e59 +#define CHT_WC_V1P2SX_CTRL 0x6e5a +#define CHT_WC_V1P8A_VSEL 0x6e5b +#define CHT_WC_VDDQ_VSEL 0x6e5c +#define CHT_WC_V2P8SX_CTRL 0x6e5d +#define CHT_WC_V3P3A_CTRL 0x6e5e +#define CHT_WC_V3P3SD_CTRL 0x6e5f +#define CHT_WC_VSDIO_CTRL 0x6e67 +#define CHT_WC_V3P3A_VSEL 0x6e68 +#define CHT_WC_VPROG1A_CTRL 0x6e90 +#define CHT_WC_VPROG1B_CTRL 0x6e91 +#define CHT_WC_VPROG1F_CTRL 0x6e95 +#define CHT_WC_VPROG2D_CTRL 0x6e99 +#define CHT_WC_VPROG3A_CTRL 0x6e9a +#define CHT_WC_VPROG3B_CTRL 0x6e9b +#define CHT_WC_VPROG4A_CTRL 0x6e9c +#define CHT_WC_VPROG4B_CTRL 0x6e9d +#define CHT_WC_VPROG4C_CTRL 0x6e9e +#define CHT_WC_VPROG4D_CTRL 0x6e9f +#define CHT_WC_VPROG5A_CTRL 0x6ea0 +#define CHT_WC_VPROG5B_CTRL 0x6ea1 +#define CHT_WC_VPROG6A_CTRL 0x6ea2 +#define CHT_WC_VPROG6B_CTRL 0x6ea3 +#define CHT_WC_VPROG1A_VSEL 0x6ec0 +#define CHT_WC_VPROG1B_VSEL 0x6ec1 +#define CHT_WC_V1P8SX_VSEL 0x6ec2 +#define CHT_WC_V1P2SX_VSEL 0x6ec3 +#define CHT_WC_V1P2A_VSEL 0x6ec4 +#define CHT_WC_VPROG1F_VSEL 0x6ec5 +#define CHT_WC_VSDIO_VSEL 0x6ec6 +#define CHT_WC_V2P8SX_VSEL 0x6ec7 +#define CHT_WC_V3P3SD_VSEL 0x6ec8 +#define CHT_WC_VPROG2D_VSEL 0x6ec9 +#define CHT_WC_VPROG3A_VSEL 0x6eca +#define CHT_WC_VPROG3B_VSEL 0x6ecb +#define CHT_WC_VPROG4A_VSEL 0x6ecc +#define CHT_WC_VPROG4B_VSEL 0x6ecd +#define CHT_WC_VPROG4C_VSEL 0x6ece +#define CHT_WC_VPROG4D_VSEL 0x6ecf +#define CHT_WC_VPROG5A_VSEL 0x6ed0 +#define CHT_WC_VPROG5B_VSEL 0x6ed1 +#define CHT_WC_VPROG6A_VSEL 0x6ed2 +#define CHT_WC_VPROG6B_VSEL 0x6ed3 + +/* + * Regulator support is based on the non upstream patch: + * "regulator: whiskey_cove: implements Whiskey Cove pmic VRF support" + * https://github.com/intel-aero/meta-intel-aero/blob/master/recipes-kernel/linux/linux-yocto/0019-regulator-whiskey_cove-implements-WhiskeyCove-pmic-V.patch + */ +static struct pmic_table power_table[] = { + { + .address = 0x0, + .reg = CHT_WC_V1P8A_CTRL, + .bit = 0x01, + }, /* V18A */ + { + .address = 0x04, + .reg = CHT_WC_V1P8SX_CTRL, + .bit = 0x07, + }, /* V18X */ + { + .address = 0x08, + .reg = CHT_WC_VDDQ_CTRL, + .bit = 0x01, + }, /* VDDQ */ + { + .address = 0x0c, + .reg = CHT_WC_V1P2A_CTRL, + .bit = 0x07, + }, /* V12A */ + { + .address = 0x10, + .reg = CHT_WC_V1P2SX_CTRL, + .bit = 0x07, + }, /* V12X */ + { + .address = 0x14, + .reg = CHT_WC_V2P8SX_CTRL, + .bit = 0x07, + }, /* V28X */ + { + .address = 0x18, + .reg = CHT_WC_V3P3A_CTRL, + .bit = 0x01, + }, /* V33A */ + { + .address = 0x1c, + .reg = CHT_WC_V3P3SD_CTRL, + .bit = 0x07, + }, /* V3SD */ + { + .address = 0x20, + .reg = CHT_WC_VSDIO_CTRL, + .bit = 0x07, + }, /* VSD */ +/* { + .address = 0x24, + .reg = ??, + .bit = ??, + }, ** VSW2 */ +/* { + .address = 0x28, + .reg = ??, + .bit = ??, + }, ** VSW1 */ +/* { + .address = 0x2c, + .reg = ??, + .bit = ??, + }, ** VUPY */ +/* { + .address = 0x30, + .reg = ??, + .bit = ??, + }, ** VRSO */ + { + .address = 0x34, + .reg = CHT_WC_VPROG1A_CTRL, + .bit = 0x07, + }, /* VP1A */ + { + .address = 0x38, + .reg = CHT_WC_VPROG1B_CTRL, + .bit = 0x07, + }, /* VP1B */ + { + .address = 0x3c, + .reg = CHT_WC_VPROG1F_CTRL, + .bit = 0x07, + }, /* VP1F */ + { + .address = 0x40, + .reg = CHT_WC_VPROG2D_CTRL, + .bit = 0x07, + }, /* VP2D */ + { + .address = 0x44, + .reg = CHT_WC_VPROG3A_CTRL, + .bit = 0x07, + }, /* VP3A */ + { + .address = 0x48, + .reg = CHT_WC_VPROG3B_CTRL, + .bit = 0x07, + }, /* VP3B */ + { + .address = 0x4c, + .reg = CHT_WC_VPROG4A_CTRL, + .bit = 0x07, + }, /* VP4A */ + { + .address = 0x50, + .reg = CHT_WC_VPROG4B_CTRL, + .bit = 0x07, + }, /* VP4B */ + { + .address = 0x54, + .reg = CHT_WC_VPROG4C_CTRL, + .bit = 0x07, + }, /* VP4C */ + { + .address = 0x58, + .reg = CHT_WC_VPROG4D_CTRL, + .bit = 0x07, + }, /* VP4D */ + { + .address = 0x5c, + .reg = CHT_WC_VPROG5A_CTRL, + .bit = 0x07, + }, /* VP5A */ + { + .address = 0x60, + .reg = CHT_WC_VPROG5B_CTRL, + .bit = 0x07, + }, /* VP5B */ + { + .address = 0x64, + .reg = CHT_WC_VPROG6A_CTRL, + .bit = 0x07, + }, /* VP6A */ + { + .address = 0x68, + .reg = CHT_WC_VPROG6B_CTRL, + .bit = 0x07, + }, /* VP6B */ +/* { + .address = 0x6c, + .reg = ??, + .bit = ??, + } ** VP7A */ +}; + +static int intel_cht_wc_pmic_get_power(struct regmap *regmap, int reg, + int bit, u64 *value) +{ + int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + *value = (data & bit) ? 1 : 0; + return 0; +} + +static int intel_cht_wc_pmic_update_power(struct regmap *regmap, int reg, + int bitmask, bool on) +{ + return regmap_update_bits(regmap, reg, bitmask, on ? 1 : 0); +} + +static int intel_cht_wc_exec_mipi_pmic_seq_element(struct regmap *regmap, + u16 i2c_client_address, + u32 reg_address, + u32 value, u32 mask) +{ + u32 address; + + if (i2c_client_address > 0xff || reg_address > 0xff) { + pr_warn("%s warning addresses too big client 0x%x reg 0x%x\n", + __func__, i2c_client_address, reg_address); + return -ERANGE; + } + + address = (i2c_client_address << 8) | reg_address; + + return regmap_update_bits(regmap, address, mask, value); +} + +/* + * The thermal table and ops are empty, we do not support the Thermal opregion + * (DPTF) due to lacking documentation. + */ +static const struct intel_pmic_opregion_data intel_cht_wc_pmic_opregion_data = { + .get_power = intel_cht_wc_pmic_get_power, + .update_power = intel_cht_wc_pmic_update_power, + .exec_mipi_pmic_seq_element = intel_cht_wc_exec_mipi_pmic_seq_element, + .lpat_raw_to_temp = acpi_lpat_raw_to_temp, + .power_table = power_table, + .power_table_count = ARRAY_SIZE(power_table), +}; + +static int intel_cht_wc_pmic_opregion_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + + return intel_pmic_install_opregion_handler(&pdev->dev, + ACPI_HANDLE(pdev->dev.parent), + pmic->regmap, + &intel_cht_wc_pmic_opregion_data); +} + +static const struct platform_device_id cht_wc_opregion_id_table[] = { + { .name = "cht_wcove_region" }, + {}, +}; + +static struct platform_driver intel_cht_wc_pmic_opregion_driver = { + .probe = intel_cht_wc_pmic_opregion_probe, + .driver = { + .name = "cht_whiskey_cove_pmic", + }, + .id_table = cht_wc_opregion_id_table, +}; +builtin_platform_driver(intel_cht_wc_pmic_opregion_driver); diff --git a/drivers/acpi/pmic/intel_pmic_xpower.c b/drivers/acpi/pmic/intel_pmic_xpower.c new file mode 100644 index 000000000..61bbe4c24 --- /dev/null +++ b/drivers/acpi/pmic/intel_pmic_xpower.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * XPower AXP288 PMIC operation region driver + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + */ + +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/mfd/axp20x.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <asm/iosf_mbi.h> +#include "intel_pmic.h" + +#define XPOWER_GPADC_LOW 0x5b +#define XPOWER_GPI1_CTRL 0x92 + +#define GPI1_LDO_MASK GENMASK(2, 0) +#define GPI1_LDO_ON (3 << 0) +#define GPI1_LDO_OFF (4 << 0) + +#define AXP288_ADC_TS_CURRENT_ON_OFF_MASK GENMASK(1, 0) +#define AXP288_ADC_TS_CURRENT_OFF (0 << 0) +#define AXP288_ADC_TS_CURRENT_ON_WHEN_CHARGING (1 << 0) +#define AXP288_ADC_TS_CURRENT_ON_ONDEMAND (2 << 0) +#define AXP288_ADC_TS_CURRENT_ON (3 << 0) + +static struct pmic_table power_table[] = { + { + .address = 0x00, + .reg = 0x13, + .bit = 0x05, + }, /* ALD1 */ + { + .address = 0x04, + .reg = 0x13, + .bit = 0x06, + }, /* ALD2 */ + { + .address = 0x08, + .reg = 0x13, + .bit = 0x07, + }, /* ALD3 */ + { + .address = 0x0c, + .reg = 0x12, + .bit = 0x03, + }, /* DLD1 */ + { + .address = 0x10, + .reg = 0x12, + .bit = 0x04, + }, /* DLD2 */ + { + .address = 0x14, + .reg = 0x12, + .bit = 0x05, + }, /* DLD3 */ + { + .address = 0x18, + .reg = 0x12, + .bit = 0x06, + }, /* DLD4 */ + { + .address = 0x1c, + .reg = 0x12, + .bit = 0x00, + }, /* ELD1 */ + { + .address = 0x20, + .reg = 0x12, + .bit = 0x01, + }, /* ELD2 */ + { + .address = 0x24, + .reg = 0x12, + .bit = 0x02, + }, /* ELD3 */ + { + .address = 0x28, + .reg = 0x13, + .bit = 0x02, + }, /* FLD1 */ + { + .address = 0x2c, + .reg = 0x13, + .bit = 0x03, + }, /* FLD2 */ + { + .address = 0x30, + .reg = 0x13, + .bit = 0x04, + }, /* FLD3 */ + { + .address = 0x34, + .reg = 0x10, + .bit = 0x03, + }, /* BUC1 */ + { + .address = 0x38, + .reg = 0x10, + .bit = 0x06, + }, /* BUC2 */ + { + .address = 0x3c, + .reg = 0x10, + .bit = 0x05, + }, /* BUC3 */ + { + .address = 0x40, + .reg = 0x10, + .bit = 0x04, + }, /* BUC4 */ + { + .address = 0x44, + .reg = 0x10, + .bit = 0x01, + }, /* BUC5 */ + { + .address = 0x48, + .reg = 0x10, + .bit = 0x00 + }, /* BUC6 */ + { + .address = 0x4c, + .reg = 0x92, + }, /* GPI1 */ +}; + +/* TMP0 - TMP5 are the same, all from GPADC */ +static struct pmic_table thermal_table[] = { + { + .address = 0x00, + .reg = XPOWER_GPADC_LOW + }, + { + .address = 0x0c, + .reg = XPOWER_GPADC_LOW + }, + { + .address = 0x18, + .reg = XPOWER_GPADC_LOW + }, + { + .address = 0x24, + .reg = XPOWER_GPADC_LOW + }, + { + .address = 0x30, + .reg = XPOWER_GPADC_LOW + }, + { + .address = 0x3c, + .reg = XPOWER_GPADC_LOW + }, +}; + +static int intel_xpower_pmic_get_power(struct regmap *regmap, int reg, + int bit, u64 *value) +{ + int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + /* GPIO1 LDO regulator needs special handling */ + if (reg == XPOWER_GPI1_CTRL) + *value = ((data & GPI1_LDO_MASK) == GPI1_LDO_ON); + else + *value = (data & BIT(bit)) ? 1 : 0; + + return 0; +} + +static int intel_xpower_pmic_update_power(struct regmap *regmap, int reg, + int bit, bool on) +{ + int data, ret; + + ret = iosf_mbi_block_punit_i2c_access(); + if (ret) + return ret; + + /* GPIO1 LDO regulator needs special handling */ + if (reg == XPOWER_GPI1_CTRL) { + ret = regmap_update_bits(regmap, reg, GPI1_LDO_MASK, + on ? GPI1_LDO_ON : GPI1_LDO_OFF); + goto out; + } + + if (regmap_read(regmap, reg, &data)) { + ret = -EIO; + goto out; + } + + if (on) + data |= BIT(bit); + else + data &= ~BIT(bit); + + if (regmap_write(regmap, reg, data)) + ret = -EIO; +out: + iosf_mbi_unblock_punit_i2c_access(); + + return ret; +} + +/** + * intel_xpower_pmic_get_raw_temp(): Get raw temperature reading from the PMIC + * + * @regmap: regmap of the PMIC device + * @reg: register to get the reading + * + * Return a positive value on success, errno on failure. + */ +static int intel_xpower_pmic_get_raw_temp(struct regmap *regmap, int reg) +{ + int ret, adc_ts_pin_ctrl; + u8 buf[2]; + + /* + * The current-source used for the battery temp-sensor (TS) is shared + * with the GPADC. For proper fuel-gauge and charger operation the TS + * current-source needs to be permanently on. But to read the GPADC we + * need to temporary switch the TS current-source to ondemand, so that + * the GPADC can use it, otherwise we will always read an all 0 value. + * + * Note that the switching from on to on-ondemand is not necessary + * when the TS current-source is off (this happens on devices which + * do not use the TS-pin). + */ + ret = regmap_read(regmap, AXP288_ADC_TS_PIN_CTRL, &adc_ts_pin_ctrl); + if (ret) + return ret; + + if (adc_ts_pin_ctrl & AXP288_ADC_TS_CURRENT_ON_OFF_MASK) { + /* + * AXP288_ADC_TS_PIN_CTRL reads are cached by the regmap, so + * this does to a single I2C-transfer, and thus there is no + * need to explicitly call iosf_mbi_block_punit_i2c_access(). + */ + ret = regmap_update_bits(regmap, AXP288_ADC_TS_PIN_CTRL, + AXP288_ADC_TS_CURRENT_ON_OFF_MASK, + AXP288_ADC_TS_CURRENT_ON_ONDEMAND); + if (ret) + return ret; + + /* Wait a bit after switching the current-source */ + usleep_range(6000, 10000); + } + + ret = iosf_mbi_block_punit_i2c_access(); + if (ret) + return ret; + + ret = regmap_bulk_read(regmap, AXP288_GP_ADC_H, buf, 2); + if (ret == 0) + ret = (buf[0] << 4) + ((buf[1] >> 4) & 0x0f); + + if (adc_ts_pin_ctrl & AXP288_ADC_TS_CURRENT_ON_OFF_MASK) { + regmap_update_bits(regmap, AXP288_ADC_TS_PIN_CTRL, + AXP288_ADC_TS_CURRENT_ON_OFF_MASK, + AXP288_ADC_TS_CURRENT_ON); + } + + iosf_mbi_unblock_punit_i2c_access(); + + return ret; +} + +static int intel_xpower_exec_mipi_pmic_seq_element(struct regmap *regmap, + u16 i2c_address, u32 reg_address, + u32 value, u32 mask) +{ + int ret; + + if (i2c_address != 0x34) { + 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); + return -ENXIO; + } + + ret = iosf_mbi_block_punit_i2c_access(); + if (ret) + return ret; + + ret = regmap_update_bits(regmap, reg_address, mask, value); + + iosf_mbi_unblock_punit_i2c_access(); + + return ret; +} + +static int intel_xpower_lpat_raw_to_temp(struct acpi_lpat_conversion_table *lpat_table, + int raw) +{ + struct acpi_lpat first = lpat_table->lpat[0]; + struct acpi_lpat last = lpat_table->lpat[lpat_table->lpat_count - 1]; + + /* + * Some LPAT tables in the ACPI Device for the AXP288 PMIC for some + * reason only describe a small temperature range, e.g. 27° - 37° + * Celcius. Resulting in errors when the tablet is idle in a cool room. + * + * To avoid these errors clamp the raw value to be inside the LPAT. + */ + if (first.raw < last.raw) + raw = clamp(raw, first.raw, last.raw); + else + raw = clamp(raw, last.raw, first.raw); + + return acpi_lpat_raw_to_temp(lpat_table, raw); +} + +static const struct intel_pmic_opregion_data intel_xpower_pmic_opregion_data = { + .get_power = intel_xpower_pmic_get_power, + .update_power = intel_xpower_pmic_update_power, + .get_raw_temp = intel_xpower_pmic_get_raw_temp, + .exec_mipi_pmic_seq_element = intel_xpower_exec_mipi_pmic_seq_element, + .lpat_raw_to_temp = intel_xpower_lpat_raw_to_temp, + .power_table = power_table, + .power_table_count = ARRAY_SIZE(power_table), + .thermal_table = thermal_table, + .thermal_table_count = ARRAY_SIZE(thermal_table), + .pmic_i2c_address = 0x34, +}; + +static acpi_status intel_xpower_pmic_gpio_handler(u32 function, + acpi_physical_address address, u32 bit_width, u64 *value, + void *handler_context, void *region_context) +{ + return AE_OK; +} + +static int intel_xpower_pmic_opregion_probe(struct platform_device *pdev) +{ + struct device *parent = pdev->dev.parent; + struct axp20x_dev *axp20x = dev_get_drvdata(parent); + acpi_status status; + int result; + + status = acpi_install_address_space_handler(ACPI_HANDLE(parent), + ACPI_ADR_SPACE_GPIO, intel_xpower_pmic_gpio_handler, + NULL, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + + result = intel_pmic_install_opregion_handler(&pdev->dev, + ACPI_HANDLE(parent), axp20x->regmap, + &intel_xpower_pmic_opregion_data); + if (result) + acpi_remove_address_space_handler(ACPI_HANDLE(parent), + ACPI_ADR_SPACE_GPIO, + intel_xpower_pmic_gpio_handler); + + return result; +} + +static struct platform_driver intel_xpower_pmic_opregion_driver = { + .probe = intel_xpower_pmic_opregion_probe, + .driver = { + .name = "axp288_pmic_acpi", + }, +}; +builtin_platform_driver(intel_xpower_pmic_opregion_driver); diff --git a/drivers/acpi/pmic/tps68470_pmic.c b/drivers/acpi/pmic/tps68470_pmic.c new file mode 100644 index 000000000..ebd03e472 --- /dev/null +++ b/drivers/acpi/pmic/tps68470_pmic.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI TPS68470 PMIC operation region driver + * + * Copyright (C) 2017 Intel Corporation. All rights reserved. + * + * Author: Rajmohan Mani <rajmohan.mani@intel.com> + * + * Based on drivers/acpi/pmic/intel_pmic* drivers + */ + +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/mfd/tps68470.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +struct tps68470_pmic_table { + u32 address; /* operation region address */ + u32 reg; /* corresponding register */ + u32 bitmask; /* bit mask for power, clock */ +}; + +#define TI_PMIC_POWER_OPREGION_ID 0xB0 +#define TI_PMIC_VR_VAL_OPREGION_ID 0xB1 +#define TI_PMIC_CLOCK_OPREGION_ID 0xB2 +#define TI_PMIC_CLKFREQ_OPREGION_ID 0xB3 + +struct tps68470_pmic_opregion { + struct mutex lock; + struct regmap *regmap; +}; + +#define S_IO_I2C_EN (BIT(0) | BIT(1)) + +static const struct tps68470_pmic_table power_table[] = { + { + .address = 0x00, + .reg = TPS68470_REG_S_I2C_CTL, + .bitmask = S_IO_I2C_EN, + /* S_I2C_CTL */ + }, + { + .address = 0x04, + .reg = TPS68470_REG_VCMCTL, + .bitmask = BIT(0), + /* VCMCTL */ + }, + { + .address = 0x08, + .reg = TPS68470_REG_VAUX1CTL, + .bitmask = BIT(0), + /* VAUX1_CTL */ + }, + { + .address = 0x0C, + .reg = TPS68470_REG_VAUX2CTL, + .bitmask = BIT(0), + /* VAUX2CTL */ + }, + { + .address = 0x10, + .reg = TPS68470_REG_VACTL, + .bitmask = BIT(0), + /* VACTL */ + }, + { + .address = 0x14, + .reg = TPS68470_REG_VDCTL, + .bitmask = BIT(0), + /* VDCTL */ + }, +}; + +/* Table to set voltage regulator value */ +static const struct tps68470_pmic_table vr_val_table[] = { + { + .address = 0x00, + .reg = TPS68470_REG_VSIOVAL, + .bitmask = TPS68470_VSIOVAL_IOVOLT_MASK, + /* TPS68470_REG_VSIOVAL */ + }, + { + .address = 0x04, + .reg = TPS68470_REG_VIOVAL, + .bitmask = TPS68470_VIOVAL_IOVOLT_MASK, + /* TPS68470_REG_VIOVAL */ + }, + { + .address = 0x08, + .reg = TPS68470_REG_VCMVAL, + .bitmask = TPS68470_VCMVAL_VCVOLT_MASK, + /* TPS68470_REG_VCMVAL */ + }, + { + .address = 0x0C, + .reg = TPS68470_REG_VAUX1VAL, + .bitmask = TPS68470_VAUX1VAL_AUX1VOLT_MASK, + /* TPS68470_REG_VAUX1VAL */ + }, + { + .address = 0x10, + .reg = TPS68470_REG_VAUX2VAL, + .bitmask = TPS68470_VAUX2VAL_AUX2VOLT_MASK, + /* TPS68470_REG_VAUX2VAL */ + }, + { + .address = 0x14, + .reg = TPS68470_REG_VAVAL, + .bitmask = TPS68470_VAVAL_AVOLT_MASK, + /* TPS68470_REG_VAVAL */ + }, + { + .address = 0x18, + .reg = TPS68470_REG_VDVAL, + .bitmask = TPS68470_VDVAL_DVOLT_MASK, + /* TPS68470_REG_VDVAL */ + }, +}; + +/* Table to configure clock frequency */ +static const struct tps68470_pmic_table clk_freq_table[] = { + { + .address = 0x00, + .reg = TPS68470_REG_POSTDIV2, + .bitmask = BIT(0) | BIT(1), + /* TPS68470_REG_POSTDIV2 */ + }, + { + .address = 0x04, + .reg = TPS68470_REG_BOOSTDIV, + .bitmask = 0x1F, + /* TPS68470_REG_BOOSTDIV */ + }, + { + .address = 0x08, + .reg = TPS68470_REG_BUCKDIV, + .bitmask = 0x0F, + /* TPS68470_REG_BUCKDIV */ + }, + { + .address = 0x0C, + .reg = TPS68470_REG_PLLSWR, + .bitmask = 0x13, + /* TPS68470_REG_PLLSWR */ + }, + { + .address = 0x10, + .reg = TPS68470_REG_XTALDIV, + .bitmask = 0xFF, + /* TPS68470_REG_XTALDIV */ + }, + { + .address = 0x14, + .reg = TPS68470_REG_PLLDIV, + .bitmask = 0xFF, + /* TPS68470_REG_PLLDIV */ + }, + { + .address = 0x18, + .reg = TPS68470_REG_POSTDIV, + .bitmask = 0x83, + /* TPS68470_REG_POSTDIV */ + }, +}; + +/* Table to configure and enable clocks */ +static const struct tps68470_pmic_table clk_table[] = { + { + .address = 0x00, + .reg = TPS68470_REG_PLLCTL, + .bitmask = 0xF5, + /* TPS68470_REG_PLLCTL */ + }, + { + .address = 0x04, + .reg = TPS68470_REG_PLLCTL2, + .bitmask = BIT(0), + /* TPS68470_REG_PLLCTL2 */ + }, + { + .address = 0x08, + .reg = TPS68470_REG_CLKCFG1, + .bitmask = TPS68470_CLKCFG1_MODE_A_MASK | + TPS68470_CLKCFG1_MODE_B_MASK, + /* TPS68470_REG_CLKCFG1 */ + }, + { + .address = 0x0C, + .reg = TPS68470_REG_CLKCFG2, + .bitmask = TPS68470_CLKCFG1_MODE_A_MASK | + TPS68470_CLKCFG1_MODE_B_MASK, + /* TPS68470_REG_CLKCFG2 */ + }, +}; + +static int pmic_get_reg_bit(u64 address, + const struct tps68470_pmic_table *table, + const unsigned int table_size, int *reg, + int *bitmask) +{ + u64 i; + + i = address / 4; + if (i >= table_size) + return -ENOENT; + + if (!reg || !bitmask) + return -EINVAL; + + *reg = table[i].reg; + *bitmask = table[i].bitmask; + + return 0; +} + +static int tps68470_pmic_get_power(struct regmap *regmap, int reg, + int bitmask, u64 *value) +{ + unsigned int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + *value = (data & bitmask) ? 1 : 0; + return 0; +} + +static int tps68470_pmic_get_vr_val(struct regmap *regmap, int reg, + int bitmask, u64 *value) +{ + unsigned int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + *value = data & bitmask; + return 0; +} + +static int tps68470_pmic_get_clk(struct regmap *regmap, int reg, + int bitmask, u64 *value) +{ + unsigned int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + *value = (data & bitmask) ? 1 : 0; + return 0; +} + +static int tps68470_pmic_get_clk_freq(struct regmap *regmap, int reg, + int bitmask, u64 *value) +{ + unsigned int data; + + if (regmap_read(regmap, reg, &data)) + return -EIO; + + *value = data & bitmask; + return 0; +} + +static int ti_tps68470_regmap_update_bits(struct regmap *regmap, int reg, + int bitmask, u64 value) +{ + return regmap_update_bits(regmap, reg, bitmask, value); +} + +static acpi_status tps68470_pmic_common_handler(u32 function, + acpi_physical_address address, + u32 bits, u64 *value, + void *region_context, + int (*get)(struct regmap *, + int, int, u64 *), + int (*update)(struct regmap *, + int, int, u64), + const struct tps68470_pmic_table *tbl, + unsigned int tbl_size) +{ + struct tps68470_pmic_opregion *opregion = region_context; + struct regmap *regmap = opregion->regmap; + int reg, ret, bitmask; + + if (bits != 32) + return AE_BAD_PARAMETER; + + ret = pmic_get_reg_bit(address, tbl, tbl_size, ®, &bitmask); + if (ret < 0) + return AE_BAD_PARAMETER; + + if (function == ACPI_WRITE && *value > bitmask) + return AE_BAD_PARAMETER; + + mutex_lock(&opregion->lock); + + ret = (function == ACPI_READ) ? + get(regmap, reg, bitmask, value) : + update(regmap, reg, bitmask, *value); + + mutex_unlock(&opregion->lock); + + return ret ? AE_ERROR : AE_OK; +} + +static acpi_status tps68470_pmic_cfreq_handler(u32 function, + acpi_physical_address address, + u32 bits, u64 *value, + void *handler_context, + void *region_context) +{ + return tps68470_pmic_common_handler(function, address, bits, value, + region_context, + tps68470_pmic_get_clk_freq, + ti_tps68470_regmap_update_bits, + clk_freq_table, + ARRAY_SIZE(clk_freq_table)); +} + +static acpi_status tps68470_pmic_clk_handler(u32 function, + acpi_physical_address address, u32 bits, + u64 *value, void *handler_context, + void *region_context) +{ + return tps68470_pmic_common_handler(function, address, bits, value, + region_context, + tps68470_pmic_get_clk, + ti_tps68470_regmap_update_bits, + clk_table, + ARRAY_SIZE(clk_table)); +} + +static acpi_status tps68470_pmic_vrval_handler(u32 function, + acpi_physical_address address, + u32 bits, u64 *value, + void *handler_context, + void *region_context) +{ + return tps68470_pmic_common_handler(function, address, bits, value, + region_context, + tps68470_pmic_get_vr_val, + ti_tps68470_regmap_update_bits, + vr_val_table, + ARRAY_SIZE(vr_val_table)); +} + +static acpi_status tps68470_pmic_pwr_handler(u32 function, + acpi_physical_address address, + u32 bits, u64 *value, + void *handler_context, + void *region_context) +{ + if (bits != 32) + return AE_BAD_PARAMETER; + + /* set/clear for bit 0, bits 0 and 1 together */ + if (function == ACPI_WRITE && + !(*value == 0 || *value == 1 || *value == 3)) { + return AE_BAD_PARAMETER; + } + + return tps68470_pmic_common_handler(function, address, bits, value, + region_context, + tps68470_pmic_get_power, + ti_tps68470_regmap_update_bits, + power_table, + ARRAY_SIZE(power_table)); +} + +static int tps68470_pmic_opregion_probe(struct platform_device *pdev) +{ + struct regmap *tps68470_regmap = dev_get_drvdata(pdev->dev.parent); + acpi_handle handle = ACPI_HANDLE(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct tps68470_pmic_opregion *opregion; + acpi_status status; + + if (!dev || !tps68470_regmap) { + dev_warn(dev, "dev or regmap is NULL\n"); + return -EINVAL; + } + + if (!handle) { + dev_warn(dev, "acpi handle is NULL\n"); + return -ENODEV; + } + + opregion = devm_kzalloc(dev, sizeof(*opregion), GFP_KERNEL); + if (!opregion) + return -ENOMEM; + + mutex_init(&opregion->lock); + opregion->regmap = tps68470_regmap; + + status = acpi_install_address_space_handler(handle, + TI_PMIC_POWER_OPREGION_ID, + tps68470_pmic_pwr_handler, + NULL, opregion); + if (ACPI_FAILURE(status)) + goto out_mutex_destroy; + + status = acpi_install_address_space_handler(handle, + TI_PMIC_VR_VAL_OPREGION_ID, + tps68470_pmic_vrval_handler, + NULL, opregion); + if (ACPI_FAILURE(status)) + goto out_remove_power_handler; + + status = acpi_install_address_space_handler(handle, + TI_PMIC_CLOCK_OPREGION_ID, + tps68470_pmic_clk_handler, + NULL, opregion); + if (ACPI_FAILURE(status)) + goto out_remove_vr_val_handler; + + status = acpi_install_address_space_handler(handle, + TI_PMIC_CLKFREQ_OPREGION_ID, + tps68470_pmic_cfreq_handler, + NULL, opregion); + if (ACPI_FAILURE(status)) + goto out_remove_clk_handler; + + return 0; + +out_remove_clk_handler: + acpi_remove_address_space_handler(handle, TI_PMIC_CLOCK_OPREGION_ID, + tps68470_pmic_clk_handler); +out_remove_vr_val_handler: + acpi_remove_address_space_handler(handle, TI_PMIC_VR_VAL_OPREGION_ID, + tps68470_pmic_vrval_handler); +out_remove_power_handler: + acpi_remove_address_space_handler(handle, TI_PMIC_POWER_OPREGION_ID, + tps68470_pmic_pwr_handler); +out_mutex_destroy: + mutex_destroy(&opregion->lock); + return -ENODEV; +} + +static struct platform_driver tps68470_pmic_opregion_driver = { + .probe = tps68470_pmic_opregion_probe, + .driver = { + .name = "tps68470_pmic_opregion", + }, +}; + +builtin_platform_driver(tps68470_pmic_opregion_driver) |