Commit c2101d01 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull more ACPI updates from Rafael Wysocki:
 "Rework the handling of the P-unit semaphore on Intel Baytrail and
  Cherrytrail systems to avoid race conditions and excessive overhead
  related to it (Hans de Goede)"

* tag 'acpi-4.20-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm:
  ACPI / PMIC: xpower: Add depends on IOSF_MBI to Kconfig entry
  i2c: designware: Cleanup bus lock handling
  ACPI / PMIC: xpower: Block P-Unit I2C access during read-modify-write
  x86: baytrail/cherrytrail: Rework and move P-Unit PMIC bus semaphore code
parents 6ef74676 6a9b593d
Loading
Loading
Loading
Loading
+29 −10
Original line number Diff line number Diff line
@@ -105,8 +105,10 @@ int iosf_mbi_modify(u8 port, u8 opcode, u32 offset, u32 mdr, u32 mask);
 * the PMIC bus while another driver is also accessing the PMIC bus various bad
 * things happen.
 *
 * To avoid these problems this function must be called before accessing the
 * P-Unit or the PMIC, be it through iosf_mbi* functions or through other means.
 * Call this function before sending requests to the P-Unit which may make it
 * access the PMIC, be it through iosf_mbi* functions or through other means.
 * This function will block all kernel access to the PMIC I2C bus, so that the
 * P-Unit can safely access the PMIC over the shared I2C bus.
 *
 * Note on these systems the i2c-bus driver will request a sempahore from the
 * P-Unit for exclusive access to the PMIC bus when i2c drivers are accessing
@@ -122,6 +124,31 @@ void iosf_mbi_punit_acquire(void);
 */
void iosf_mbi_punit_release(void);

/**
 * iosf_mbi_block_punit_i2c_access() - Block P-Unit accesses to the PMIC bus
 *
 * Call this function to block P-Unit access to the PMIC I2C bus, so that the
 * kernel can safely access the PMIC over the shared I2C bus.
 *
 * This function acquires the P-Unit bus semaphore and notifies
 * pmic_bus_access_notifier listeners that they may no longer access the
 * P-Unit in a way which may cause it to access the shared I2C bus.
 *
 * Note this function may be called multiple times and the bus will not
 * be released until iosf_mbi_unblock_punit_i2c_access() has been called the
 * same amount of times.
 *
 * Return: Nonzero on error
 */
int iosf_mbi_block_punit_i2c_access(void);

/*
 * iosf_mbi_unblock_punit_i2c_access() - Release PMIC I2C bus block
 *
 * Release i2c access block gotten through iosf_mbi_block_punit_i2c_access().
 */
void iosf_mbi_unblock_punit_i2c_access(void);

/**
 * iosf_mbi_register_pmic_bus_access_notifier - Register PMIC bus notifier
 *
@@ -158,14 +185,6 @@ int iosf_mbi_unregister_pmic_bus_access_notifier(struct notifier_block *nb);
int iosf_mbi_unregister_pmic_bus_access_notifier_unlocked(
	struct notifier_block *nb);

/**
 * iosf_mbi_call_pmic_bus_access_notifier_chain - Call PMIC bus notifier chain
 *
 * @val: action to pass into listener's notifier_call function
 * @v: data pointer to pass into listener's notifier_call function
 */
int iosf_mbi_call_pmic_bus_access_notifier_chain(unsigned long val, void *v);

/**
 * iosf_mbi_assert_punit_acquired - Assert that the P-Unit has been acquired.
 */
+199 −18
Original line number Diff line number Diff line
@@ -18,24 +18,26 @@
 * enumerate the device using PCI.
 */

#include <linux/delay.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/pci.h>
#include <linux/debugfs.h>
#include <linux/capability.h>
#include <linux/pm_qos.h>

#include <asm/iosf_mbi.h>

