2751541555
On most Intel Bay- and Cherry-Trail systems the PMIC is connected over I2C and the PMIC is accessed through various means by the _PS0 and _PS3 ACPI methods (power on / off methods) of various devices. This leads to suspend/resume ordering problems where a device may be resumed and get its _PS0 method executed before the I2C controller is resumed. On Cherry Trail this leads to errors like these: i2c_designware 808622C1:06: controller timed out ACPI Error: AE_ERROR, Returned by Handler for [UserDefinedRegion] ACPI Error: Method parse/execution failed \_SB.P18W._ON, AE_ERROR video LNXVIDEO:00: Failed to change power state to D0 But on Bay Trail this caused I2C reads to seem to succeed, but they end up returning wrong data, which ends up getting written back by the typical read-modify-write cycle done to turn on various power-resources. Debugging the problems caused by this silent data corruption is quite nasty. This commit adds a check which disallows i2c_dw_xfer() calls to happen until the controller's resume method has completed. Which turns the silent data corruption into getting these errors in dmesg instead: i2c_designware 80860F41:04: Error i2c_dw_xfer call while suspended ACPI Error: AE_ERROR, Returned by Handler for [UserDefinedRegion] ACPI Error: Method parse/execution failed \_SB.PCI0.GFX0._PS0, AE_ERROR Which is much better. Note the above errors are an example of issues which this patch will help to debug, the actual fix requires fixing the suspend order and this has been fixed by a different commit. Note the setting / clearing of the suspended flag in the suspend / resume methods is NOT protected by i2c_lock_bus(). This is intentional as these methods get called from i2c_dw_xfer() (through pm_runtime_get/put) a nd i2c_dw_xfer() is called with the i2c_bus_lock held, so otherwise we would deadlock. This means that there is a theoretical race between a non runtime suspend and the suspended check in i2c_dw_xfer(), this is not a problem since normally we should not hit the race and this check is primarily a debugging tool so hitting the check if there are suspend/resume ordering problems does not need to be 100% reliable. Signed-off-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
353 lines
8.3 KiB
C
353 lines
8.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Synopsys DesignWare I2C adapter driver (master only).
|
|
*
|
|
* Based on the TI DAVINCI I2C adapter driver.
|
|
*
|
|
* Copyright (C) 2006 Texas Instruments.
|
|
* Copyright (C) 2007 MontaVista Software Inc.
|
|
* Copyright (C) 2009 Provigent Ltd.
|
|
* Copyright (C) 2011, 2015, 2016 Intel Corporation.
|
|
*/
|
|
#include <linux/acpi.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "i2c-designware-core.h"
|
|
|
|
#define DRIVER_NAME "i2c-designware-pci"
|
|
|
|
enum dw_pci_ctl_id_t {
|
|
medfield,
|
|
merrifield,
|
|
baytrail,
|
|
cherrytrail,
|
|
haswell,
|
|
};
|
|
|
|
struct dw_scl_sda_cfg {
|
|
u32 ss_hcnt;
|
|
u32 fs_hcnt;
|
|
u32 ss_lcnt;
|
|
u32 fs_lcnt;
|
|
u32 sda_hold;
|
|
};
|
|
|
|
struct dw_pci_controller {
|
|
u32 bus_num;
|
|
u32 bus_cfg;
|
|
u32 tx_fifo_depth;
|
|
u32 rx_fifo_depth;
|
|
u32 clk_khz;
|
|
u32 functionality;
|
|
u32 flags;
|
|
struct dw_scl_sda_cfg *scl_sda_cfg;
|
|
int (*setup)(struct pci_dev *pdev, struct dw_pci_controller *c);
|
|
};
|
|
|
|
#define INTEL_MID_STD_CFG (DW_IC_CON_MASTER | \
|
|
DW_IC_CON_SLAVE_DISABLE | \
|
|
DW_IC_CON_RESTART_EN)
|
|
|
|
/* Merrifield HCNT/LCNT/SDA hold time */
|
|
static struct dw_scl_sda_cfg mrfld_config = {
|
|
.ss_hcnt = 0x2f8,
|
|
.fs_hcnt = 0x87,
|
|
.ss_lcnt = 0x37b,
|
|
.fs_lcnt = 0x10a,
|
|
};
|
|
|
|
/* BayTrail HCNT/LCNT/SDA hold time */
|
|
static struct dw_scl_sda_cfg byt_config = {
|
|
.ss_hcnt = 0x200,
|
|
.fs_hcnt = 0x55,
|
|
.ss_lcnt = 0x200,
|
|
.fs_lcnt = 0x99,
|
|
.sda_hold = 0x6,
|
|
};
|
|
|
|
/* Haswell HCNT/LCNT/SDA hold time */
|
|
static struct dw_scl_sda_cfg hsw_config = {
|
|
.ss_hcnt = 0x01b0,
|
|
.fs_hcnt = 0x48,
|
|
.ss_lcnt = 0x01fb,
|
|
.fs_lcnt = 0xa0,
|
|
.sda_hold = 0x9,
|
|
};
|
|
|
|
static int mfld_setup(struct pci_dev *pdev, struct dw_pci_controller *c)
|
|
{
|
|
switch (pdev->device) {
|
|
case 0x0817:
|
|
c->bus_cfg &= ~DW_IC_CON_SPEED_MASK;
|
|
c->bus_cfg |= DW_IC_CON_SPEED_STD;
|
|
/* fall through */
|
|
case 0x0818:
|
|
case 0x0819:
|
|
c->bus_num = pdev->device - 0x817 + 3;
|
|
return 0;
|
|
case 0x082C:
|
|
case 0x082D:
|
|
case 0x082E:
|
|
c->bus_num = pdev->device - 0x82C + 0;
|
|
return 0;
|
|
}
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int mrfld_setup(struct pci_dev *pdev, struct dw_pci_controller *c)
|
|
{
|
|
/*
|
|
* On Intel Merrifield the user visible i2c busses are enumerated
|
|
* [1..7]. So, we add 1 to shift the default range. Besides that the
|
|
* first PCI slot provides 4 functions, that's why we have to add 0 to
|
|
* the first slot and 4 to the next one.
|
|
*/
|
|
switch (PCI_SLOT(pdev->devfn)) {
|
|
case 8:
|
|
c->bus_num = PCI_FUNC(pdev->devfn) + 0 + 1;
|
|
return 0;
|
|
case 9:
|
|
c->bus_num = PCI_FUNC(pdev->devfn) + 4 + 1;
|
|
return 0;
|
|
}
|
|
return -ENODEV;
|
|
}
|
|
|
|
static struct dw_pci_controller dw_pci_controllers[] = {
|
|
[medfield] = {
|
|
.bus_num = -1,
|
|
.bus_cfg = INTEL_MID_STD_CFG | DW_IC_CON_SPEED_FAST,
|
|
.tx_fifo_depth = 32,
|
|
.rx_fifo_depth = 32,
|
|
.functionality = I2C_FUNC_10BIT_ADDR,
|
|
.clk_khz = 25000,
|
|
.setup = mfld_setup,
|
|
},
|
|
[merrifield] = {
|
|
.bus_num = -1,
|
|
.bus_cfg = INTEL_MID_STD_CFG | DW_IC_CON_SPEED_FAST,
|
|
.tx_fifo_depth = 64,
|
|
.rx_fifo_depth = 64,
|
|
.functionality = I2C_FUNC_10BIT_ADDR,
|
|
.scl_sda_cfg = &mrfld_config,
|
|
.setup = mrfld_setup,
|
|
},
|
|
[baytrail] = {
|
|
.bus_num = -1,
|
|
.bus_cfg = INTEL_MID_STD_CFG | DW_IC_CON_SPEED_FAST,
|
|
.tx_fifo_depth = 32,
|
|
.rx_fifo_depth = 32,
|
|
.functionality = I2C_FUNC_10BIT_ADDR,
|
|
.scl_sda_cfg = &byt_config,
|
|
},
|
|
[haswell] = {
|
|
.bus_num = -1,
|
|
.bus_cfg = INTEL_MID_STD_CFG | DW_IC_CON_SPEED_FAST,
|
|
.tx_fifo_depth = 32,
|
|
.rx_fifo_depth = 32,
|
|
.functionality = I2C_FUNC_10BIT_ADDR,
|
|
.scl_sda_cfg = &hsw_config,
|
|
},
|
|
[cherrytrail] = {
|
|
.bus_num = -1,
|
|
.bus_cfg = INTEL_MID_STD_CFG | DW_IC_CON_SPEED_FAST,
|
|
.tx_fifo_depth = 32,
|
|
.rx_fifo_depth = 32,
|
|
.functionality = I2C_FUNC_10BIT_ADDR,
|
|
.flags = MODEL_CHERRYTRAIL,
|
|
.scl_sda_cfg = &byt_config,
|
|
},
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
static int i2c_dw_pci_suspend(struct device *dev)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct dw_i2c_dev *i_dev = pci_get_drvdata(pdev);
|
|
|
|
i_dev->suspended = true;
|
|
i_dev->disable(i_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_dw_pci_resume(struct device *dev)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct dw_i2c_dev *i_dev = pci_get_drvdata(pdev);
|
|
int ret;
|
|
|
|
ret = i_dev->init(i_dev);
|
|
i_dev->suspended = false;
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static UNIVERSAL_DEV_PM_OPS(i2c_dw_pm_ops, i2c_dw_pci_suspend,
|
|
i2c_dw_pci_resume, NULL);
|
|
|
|
static u32 i2c_dw_get_clk_rate_khz(struct dw_i2c_dev *dev)
|
|
{
|
|
return dev->controller->clk_khz;
|
|
}
|
|
|
|
static int i2c_dw_pci_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *id)
|
|
{
|
|
struct dw_i2c_dev *dev;
|
|
struct i2c_adapter *adap;
|
|
int r;
|
|
struct dw_pci_controller *controller;
|
|
struct dw_scl_sda_cfg *cfg;
|
|
|
|
if (id->driver_data >= ARRAY_SIZE(dw_pci_controllers)) {
|
|
dev_err(&pdev->dev, "%s: invalid driver data %ld\n", __func__,
|
|
id->driver_data);
|
|
return -EINVAL;
|
|
}
|
|
|
|
controller = &dw_pci_controllers[id->driver_data];
|
|
|
|
r = pcim_enable_device(pdev);
|
|
if (r) {
|
|
dev_err(&pdev->dev, "Failed to enable I2C PCI device (%d)\n",
|
|
r);
|
|
return r;
|
|
}
|
|
|
|
r = pcim_iomap_regions(pdev, 1 << 0, pci_name(pdev));
|
|
if (r) {
|
|
dev_err(&pdev->dev, "I/O memory remapping failed\n");
|
|
return r;
|
|
}
|
|
|
|
dev = devm_kzalloc(&pdev->dev, sizeof(struct dw_i2c_dev), GFP_KERNEL);
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
dev->clk = NULL;
|
|
dev->controller = controller;
|
|
dev->get_clk_rate_khz = i2c_dw_get_clk_rate_khz;
|
|
dev->base = pcim_iomap_table(pdev)[0];
|
|
dev->dev = &pdev->dev;
|
|
dev->irq = pdev->irq;
|
|
dev->flags |= controller->flags;
|
|
|
|
if (controller->setup) {
|
|
r = controller->setup(pdev, controller);
|
|
if (r)
|
|
return r;
|
|
}
|
|
|
|
dev->functionality = controller->functionality |
|
|
DW_IC_DEFAULT_FUNCTIONALITY;
|
|
|
|
dev->master_cfg = controller->bus_cfg;
|
|
if (controller->scl_sda_cfg) {
|
|
cfg = controller->scl_sda_cfg;
|
|
dev->ss_hcnt = cfg->ss_hcnt;
|
|
dev->fs_hcnt = cfg->fs_hcnt;
|
|
dev->ss_lcnt = cfg->ss_lcnt;
|
|
dev->fs_lcnt = cfg->fs_lcnt;
|
|
dev->sda_hold_time = cfg->sda_hold;
|
|
}
|
|
|
|
pci_set_drvdata(pdev, dev);
|
|
|
|
dev->tx_fifo_depth = controller->tx_fifo_depth;
|
|
dev->rx_fifo_depth = controller->rx_fifo_depth;
|
|
|
|
adap = &dev->adapter;
|
|
adap->owner = THIS_MODULE;
|
|
adap->class = 0;
|
|
ACPI_COMPANION_SET(&adap->dev, ACPI_COMPANION(&pdev->dev));
|
|
adap->nr = controller->bus_num;
|
|
|
|
r = i2c_dw_probe(dev);
|
|
if (r)
|
|
return r;
|
|
|
|
pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);
|
|
pm_runtime_use_autosuspend(&pdev->dev);
|
|
pm_runtime_put_autosuspend(&pdev->dev);
|
|
pm_runtime_allow(&pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void i2c_dw_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct dw_i2c_dev *dev = pci_get_drvdata(pdev);
|
|
|
|
dev->disable(dev);
|
|
pm_runtime_forbid(&pdev->dev);
|
|
pm_runtime_get_noresume(&pdev->dev);
|
|
|
|
i2c_del_adapter(&dev->adapter);
|
|
}
|
|
|
|
/* work with hotplug and coldplug */
|
|
MODULE_ALIAS("i2c_designware-pci");
|
|
|
|
static const struct pci_device_id i2_designware_pci_ids[] = {
|
|
/* Medfield */
|
|
{ PCI_VDEVICE(INTEL, 0x0817), medfield },
|
|
{ PCI_VDEVICE(INTEL, 0x0818), medfield },
|
|
{ PCI_VDEVICE(INTEL, 0x0819), medfield },
|
|
{ PCI_VDEVICE(INTEL, 0x082C), medfield },
|
|
{ PCI_VDEVICE(INTEL, 0x082D), medfield },
|
|
{ PCI_VDEVICE(INTEL, 0x082E), medfield },
|
|
/* Merrifield */
|
|
{ PCI_VDEVICE(INTEL, 0x1195), merrifield },
|
|
{ PCI_VDEVICE(INTEL, 0x1196), merrifield },
|
|
/* Baytrail */
|
|
{ PCI_VDEVICE(INTEL, 0x0F41), baytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x0F42), baytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x0F43), baytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x0F44), baytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x0F45), baytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x0F46), baytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x0F47), baytrail },
|
|
/* Haswell */
|
|
{ PCI_VDEVICE(INTEL, 0x9c61), haswell },
|
|
{ PCI_VDEVICE(INTEL, 0x9c62), haswell },
|
|
/* Braswell / Cherrytrail */
|
|
{ PCI_VDEVICE(INTEL, 0x22C1), cherrytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x22C2), cherrytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x22C3), cherrytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x22C4), cherrytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x22C5), cherrytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x22C6), cherrytrail },
|
|
{ PCI_VDEVICE(INTEL, 0x22C7), cherrytrail },
|
|
{ 0,}
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, i2_designware_pci_ids);
|
|
|
|
static struct pci_driver dw_i2c_driver = {
|
|
.name = DRIVER_NAME,
|
|
.id_table = i2_designware_pci_ids,
|
|
.probe = i2c_dw_pci_probe,
|
|
.remove = i2c_dw_pci_remove,
|
|
.driver = {
|
|
.pm = &i2c_dw_pm_ops,
|
|
},
|
|
};
|
|
|
|
module_pci_driver(dw_i2c_driver);
|
|
|
|
MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>");
|
|
MODULE_DESCRIPTION("Synopsys DesignWare PCI I2C bus adapter");
|
|
MODULE_LICENSE("GPL");
|