linux/drivers/mfd/intel-lpss.c

544 lines
13 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel Sunrisepoint LPSS core support.
*
* Copyright (C) 2015, Intel Corporation
*
* Authors: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
* Heikki Krogerus <heikki.krogerus@linux.intel.com>
* Jarkko Nikula <jarkko.nikula@linux.intel.com>
*/
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/debugfs.h>
#include <linux/idr.h>
clk: Remove io.h from clk-provider.h Now that we've gotten rid of clk_readl() we can remove io.h from the clk-provider header and push out the io.h include to any code that isn't already including the io.h header but using things like readl/writel, etc. Found with this grep: git grep -l clk-provider.h | grep '.c$' | xargs git grep -L 'linux/io.h' | \ xargs git grep -l \ -e '\<__iowrite32_copy\>' --or \ -e '\<__ioread32_copy\>' --or \ -e '\<__iowrite64_copy\>' --or \ -e '\<ioremap_page_range\>' --or \ -e '\<ioremap_huge_init\>' --or \ -e '\<arch_ioremap_pud_supported\>' --or \ -e '\<arch_ioremap_pmd_supported\>' --or \ -e '\<devm_ioport_map\>' --or \ -e '\<devm_ioport_unmap\>' --or \ -e '\<IOMEM_ERR_PTR\>' --or \ -e '\<devm_ioremap\>' --or \ -e '\<devm_ioremap_nocache\>' --or \ -e '\<devm_ioremap_wc\>' --or \ -e '\<devm_iounmap\>' --or \ -e '\<devm_ioremap_release\>' --or \ -e '\<devm_memremap\>' --or \ -e '\<devm_memunmap\>' --or \ -e '\<__devm_memremap_pages\>' --or \ -e '\<pci_remap_cfgspace\>' --or \ -e '\<arch_has_dev_port\>' --or \ -e '\<arch_phys_wc_add\>' --or \ -e '\<arch_phys_wc_del\>' --or \ -e '\<memremap\>' --or \ -e '\<memunmap\>' --or \ -e '\<arch_io_reserve_memtype_wc\>' --or \ -e '\<arch_io_free_memtype_wc\>' --or \ -e '\<__io_aw\>' --or \ -e '\<__io_pbw\>' --or \ -e '\<__io_paw\>' --or \ -e '\<__io_pbr\>' --or \ -e '\<__io_par\>' --or \ -e '\<__raw_readb\>' --or \ -e '\<__raw_readw\>' --or \ -e '\<__raw_readl\>' --or \ -e '\<__raw_readq\>' --or \ -e '\<__raw_writeb\>' --or \ -e '\<__raw_writew\>' --or \ -e '\<__raw_writel\>' --or \ -e '\<__raw_writeq\>' --or \ -e '\<readb\>' --or \ -e '\<readw\>' --or \ -e '\<readl\>' --or \ -e '\<readq\>' --or \ -e '\<writeb\>' --or \ -e '\<writew\>' --or \ -e '\<writel\>' --or \ -e '\<writeq\>' --or \ -e '\<readb_relaxed\>' --or \ -e '\<readw_relaxed\>' --or \ -e '\<readl_relaxed\>' --or \ -e '\<readq_relaxed\>' --or \ -e '\<writeb_relaxed\>' --or \ -e '\<writew_relaxed\>' --or \ -e '\<writel_relaxed\>' --or \ -e '\<writeq_relaxed\>' --or \ -e '\<readsb\>' --or \ -e '\<readsw\>' --or \ -e '\<readsl\>' --or \ -e '\<readsq\>' --or \ -e '\<writesb\>' --or \ -e '\<writesw\>' --or \ -e '\<writesl\>' --or \ -e '\<writesq\>' --or \ -e '\<inb\>' --or \ -e '\<inw\>' --or \ -e '\<inl\>' --or \ -e '\<outb\>' --or \ -e '\<outw\>' --or \ -e '\<outl\>' --or \ -e '\<inb_p\>' --or \ -e '\<inw_p\>' --or \ -e '\<inl_p\>' --or \ -e '\<outb_p\>' --or \ -e '\<outw_p\>' --or \ -e '\<outl_p\>' --or \ -e '\<insb\>' --or \ -e '\<insw\>' --or \ -e '\<insl\>' --or \ -e '\<outsb\>' --or \ -e '\<outsw\>' --or \ -e '\<outsl\>' --or \ -e '\<insb_p\>' --or \ -e '\<insw_p\>' --or \ -e '\<insl_p\>' --or \ -e '\<outsb_p\>' --or \ -e '\<outsw_p\>' --or \ -e '\<outsl_p\>' --or \ -e '\<ioread8\>' --or \ -e '\<ioread16\>' --or \ -e '\<ioread32\>' --or \ -e '\<ioread64\>' --or \ -e '\<iowrite8\>' --or \ -e '\<iowrite16\>' --or \ -e '\<iowrite32\>' --or \ -e '\<iowrite64\>' --or \ -e '\<ioread16be\>' --or \ -e '\<ioread32be\>' --or \ -e '\<ioread64be\>' --or \ -e '\<iowrite16be\>' --or \ -e '\<iowrite32be\>' --or \ -e '\<iowrite64be\>' --or \ -e '\<ioread8_rep\>' --or \ -e '\<ioread16_rep\>' --or \ -e '\<ioread32_rep\>' --or \ -e '\<ioread64_rep\>' --or \ -e '\<iowrite8_rep\>' --or \ -e '\<iowrite16_rep\>' --or \ -e '\<iowrite32_rep\>' --or \ -e '\<iowrite64_rep\>' --or \ -e '\<__io_virt\>' --or \ -e '\<pci_iounmap\>' --or \ -e '\<virt_to_phys\>' --or \ -e '\<phys_to_virt\>' --or \ -e '\<ioremap_uc\>' --or \ -e '\<ioremap\>' --or \ -e '\<__ioremap\>' --or \ -e '\<iounmap\>' --or \ -e '\<ioremap\>' --or \ -e '\<ioremap_nocache\>' --or \ -e '\<ioremap_uc\>' --or \ -e '\<ioremap_wc\>' --or \ -e '\<ioremap_wc\>' --or \ -e '\<ioremap_wt\>' --or \ -e '\<ioport_map\>' --or \ -e '\<ioport_unmap\>' --or \ -e '\<ioport_map\>' --or \ -e '\<ioport_unmap\>' --or \ -e '\<xlate_dev_kmem_ptr\>' --or \ -e '\<xlate_dev_mem_ptr\>' --or \ -e '\<unxlate_dev_mem_ptr\>' --or \ -e '\<virt_to_bus\>' --or \ -e '\<bus_to_virt\>' --or \ -e '\<memset_io\>' --or \ -e '\<memcpy_fromio\>' --or \ -e '\<memcpy_toio\>' I also reordered a couple includes when they weren't alphabetical and removed clk.h from kona, replacing it with clk-provider.h because that driver doesn't use clk consumer APIs. Acked-by: Geert Uytterhoeven <geert+renesas@glider.be> Cc: Chen-Yu Tsai <wens@csie.org> Acked-by: Maxime Ripard <maxime.ripard@bootlin.com> Acked-by: Tero Kristo <t-kristo@ti.com> Acked-by: Sekhar Nori <nsekhar@ti.com> Cc: Krzysztof Kozlowski <krzk@kernel.org> Acked-by: Mark Brown <broonie@kernel.org> Cc: Chris Zankel <chris@zankel.net> Acked-by: Max Filippov <jcmvbkbc@gmail.com> Acked-by: John Crispin <john@phrozen.org> Acked-by: Heiko Stuebner <heiko@sntech.de> Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2019-04-19 06:20:22 +08:00
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mfd/core.h>
#include <linux/pm_qos.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/seq_file.h>
asm-generic cleanups The asm-generic changes for 4.4 are mostly a series from Christoph Hellwig to clean up various abuses of headers in there. The patch to rename the io-64-nonatomic-*.h headers caused some conflicts with new users, so I added a workaround that we can remove in the next merge window. The only other patch is a warning fix from Marek Vasut -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIVAwUAVjzaf2CrR//JCVInAQImmhAA20fZ91sUlnA5skKNPT1phhF6Z7UF2Sx5 nPKcHQD3HA3lT1OKfPBYvCo+loYflvXFLaQThVylVcnE/8ecAEMtft4nnGW2nXvh sZqHIZ8fszTB53cynAZKTjdobD1wu33Rq7XRzg0ugn1mdxFkOzCHW/xDRvWRR5TL rdQjzzgvn2PNlqFfHlh6cZ5ykShM36AIKs3WGA0H0Y/aYsE9GmDOAUp41q1mLXnA 4lKQaIxoeOa+kmlsUB0wEHUecWWWJH4GAP+CtdKzTX9v12bGNhmiKUMCETG78BT3 uL8irSqaViNwSAS9tBxSpqvmVUsa5aCA5M3MYiO+fH9ifd7wbR65g/wq39D3Pc01 KnZ3BNVRW5XSA3c86pr8vbg/HOynUXK8TN0lzt6rEk8bjoPBNEDy5YWzy0t6reVe wX65F+ver8upjOKe9yl2Jsg+5Kcmy79GyYjLUY3TU2mZ+dIdScy/jIWatXe/OTKZ iB4Ctc4MDe9GDECmlPOWf98AXqsBUuKQiWKCN/OPxLtFOeWBvi4IzvFuO8QvnL9p jZcRDmIlIWAcDX/2wMnLjV+Hqi3EeReIrYznxTGnO7HHVInF555GP51vFaG5k+SN smJQAB0/sostmC1OCCqBKq5b6/li95/No7+0v0SUhJJ5o76AR5CcNsnolXesw1fu vTUkB/I66Hk= =dQKG -----END PGP SIGNATURE----- Merge tag 'asm-generic-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/arnd/asm-generic Pull asm-generic cleanups from Arnd Bergmann: "The asm-generic changes for 4.4 are mostly a series from Christoph Hellwig to clean up various abuses of headers in there. The patch to rename the io-64-nonatomic-*.h headers caused some conflicts with new users, so I added a workaround that we can remove in the next merge window. The only other patch is a warning fix from Marek Vasut" * tag 'asm-generic-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/arnd/asm-generic: asm-generic: temporarily add back asm-generic/io-64-nonatomic*.h asm-generic: cmpxchg: avoid warnings from macro-ized cmpxchg() implementations gpio-mxc: stop including <asm-generic/bug> n_tracesink: stop including <asm-generic/bug> n_tracerouter: stop including <asm-generic/bug> mlx5: stop including <asm-generic/kmap_types.h> hifn_795x: stop including <asm-generic/kmap_types.h> drbd: stop including <asm-generic/kmap_types.h> move count_zeroes.h out of asm-generic move io-64-nonatomic*.h out of asm-generic
2015-11-07 06:22:15 +08:00
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/dma/idma64.h>
#include "intel-lpss.h"
#define LPSS_DEV_OFFSET 0x000
#define LPSS_DEV_SIZE 0x200
#define LPSS_PRIV_OFFSET 0x200
#define LPSS_PRIV_SIZE 0x100
#define LPSS_PRIV_REG_COUNT (LPSS_PRIV_SIZE / 4)
#define LPSS_IDMA64_OFFSET 0x800
#define LPSS_IDMA64_SIZE 0x800
/* Offsets from lpss->priv */
#define LPSS_PRIV_RESETS 0x04
#define LPSS_PRIV_RESETS_IDMA BIT(2)
#define LPSS_PRIV_RESETS_FUNC 0x3
#define LPSS_PRIV_ACTIVELTR 0x10
#define LPSS_PRIV_IDLELTR 0x14
#define LPSS_PRIV_LTR_REQ BIT(15)
#define LPSS_PRIV_LTR_SCALE_MASK GENMASK(11, 10)
#define LPSS_PRIV_LTR_SCALE_1US (2 << 10)
#define LPSS_PRIV_LTR_SCALE_32US (3 << 10)
#define LPSS_PRIV_LTR_VALUE_MASK GENMASK(9, 0)
#define LPSS_PRIV_SSP_REG 0x20
#define LPSS_PRIV_SSP_REG_DIS_DMA_FIN BIT(0)
#define LPSS_PRIV_REMAP_ADDR 0x40
#define LPSS_PRIV_CAPS 0xfc
#define LPSS_PRIV_CAPS_NO_IDMA BIT(8)
#define LPSS_PRIV_CAPS_TYPE_MASK GENMASK(7, 4)
#define LPSS_PRIV_CAPS_TYPE_SHIFT 4
/* This matches the type field in CAPS register */
enum intel_lpss_dev_type {
LPSS_DEV_I2C = 0,
LPSS_DEV_UART,
LPSS_DEV_SPI,
};
struct intel_lpss {
const struct intel_lpss_platform_info *info;
enum intel_lpss_dev_type type;
struct clk *clk;
struct clk_lookup *clock;
struct mfd_cell *cell;
struct device *dev;
void __iomem *priv;
u32 priv_ctx[LPSS_PRIV_REG_COUNT];
int devid;
u32 caps;
u32 active_ltr;
u32 idle_ltr;
struct dentry *debugfs;
};
static const struct resource intel_lpss_dev_resources[] = {
DEFINE_RES_MEM_NAMED(LPSS_DEV_OFFSET, LPSS_DEV_SIZE, "lpss_dev"),
DEFINE_RES_MEM_NAMED(LPSS_PRIV_OFFSET, LPSS_PRIV_SIZE, "lpss_priv"),
DEFINE_RES_IRQ(0),
};
static const struct resource intel_lpss_idma64_resources[] = {
DEFINE_RES_MEM(LPSS_IDMA64_OFFSET, LPSS_IDMA64_SIZE),
DEFINE_RES_IRQ(0),
};
/*
* Cells needs to be ordered so that the iDMA is created first. This is
* because we need to be sure the DMA is available when the host controller
* driver is probed.
*/
static const struct mfd_cell intel_lpss_idma64_cell = {
.name = LPSS_IDMA64_DRIVER_NAME,
.num_resources = ARRAY_SIZE(intel_lpss_idma64_resources),
.resources = intel_lpss_idma64_resources,
};
static const struct mfd_cell intel_lpss_i2c_cell = {
.name = "i2c_designware",
.num_resources = ARRAY_SIZE(intel_lpss_dev_resources),
.resources = intel_lpss_dev_resources,
};
static const struct mfd_cell intel_lpss_uart_cell = {
.name = "dw-apb-uart",
.num_resources = ARRAY_SIZE(intel_lpss_dev_resources),
.resources = intel_lpss_dev_resources,
};
static const struct mfd_cell intel_lpss_spi_cell = {
.name = "pxa2xx-spi",
.num_resources = ARRAY_SIZE(intel_lpss_dev_resources),
.resources = intel_lpss_dev_resources,
};
static DEFINE_IDA(intel_lpss_devid_ida);
static struct dentry *intel_lpss_debugfs;
static void intel_lpss_cache_ltr(struct intel_lpss *lpss)
{
lpss->active_ltr = readl(lpss->priv + LPSS_PRIV_ACTIVELTR);
lpss->idle_ltr = readl(lpss->priv + LPSS_PRIV_IDLELTR);
}
static int intel_lpss_debugfs_add(struct intel_lpss *lpss)
{
struct dentry *dir;
dir = debugfs_create_dir(dev_name(lpss->dev), intel_lpss_debugfs);
if (IS_ERR(dir))
return PTR_ERR(dir);
/* Cache the values into lpss structure */
intel_lpss_cache_ltr(lpss);
debugfs_create_x32("capabilities", S_IRUGO, dir, &lpss->caps);
debugfs_create_x32("active_ltr", S_IRUGO, dir, &lpss->active_ltr);
debugfs_create_x32("idle_ltr", S_IRUGO, dir, &lpss->idle_ltr);
lpss->debugfs = dir;
return 0;
}
static void intel_lpss_debugfs_remove(struct intel_lpss *lpss)
{
debugfs_remove_recursive(lpss->debugfs);
}
static void intel_lpss_ltr_set(struct device *dev, s32 val)
{
struct intel_lpss *lpss = dev_get_drvdata(dev);
u32 ltr;
/*
* Program latency tolerance (LTR) accordingly what has been asked
* by the PM QoS layer or disable it in case we were passed
* negative value or PM_QOS_LATENCY_ANY.
*/
ltr = readl(lpss->priv + LPSS_PRIV_ACTIVELTR);
if (val == PM_QOS_LATENCY_ANY || val < 0) {
ltr &= ~LPSS_PRIV_LTR_REQ;
} else {
ltr |= LPSS_PRIV_LTR_REQ;
ltr &= ~LPSS_PRIV_LTR_SCALE_MASK;
ltr &= ~LPSS_PRIV_LTR_VALUE_MASK;
if (val > LPSS_PRIV_LTR_VALUE_MASK)
ltr |= LPSS_PRIV_LTR_SCALE_32US | val >> 5;
else
ltr |= LPSS_PRIV_LTR_SCALE_1US | val;
}
if (ltr == lpss->active_ltr)
return;
writel(ltr, lpss->priv + LPSS_PRIV_ACTIVELTR);
writel(ltr, lpss->priv + LPSS_PRIV_IDLELTR);
/* Cache the values into lpss structure */
intel_lpss_cache_ltr(lpss);
}
static void intel_lpss_ltr_expose(struct intel_lpss *lpss)
{
lpss->dev->power.set_latency_tolerance = intel_lpss_ltr_set;
dev_pm_qos_expose_latency_tolerance(lpss->dev);
}
static void intel_lpss_ltr_hide(struct intel_lpss *lpss)
{
dev_pm_qos_hide_latency_tolerance(lpss->dev);
lpss->dev->power.set_latency_tolerance = NULL;
}
static int intel_lpss_assign_devs(struct intel_lpss *lpss)
{
const struct mfd_cell *cell;
unsigned int type;
type = lpss->caps & LPSS_PRIV_CAPS_TYPE_MASK;
type >>= LPSS_PRIV_CAPS_TYPE_SHIFT;
switch (type) {
case LPSS_DEV_I2C:
cell = &intel_lpss_i2c_cell;
break;
case LPSS_DEV_UART:
cell = &intel_lpss_uart_cell;
break;
case LPSS_DEV_SPI:
cell = &intel_lpss_spi_cell;
break;
default:
return -ENODEV;
}
lpss->cell = devm_kmemdup(lpss->dev, cell, sizeof(*cell), GFP_KERNEL);
if (!lpss->cell)
return -ENOMEM;
lpss->type = type;
return 0;
}
static bool intel_lpss_has_idma(const struct intel_lpss *lpss)
{
return (lpss->caps & LPSS_PRIV_CAPS_NO_IDMA) == 0;
}
static void intel_lpss_set_remap_addr(const struct intel_lpss *lpss)
{
resource_size_t addr = lpss->info->mem->start;
lo_hi_writeq(addr, lpss->priv + LPSS_PRIV_REMAP_ADDR);
}
static void intel_lpss_deassert_reset(const struct intel_lpss *lpss)
{
u32 value = LPSS_PRIV_RESETS_FUNC | LPSS_PRIV_RESETS_IDMA;
/* Bring out the device from reset */
writel(value, lpss->priv + LPSS_PRIV_RESETS);
}
static void intel_lpss_init_dev(const struct intel_lpss *lpss)
{
u32 value = LPSS_PRIV_SSP_REG_DIS_DMA_FIN;
mfd: intel-lpss: Set the device in reset state when init In virtualized setup, when system reboots due to warm reset interrupt storm is seen. Call Trace: <IRQ> dump_stack+0x70/0xa5 __report_bad_irq+0x2e/0xc0 note_interrupt+0x248/0x290 ? add_interrupt_randomness+0x30/0x220 handle_irq_event_percpu+0x54/0x80 handle_irq_event+0x39/0x60 handle_fasteoi_irq+0x91/0x150 handle_irq+0x108/0x180 do_IRQ+0x52/0xf0 common_interrupt+0xf/0xf </IRQ> RIP: 0033:0x76fc2cfabc1d Code: 24 28 bf 03 00 00 00 31 c0 48 8d 35 63 77 0e 00 48 8d 15 2e 94 0e 00 4c 89 f9 49 89 d9 4c 89 d3 e8 b8 e2 01 00 48 8b 54 24 18 <48> 89 ef 48 89 de 4c 89 e1 e8 d5 97 01 00 84 c0 74 2d 48 8b 04 24 RSP: 002b:00007ffd247c1fc0 EFLAGS: 00000293 ORIG_RAX: ffffffffffffffda RAX: 0000000000000000 RBX: 00007ffd247c1ff0 RCX: 000000000003d3ce RDX: 0000000000000000 RSI: 00007ffd247c1ff0 RDI: 000076fc2cbb6010 RBP: 000076fc2cded010 R08: 00007ffd247c2210 R09: 00007ffd247c22a0 R10: 000076fc29465470 R11: 0000000000000000 R12: 00007ffd247c1fc0 R13: 000076fc2ce8e470 R14: 000076fc27ec9960 R15: 0000000000000414 handlers: [<000000000d3fa913>] idma64_irq Disabling IRQ #27 To avoid interrupt storm, set the device in reset state before bringing out the device from reset state. Changelog v2: - correct the subject line by adding "mfd: " Signed-off-by: Binbin Wu <binbin.wu@intel.com> Acked-by: Mika Westerberg <mika.westerberg@linux.intel.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Lee Jones <lee.jones@linaro.org>
2019-04-08 16:09:10 +08:00
/* Set the device in reset state */
writel(0, lpss->priv + LPSS_PRIV_RESETS);
intel_lpss_deassert_reset(lpss);
intel_lpss_set_remap_addr(lpss);
if (!intel_lpss_has_idma(lpss))
return;
/* Make sure that SPI multiblock DMA transfers are re-enabled */
if (lpss->type == LPSS_DEV_SPI)
writel(value, lpss->priv + LPSS_PRIV_SSP_REG);
}
static void intel_lpss_unregister_clock_tree(struct clk *clk)
{
struct clk *parent;
while (clk) {
parent = clk_get_parent(clk);
clk_unregister(clk);
clk = parent;
}
}
static int intel_lpss_register_clock_divider(struct intel_lpss *lpss,
const char *devname,
struct clk **clk)
{
char name[32];
struct clk *tmp = *clk;
snprintf(name, sizeof(name), "%s-enable", devname);
tmp = clk_register_gate(NULL, name, __clk_get_name(tmp), 0,
lpss->priv, 0, 0, NULL);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
snprintf(name, sizeof(name), "%s-div", devname);
tmp = clk_register_fractional_divider(NULL, name, __clk_get_name(tmp),
0, lpss->priv, 1, 15, 16, 15, 0,
NULL);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
*clk = tmp;
snprintf(name, sizeof(name), "%s-update", devname);
tmp = clk_register_gate(NULL, name, __clk_get_name(tmp),
CLK_SET_RATE_PARENT, lpss->priv, 31, 0, NULL);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
*clk = tmp;
return 0;
}
static int intel_lpss_register_clock(struct intel_lpss *lpss)
{
const struct mfd_cell *cell = lpss->cell;
struct clk *clk;
char devname[24];
int ret;
if (!lpss->info->clk_rate)
return 0;
/* Root clock */
clk = clk_register_fixed_rate(NULL, dev_name(lpss->dev), NULL, 0,
lpss->info->clk_rate);
if (IS_ERR(clk))
return PTR_ERR(clk);
snprintf(devname, sizeof(devname), "%s.%d", cell->name, lpss->devid);
/*
* Support for clock divider only if it has some preset value.
* Otherwise we assume that the divider is not used.
*/
if (lpss->type != LPSS_DEV_I2C) {
ret = intel_lpss_register_clock_divider(lpss, devname, &clk);
if (ret)
goto err_clk_register;
}
ret = -ENOMEM;
/* Clock for the host controller */
lpss->clock = clkdev_create(clk, lpss->info->clk_con_id, "%s", devname);
if (!lpss->clock)
goto err_clk_register;
lpss->clk = clk;
return 0;
err_clk_register:
intel_lpss_unregister_clock_tree(clk);
return ret;
}
static void intel_lpss_unregister_clock(struct intel_lpss *lpss)
{
if (IS_ERR_OR_NULL(lpss->clk))
return;
clkdev_drop(lpss->clock);
intel_lpss_unregister_clock_tree(lpss->clk);
}
int intel_lpss_probe(struct device *dev,
const struct intel_lpss_platform_info *info)
{
struct intel_lpss *lpss;
int ret;
if (!info || !info->mem || info->irq <= 0)
return -EINVAL;
lpss = devm_kzalloc(dev, sizeof(*lpss), GFP_KERNEL);
if (!lpss)
return -ENOMEM;
lpss->priv = devm_ioremap_uc(dev, info->mem->start + LPSS_PRIV_OFFSET,
LPSS_PRIV_SIZE);
if (!lpss->priv)
return -ENOMEM;
lpss->info = info;
lpss->dev = dev;
lpss->caps = readl(lpss->priv + LPSS_PRIV_CAPS);
dev_set_drvdata(dev, lpss);
ret = intel_lpss_assign_devs(lpss);
if (ret)
return ret;
lpss->cell->properties = info->properties;
intel_lpss_init_dev(lpss);
lpss->devid = ida_simple_get(&intel_lpss_devid_ida, 0, 0, GFP_KERNEL);
if (lpss->devid < 0)
return lpss->devid;
ret = intel_lpss_register_clock(lpss);
if (ret)
goto err_clk_register;
intel_lpss_ltr_expose(lpss);
ret = intel_lpss_debugfs_add(lpss);
if (ret)
dev_warn(dev, "Failed to create debugfs entries\n");
if (intel_lpss_has_idma(lpss)) {
ret = mfd_add_devices(dev, lpss->devid, &intel_lpss_idma64_cell,
1, info->mem, info->irq, NULL);
if (ret)
dev_warn(dev, "Failed to add %s, fallback to PIO\n",
LPSS_IDMA64_DRIVER_NAME);
}
ret = mfd_add_devices(dev, lpss->devid, lpss->cell,
1, info->mem, info->irq, NULL);
if (ret)
goto err_remove_ltr;
dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND);
return 0;
err_remove_ltr:
intel_lpss_debugfs_remove(lpss);
intel_lpss_ltr_hide(lpss);
intel_lpss_unregister_clock(lpss);
err_clk_register:
ida_simple_remove(&intel_lpss_devid_ida, lpss->devid);
return ret;
}
EXPORT_SYMBOL_GPL(intel_lpss_probe);
void intel_lpss_remove(struct device *dev)
{
struct intel_lpss *lpss = dev_get_drvdata(dev);
mfd_remove_devices(dev);
intel_lpss_debugfs_remove(lpss);
intel_lpss_ltr_hide(lpss);
intel_lpss_unregister_clock(lpss);
ida_simple_remove(&intel_lpss_devid_ida, lpss->devid);
}
EXPORT_SYMBOL_GPL(intel_lpss_remove);
static int resume_lpss_device(struct device *dev, void *data)
{
if (!dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND))
pm_runtime_resume(dev);
return 0;
}
int intel_lpss_prepare(struct device *dev)
{
/*
* Resume both child devices before entering system sleep. This
* ensures that they are in proper state before they get suspended.
*/
device_for_each_child_reverse(dev, NULL, resume_lpss_device);
return 0;
}
EXPORT_SYMBOL_GPL(intel_lpss_prepare);
int intel_lpss_suspend(struct device *dev)
{
struct intel_lpss *lpss = dev_get_drvdata(dev);
unsigned int i;
/* Save device context */
for (i = 0; i < LPSS_PRIV_REG_COUNT; i++)
lpss->priv_ctx[i] = readl(lpss->priv + i * 4);
mfd: intel-lpss: Put I2C and SPI controllers into reset state on suspend Commit 274e43edcda6f ("mfd: intel-lpss: Do not put device in reset state on suspend") changed the behavior on suspend by not putting LPSS controllers into reset. This was done because S3/S0ix fail if UART device is put into reset and no_console_suspend flag is enabled. Because of the above change, I2C controller gets into a bad state if it observes that the I2C lines are pulled low when power to I2C device is cut off during suspend (generally, I2C lines are pulled to power rail of the I2C device in order to ensure that there is no leakage because of the pulls when device is turned off). This results in the controller timing out for all future I2C operations after resume. It is primarily because of the following sequence of operations: During suspend: 1. I2C controller is disabled, but it is not put into reset. 2. Power to I2C device is cut off. 3. #2 results in the I2C lines being pulled low. ==> At this point the I2C controller gets into a bad state On resume: 1. Power to I2C device is enabled. 2. #2 results in the I2C lines being pulled high. 3. I2C controller is enabled. However, even after enabling the I2C controller, all future I2C xfers fail since the controller is in a bad state and does not attempt to make any transactions and hence times out. In order to ensure that the controller does not get into a bad state, this change puts it into reset if the controller type is not UART. With this change, the order of operations is: During suspend: 1. I2C controller is disabled and put into reset. 2. Power to I2C device is cut off. 3. #2 results in the I2C lines being pulled low. On resume: 1. Power to I2C device is enabled. 2. #2 results in the I2C lines being pulled high. 3. I2C controller is enabled and taken out of reset. Signed-off-by: Furquan Shaikh <furquan@google.com> Signed-off-by: Lee Jones <lee.jones@linaro.org>
2017-07-24 14:02:19 +08:00
/*
* If the device type is not UART, then put the controller into
* reset. UART cannot be put into reset since S3/S0ix fail when
* no_console_suspend flag is enabled.
*/
if (lpss->type != LPSS_DEV_UART)
writel(0, lpss->priv + LPSS_PRIV_RESETS);
return 0;
}
EXPORT_SYMBOL_GPL(intel_lpss_suspend);
int intel_lpss_resume(struct device *dev)
{
struct intel_lpss *lpss = dev_get_drvdata(dev);
unsigned int i;
intel_lpss_deassert_reset(lpss);
/* Restore device context */
for (i = 0; i < LPSS_PRIV_REG_COUNT; i++)
writel(lpss->priv_ctx[i], lpss->priv + i * 4);
return 0;
}
EXPORT_SYMBOL_GPL(intel_lpss_resume);
static int __init intel_lpss_init(void)
{
intel_lpss_debugfs = debugfs_create_dir("intel_lpss", NULL);
return 0;
}
module_init(intel_lpss_init);
static void __exit intel_lpss_exit(void)
{
ida_destroy(&intel_lpss_devid_ida);
debugfs_remove(intel_lpss_debugfs);
}
module_exit(intel_lpss_exit);
MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
MODULE_DESCRIPTION("Intel LPSS core driver");
MODULE_LICENSE("GPL v2");
/*
* Ensure the DMA driver is loaded before the host controller device appears,
* so that the host controller driver can request its DMA channels as early
* as possible.
*
* If the DMA module is not there that's OK as well.
*/
MODULE_SOFTDEP("pre: platform:" LPSS_IDMA64_DRIVER_NAME);