#define PCI_DEVICE_ID_BAYTRAIL		0x0F00
#define PCI_DEVICE_ID_BRASWELL		0x2280
#define PCI_DEVICE_ID_QUARK_X1000	0x0958
#define PCI_DEVICE_ID_TANGIER		0x1170
#define PCI_DEVICE_ID_INTEL_BAYTRAIL		0x0F00
#define PCI_DEVICE_ID_INTEL_BRASWELL		0x2280
#define PCI_DEVICE_ID_INTEL_QUARK_X1000		0x0958
#define PCI_DEVICE_ID_INTEL_TANGIER		0x1170

static struct pci_dev *mbi_pdev;
static DEFINE_SPINLOCK(iosf_mbi_lock);
static DEFINE_MUTEX(iosf_mbi_punit_mutex);
static BLOCKING_NOTIFIER_HEAD(iosf_mbi_pmic_bus_access_notifier);

/**************** Generic iosf_mbi access helpers ****************/

static inline u32 iosf_mbi_form_mcr(u8 op, u8 port, u8 offset)
{
@@ -192,6 +194,30 @@ bool iosf_mbi_available(void)
}
EXPORT_SYMBOL(iosf_mbi_available);

/*
 **************** P-Unit/kernel shared I2C bus arbritration ****************
 *
 * Some Bay Trail and Cherry Trail devices have the P-Unit and us (the kernel)
 * share a single I2C bus to the PMIC. Below are helpers to arbitrate the
 * accesses between the kernel and the P-Unit.
 *
 * See arch/x86/include/asm/iosf_mbi.h for kernel-doc text for each function.
 */

#define SEMAPHORE_TIMEOUT		500
#define PUNIT_SEMAPHORE_BYT		0x7
#define PUNIT_SEMAPHORE_CHT		0x10e
#define PUNIT_SEMAPHORE_BIT		BIT(0)
#define PUNIT_SEMAPHORE_ACQUIRE		BIT(1)

static DEFINE_MUTEX(iosf_mbi_punit_mutex);
static DEFINE_MUTEX(iosf_mbi_block_punit_i2c_access_count_mutex);
static BLOCKING_NOTIFIER_HEAD(iosf_mbi_pmic_bus_access_notifier);
static u32 iosf_mbi_block_punit_i2c_access_count;
static u32 iosf_mbi_sem_address;
static unsigned long iosf_mbi_sem_acquired;
static struct pm_qos_request iosf_mbi_pm_qos;

void iosf_mbi_punit_acquire(void)
{
	mutex_lock(&iosf_mbi_punit_mutex);
@@ -204,6 +230,159 @@ void iosf_mbi_punit_release(void)
}
EXPORT_SYMBOL(iosf_mbi_punit_release);

static int iosf_mbi_get_sem(u32 *sem)
{
	int ret;

	ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
			    iosf_mbi_sem_address, sem);
	if (ret) {
		dev_err(&mbi_pdev->dev, "Error P-Unit semaphore read failed\n");
		return ret;
	}

	*sem &= PUNIT_SEMAPHORE_BIT;
	return 0;
}

static void iosf_mbi_reset_semaphore(void)
{
	if (iosf_mbi_modify(BT_MBI_UNIT_PMC, MBI_REG_READ,
			    iosf_mbi_sem_address, 0, PUNIT_SEMAPHORE_BIT))
		dev_err(&mbi_pdev->dev, "Error P-Unit semaphore reset failed\n");

	pm_qos_update_request(&iosf_mbi_pm_qos, PM_QOS_DEFAULT_VALUE);

	blocking_notifier_call_chain(&iosf_mbi_pmic_bus_access_notifier,
				     MBI_PMIC_BUS_ACCESS_END, NULL);
}

/*
 * This function blocks P-Unit accesses to the PMIC I2C bus, so that kernel
 * I2C code, such as e.g. a fuel-gauge driver, can access it safely.
 *
 * This function may be called by I2C controller code while an I2C driver has
 * already blocked P-Unit accesses because it wants them blocked over multiple
 * i2c-transfers, for e.g. read-modify-write of an I2C client register.
 *
 * The P-Unit accesses already being blocked is tracked through the
 * iosf_mbi_block_punit_i2c_access_count variable which is protected by the
 * iosf_mbi_block_punit_i2c_access_count_mutex this mutex is hold for the
 * entire duration of the function.
 *
 * If access is not blocked yet, this function takes the following steps:
 *
 * 1) Some code sends request to the P-Unit which make it access the PMIC
 *    I2C bus. Testing has shown that the P-Unit does not check its internal
 *    PMIC bus semaphore for these requests. Callers of these requests call
 *    iosf_mbi_punit_acquire()/_release() around their P-Unit accesses, these
 *    functions lock/unlock the iosf_mbi_punit_mutex.
 *    As the first step we lock the iosf_mbi_punit_mutex, to wait for any in
 *    flight requests to finish and to block any new requests.
 *
 * 2) Some code makes such P-Unit requests from atomic contexts where it
 *    cannot call iosf_mbi_punit_acquire() as that may sleep.
 *    As the second step we call a notifier chain which allows any code
 *    needing P-Unit resources from atomic context to acquire them before
 *    we take control over the PMIC I2C bus.
 *
 * 3) When CPU cores enter C6 or C7 the P-Unit needs to talk to the PMIC
 *    if this happens while the kernel itself is accessing the PMIC I2C bus
 *    the SoC hangs.
 *    As the third step we call pm_qos_update_request() to disallow the CPU
 *    to enter C6 or C7.
 *
 * 4) The P-Unit has a PMIC bus semaphore which we can request to stop
 *    autonomous P-Unit tasks from accessing the PMIC I2C bus while we hold it.
 *    As the fourth and final step we request this semaphore and wait for our
 *    request to be acknowledged.
 */
int iosf_mbi_block_punit_i2c_access(void)
{
	unsigned long start, end;
	int ret = 0;
	u32 sem;

	if (WARN_ON(!mbi_pdev || !iosf_mbi_sem_address))
		return -ENXIO;

	mutex_lock(&iosf_mbi_block_punit_i2c_access_count_mutex);

	if (iosf_mbi_block_punit_i2c_access_count > 0)
		goto success;

	mutex_lock(&iosf_mbi_punit_mutex);
	blocking_notifier_call_chain(&iosf_mbi_pmic_bus_access_notifier,
				     MBI_PMIC_BUS_ACCESS_BEGIN, NULL);

	/*
	 * Disallow the CPU to enter C6 or C7 state, entering these states
	 * requires the P-Unit to talk to the PMIC and if this happens while
	 * we're holding the semaphore, the SoC hangs.
	 */
	pm_qos_update_request(&iosf_mbi_pm_qos, 0);

	/* host driver writes to side band semaphore register */
	ret = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
			     iosf_mbi_sem_address, PUNIT_SEMAPHORE_ACQUIRE);
	if (ret) {
		dev_err(&mbi_pdev->dev, "Error P-Unit semaphore request failed\n");
		goto error;
	}

	/* host driver waits for bit 0 to be set in semaphore register */
	start = jiffies;
	end = start + msecs_to_jiffies(SEMAPHORE_TIMEOUT);
	do {
		ret = iosf_mbi_get_sem(&sem);
		if (!ret && sem) {
			iosf_mbi_sem_acquired = jiffies;
			dev_dbg(&mbi_pdev->dev, "P-Unit semaphore acquired after %ums\n",
				jiffies_to_msecs(jiffies - start));
			/*
			 * Success, keep iosf_mbi_punit_mutex locked till
			 * iosf_mbi_unblock_punit_i2c_access() gets called.
			 */
			goto success;
		}

		usleep_range(1000, 2000);
	} while (time_before(jiffies, end));

	ret = -ETIMEDOUT;
	dev_err(&mbi_pdev->dev, "Error P-Unit semaphore timed out, resetting\n");
error:
	iosf_mbi_reset_semaphore();
	mutex_unlock(&iosf_mbi_punit_mutex);

	if (!iosf_mbi_get_sem(&sem))
		dev_err(&mbi_pdev->dev, "P-Unit semaphore: %d\n", sem);
success:
	if (!WARN_ON(ret))
		iosf_mbi_block_punit_i2c_access_count++;

	mutex_unlock(&iosf_mbi_block_punit_i2c_access_count_mutex);

	return ret;
}
EXPORT_SYMBOL(iosf_mbi_block_punit_i2c_access);

void iosf_mbi_unblock_punit_i2c_access(void)
{
	mutex_lock(&iosf_mbi_block_punit_i2c_access_count_mutex);

	iosf_mbi_block_punit_i2c_access_count--;
	if (iosf_mbi_block_punit_i2c_access_count == 0) {
		iosf_mbi_reset_semaphore();
		mutex_unlock(&iosf_mbi_punit_mutex);
		dev_dbg(&mbi_pdev->dev, "punit semaphore held for %ums\n",
			jiffies_to_msecs(jiffies - iosf_mbi_sem_acquired));
	}

	mutex_unlock(&iosf_mbi_block_punit_i2c_access_count_mutex);
}
EXPORT_SYMBOL(iosf_mbi_unblock_punit_i2c_access);

int iosf_mbi_register_pmic_bus_access_notifier(struct notifier_block *nb)
{
	int ret;
@@ -241,19 +420,14 @@ int iosf_mbi_unregister_pmic_bus_access_notifier(struct notifier_block *nb)
}
EXPORT_SYMBOL(iosf_mbi_unregister_pmic_bus_access_notifier);

int iosf_mbi_call_pmic_bus_access_notifier_chain(unsigned long val, void *v)
{
	return blocking_notifier_call_chain(
				&iosf_mbi_pmic_bus_access_notifier, val, v);
}
EXPORT_SYMBOL(iosf_mbi_call_pmic_bus_access_notifier_chain);

void iosf_mbi_assert_punit_acquired(void)
{
	WARN_ON(!mutex_is_locked(&iosf_mbi_punit_mutex));
}
EXPORT_SYMBOL(iosf_mbi_assert_punit_acquired);

/**************** iosf_mbi debug code ****************/

#ifdef CONFIG_IOSF_MBI_DEBUG
static u32	dbg_mdr;
static u32	dbg_mcr;
@@ -338,7 +512,7 @@ static inline void iosf_debugfs_remove(void) { }
#endif /* CONFIG_IOSF_MBI_DEBUG */

static int iosf_mbi_probe(struct pci_dev *pdev,
			  const struct pci_device_id *unused)
			  const struct pci_device_id *dev_id)
{
	int ret;

@@ -349,14 +523,16 @@ static int iosf_mbi_probe(struct pci_dev *pdev,
	}

	mbi_pdev = pci_dev_get(pdev);
	iosf_mbi_sem_address = dev_id->driver_data;

	return 0;
}

static const struct pci_device_id iosf_mbi_pci_ids[] = {
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_BAYTRAIL) },
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_BRASWELL) },
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_QUARK_X1000) },
	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_TANGIER) },
	{ PCI_DEVICE_DATA(INTEL, BAYTRAIL, PUNIT_SEMAPHORE_BYT) },
	{ PCI_DEVICE_DATA(INTEL, BRASWELL, PUNIT_SEMAPHORE_CHT) },
	{ PCI_DEVICE_DATA(INTEL, QUARK_X1000, 0) },
	{ PCI_DEVICE_DATA(INTEL, TANGIER, 0) },
	{ 0, },
};
MODULE_DEVICE_TABLE(pci, iosf_mbi_pci_ids);
@@ -371,6 +547,9 @@ static int __init iosf_mbi_init(void)
{
	iosf_debugfs_init();

	pm_qos_add_request(&iosf_mbi_pm_qos, PM_QOS_CPU_DMA_LATENCY,
			   PM_QOS_DEFAULT_VALUE);

	return pci_register_driver(&iosf_mbi_pci_driver);
}

@@ -381,6 +560,8 @@ static void __exit iosf_mbi_exit(void)
	pci_unregister_driver(&iosf_mbi_pci_driver);
	pci_dev_put(mbi_pdev);
	mbi_pdev = NULL;

	pm_qos_remove_request(&iosf_mbi_pm_qos);
}

module_init(iosf_mbi_init);
+1 −1
Original line number Diff line number Diff line
@@ -512,7 +512,7 @@ config CRC_PMIC_OPREGION

config XPOWER_PMIC_OPREGION
	bool "ACPI operation region support for XPower AXP288 PMIC"
	depends on MFD_AXP20X_I2C
	depends on MFD_AXP20X_I2C && IOSF_MBI
	help
	  This config adds ACPI operation region support for XPower AXP288 PMIC.

+15 −6
Original line number Diff line number Diff line
@@ -8,8 +8,9 @@
#include <linux/acpi.h>
#include <linux/init.h>
#include <linux/mfd/axp20x.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/platform_device.h>
#include <asm/iosf_mbi.h>
#include "intel_pmic.h"

#define XPOWER_GPADC_LOW	0x5b
@@ -172,15 +173,21 @@ static int intel_xpower_pmic_get_power(struct regmap *regmap, int reg,
static int intel_xpower_pmic_update_power(struct regmap *regmap, int reg,
					  int bit, bool on)
{
	int data;
	int data, ret;

	/* GPIO1 LDO regulator needs special handling */
	if (reg == XPOWER_GPI1_CTRL)
		return regmap_update_bits(regmap, reg, GPI1_LDO_MASK,
					  on ? GPI1_LDO_ON : GPI1_LDO_OFF);

	if (regmap_read(regmap, reg, &data))
		return -EIO;
	ret = iosf_mbi_block_punit_i2c_access();
	if (ret)
		return ret;

	if (regmap_read(regmap, reg, &data)) {
		ret = -EIO;
		goto out;
	}

	if (on)
		data |= BIT(bit);
@@ -188,9 +195,11 @@ static int intel_xpower_pmic_update_power(struct regmap *regmap, int reg,
		data &= ~BIT(bit);

	if (regmap_write(regmap, reg, data))
		return -EIO;
		ret = -EIO;
out:
	iosf_mbi_unblock_punit_i2c_access();

	return 0;
	return ret;
}

/**
+2 −137
Original line number Diff line number Diff line
@@ -3,141 +3,15 @@
 * Intel BayTrail PMIC I2C bus semaphore implementaion
 * Copyright (c) 2014, Intel Corporation.
 */
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/pm_qos.h>

#include <asm/iosf_mbi.h>

#include "i2c-designware-core.h"

#define SEMAPHORE_TIMEOUT	500
#define PUNIT_SEMAPHORE		0x7
#define PUNIT_SEMAPHORE_CHT	0x10e
#define PUNIT_SEMAPHORE_BIT	BIT(0)
#define PUNIT_SEMAPHORE_ACQUIRE	BIT(1)

static unsigned long acquired;

static u32 get_sem_addr(struct dw_i2c_dev *dev)
{
	if (dev->flags & MODEL_CHERRYTRAIL)
		return PUNIT_SEMAPHORE_CHT;
	else
		return PUNIT_SEMAPHORE;
}

static int get_sem(struct dw_i2c_dev *dev, u32 *sem)
{
	u32 addr = get_sem_addr(dev);
	u32 data;
	int ret;

	ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, addr, &data);
	if (ret) {
		dev_err(dev->dev, "iosf failed to read punit semaphore\n");
		return ret;
	}

	*sem = data & PUNIT_SEMAPHORE_BIT;

	return 0;
}

static void reset_semaphore(struct dw_i2c_dev *dev)
{
	if (iosf_mbi_modify(BT_MBI_UNIT_PMC, MBI_REG_READ, get_sem_addr(dev),
			    0, PUNIT_SEMAPHORE_BIT))
		dev_err(dev->dev, "iosf failed to reset punit semaphore during write\n");

	pm_qos_update_request(&dev->pm_qos, PM_QOS_DEFAULT_VALUE);

	iosf_mbi_call_pmic_bus_access_notifier_chain(MBI_PMIC_BUS_ACCESS_END,
						     NULL);
	iosf_mbi_punit_release();
}

static int baytrail_i2c_acquire(struct dw_i2c_dev *dev)
{
	u32 addr;
	u32 sem = PUNIT_SEMAPHORE_ACQUIRE;
	int ret;
	unsigned long start, end;

	might_sleep();

	if (!dev || !dev->dev)
		return -ENODEV;

	if (!dev->release_lock)
		return 0;

	iosf_mbi_punit_acquire();
	iosf_mbi_call_pmic_bus_access_notifier_chain(MBI_PMIC_BUS_ACCESS_BEGIN,
						     NULL);

	/*
	 * Disallow the CPU to enter C6 or C7 state, entering these states
	 * requires the punit to talk to the pmic and if this happens while
	 * we're holding the semaphore, the SoC hangs.
	 */
	pm_qos_update_request(&dev->pm_qos, 0);

	addr = get_sem_addr(dev);

	/* host driver writes to side band semaphore register */
	ret = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, addr, sem);
	if (ret) {
		dev_err(dev->dev, "iosf punit semaphore request failed\n");
		goto out;
	}

	/* host driver waits for bit 0 to be set in semaphore register */
	start = jiffies;
	end = start + msecs_to_jiffies(SEMAPHORE_TIMEOUT);
	do {
		ret = get_sem(dev, &sem);
		if (!ret && sem) {
			acquired = jiffies;
			dev_dbg(dev->dev, "punit semaphore acquired after %ums\n",
				jiffies_to_msecs(jiffies - start));
			return 0;
		}

		usleep_range(1000, 2000);
	} while (time_before(jiffies, end));

	dev_err(dev->dev, "punit semaphore timed out, resetting\n");
out:
	reset_semaphore(dev);

	ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, addr, &sem);
	if (ret)
		dev_err(dev->dev, "iosf failed to read punit semaphore\n");
	else
		dev_err(dev->dev, "PUNIT SEM: %d\n", sem);

	WARN_ON(1);

	return -ETIMEDOUT;
}

static void baytrail_i2c_release(struct dw_i2c_dev *dev)
{
	if (!dev || !dev->dev)
		return;

	if (!dev->acquire_lock)
		return;

	reset_semaphore(dev);
	dev_dbg(dev->dev, "punit semaphore held for %ums\n",
		jiffies_to_msecs(jiffies - acquired));
}

int i2c_dw_probe_lock_support(struct dw_i2c_dev *dev)
{
	acpi_status status;
@@ -162,18 +36,9 @@ int i2c_dw_probe_lock_support(struct dw_i2c_dev *dev)
		return -EPROBE_DEFER;

	dev_info(dev->dev, "I2C bus managed by PUNIT\n");
	dev->acquire_lock = baytrail_i2c_acquire;
	dev->release_lock = baytrail_i2c_release;
	dev->acquire_lock = iosf_mbi_block_punit_i2c_access;
	dev->release_lock = iosf_mbi_unblock_punit_i2c_access;
	dev->shared_with_punit = true;

	pm_qos_add_request(&dev->pm_qos, PM_QOS_CPU_DMA_LATENCY,
			   PM_QOS_DEFAULT_VALUE);

	return 0;
}

void i2c_dw_remove_lock_support(struct dw_i2c_dev *dev)
{
	if (dev->acquire_lock)
		pm_qos_remove_request(&dev->pm_qos);
}
Loading