linux-stable/drivers/mfd/sec-core.c
Krzysztof Kozlowski f6d6daaf9b mfd: sec: Add PM ops and make it a wake up source
Add PM suspend/resume ops to the sec MFD core driver and make it a wake
up source. This allows proper waking from suspend to RAM and also fixes
broken interrupts after resuming:
[   42.705703] sec_pmic 7-0066: Failed to read IRQ status: -5

Interrupts stop working after first resume initiated by them (e.g. by
RTC Alarm interrupt) because interrupt registers were not cleared properly.

When device is woken up from suspend by RTC Alarm, an interrupt occurs
before resuming I2C bus controller. The interrupt is handled by
regmap_irq_thread which tries to read RTC registers. This read fails
(I2C is still suspended) and RTC Alarm interrupt is disabled.

Disable the S5M8767 interrupts during suspend (disable_irq()) and enable
them during resume so the device will be still woken up but the interrupt
won't happen before resuming I2C bus.

Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
2014-01-21 08:13:35 +00:00

416 lines
9.6 KiB
C

/*
* sec-core.c
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/pm_runtime.h>
#include <linux/mutex.h>
#include <linux/mfd/core.h>
#include <linux/mfd/samsung/core.h>
#include <linux/mfd/samsung/irq.h>
#include <linux/mfd/samsung/rtc.h>
#include <linux/mfd/samsung/s2mps11.h>
#include <linux/mfd/samsung/s5m8763.h>
#include <linux/mfd/samsung/s5m8767.h>
#include <linux/regmap.h>
static const struct mfd_cell s5m8751_devs[] = {
{
.name = "s5m8751-pmic",
}, {
.name = "s5m-charger",
}, {
.name = "s5m8751-codec",
},
};
static const struct mfd_cell s5m8763_devs[] = {
{
.name = "s5m8763-pmic",
}, {
.name = "s5m-rtc",
}, {
.name = "s5m-charger",
},
};
static const struct mfd_cell s5m8767_devs[] = {
{
.name = "s5m8767-pmic",
}, {
.name = "s5m-rtc",
},
};
static const struct mfd_cell s2mps11_devs[] = {
{
.name = "s2mps11-pmic",
}, {
.name = "s2mps11-clk",
}
};
#ifdef CONFIG_OF
static struct of_device_id sec_dt_match[] = {
{ .compatible = "samsung,s5m8767-pmic",
.data = (void *)S5M8767X,
},
{ .compatible = "samsung,s2mps11-pmic",
.data = (void *)S2MPS11X,
},
{},
};
#endif
int sec_reg_read(struct sec_pmic_dev *sec_pmic, u8 reg, void *dest)
{
return regmap_read(sec_pmic->regmap_pmic, reg, dest);
}
EXPORT_SYMBOL_GPL(sec_reg_read);
int sec_bulk_read(struct sec_pmic_dev *sec_pmic, u8 reg, int count, u8 *buf)
{
return regmap_bulk_read(sec_pmic->regmap_pmic, reg, buf, count);
}
EXPORT_SYMBOL_GPL(sec_bulk_read);
int sec_reg_write(struct sec_pmic_dev *sec_pmic, u8 reg, u8 value)
{
return regmap_write(sec_pmic->regmap_pmic, reg, value);
}
EXPORT_SYMBOL_GPL(sec_reg_write);
int sec_bulk_write(struct sec_pmic_dev *sec_pmic, u8 reg, int count, u8 *buf)
{
return regmap_raw_write(sec_pmic->regmap_pmic, reg, buf, count);
}
EXPORT_SYMBOL_GPL(sec_bulk_write);
int sec_reg_update(struct sec_pmic_dev *sec_pmic, u8 reg, u8 val, u8 mask)
{
return regmap_update_bits(sec_pmic->regmap_pmic, reg, mask, val);
}
EXPORT_SYMBOL_GPL(sec_reg_update);
static bool s2mps11_volatile(struct device *dev, unsigned int reg)
{
switch (reg) {
case S2MPS11_REG_INT1M:
case S2MPS11_REG_INT2M:
case S2MPS11_REG_INT3M:
return false;
default:
return true;
}
}
static bool s5m8763_volatile(struct device *dev, unsigned int reg)
{
switch (reg) {
case S5M8763_REG_IRQM1:
case S5M8763_REG_IRQM2:
case S5M8763_REG_IRQM3:
case S5M8763_REG_IRQM4:
return false;
default:
return true;
}
}
static struct regmap_config sec_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
};
static struct regmap_config s2mps11_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = S2MPS11_REG_L38CTRL,
.volatile_reg = s2mps11_volatile,
.cache_type = REGCACHE_FLAT,
};
static struct regmap_config s5m8763_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = S5M8763_REG_LBCNFG2,
.volatile_reg = s5m8763_volatile,
.cache_type = REGCACHE_FLAT,
};
static struct regmap_config s5m8767_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = S5M8767_REG_LDO28CTRL,
.volatile_reg = s2mps11_volatile,
.cache_type = REGCACHE_FLAT,
};
static const struct regmap_config sec_rtc_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
};
#ifdef CONFIG_OF
/*
* Only the common platform data elements for s5m8767 are parsed here from the
* device tree. Other sub-modules of s5m8767 such as pmic, rtc , charger and
* others have to parse their own platform data elements from device tree.
*
* The s5m8767 platform data structure is instantiated here and the drivers for
* the sub-modules need not instantiate another instance while parsing their
* platform data.
*/
static struct sec_platform_data *sec_pmic_i2c_parse_dt_pdata(
struct device *dev)
{
struct sec_platform_data *pd;
pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
if (!pd) {
dev_err(dev, "could not allocate memory for pdata\n");
return ERR_PTR(-ENOMEM);
}
/*
* ToDo: the 'wakeup' member in the platform data is more of a linux
* specfic information. Hence, there is no binding for that yet and
* not parsed here.
*/
return pd;
}
#else
static struct sec_platform_data *sec_pmic_i2c_parse_dt_pdata(
struct device *dev)
{
return 0;
}
#endif
static inline int sec_i2c_get_driver_data(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
#ifdef CONFIG_OF
if (i2c->dev.of_node) {
const struct of_device_id *match;
match = of_match_node(sec_dt_match, i2c->dev.of_node);
return (int)match->data;
}
#endif
return (int)id->driver_data;
}
static int sec_pmic_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct sec_platform_data *pdata = dev_get_platdata(&i2c->dev);
const struct regmap_config *regmap;
struct sec_pmic_dev *sec_pmic;
int ret;
sec_pmic = devm_kzalloc(&i2c->dev, sizeof(struct sec_pmic_dev),
GFP_KERNEL);
if (sec_pmic == NULL)
return -ENOMEM;
i2c_set_clientdata(i2c, sec_pmic);
sec_pmic->dev = &i2c->dev;
sec_pmic->i2c = i2c;
sec_pmic->irq = i2c->irq;
sec_pmic->type = sec_i2c_get_driver_data(i2c, id);
if (sec_pmic->dev->of_node) {
pdata = sec_pmic_i2c_parse_dt_pdata(sec_pmic->dev);
if (IS_ERR(pdata)) {
ret = PTR_ERR(pdata);
return ret;
}
pdata->device_type = sec_pmic->type;
}
if (pdata) {
sec_pmic->device_type = pdata->device_type;
sec_pmic->ono = pdata->ono;
sec_pmic->irq_base = pdata->irq_base;
sec_pmic->wakeup = pdata->wakeup;
sec_pmic->pdata = pdata;
}
switch (sec_pmic->device_type) {
case S2MPS11X:
regmap = &s2mps11_regmap_config;
break;
case S5M8763X:
regmap = &s5m8763_regmap_config;
break;
case S5M8767X:
regmap = &s5m8767_regmap_config;
break;
default:
regmap = &sec_regmap_config;
break;
}
sec_pmic->regmap_pmic = devm_regmap_init_i2c(i2c, regmap);
if (IS_ERR(sec_pmic->regmap_pmic)) {
ret = PTR_ERR(sec_pmic->regmap_pmic);
dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
ret);
return ret;
}
sec_pmic->rtc = i2c_new_dummy(i2c->adapter, RTC_I2C_ADDR);
i2c_set_clientdata(sec_pmic->rtc, sec_pmic);
sec_pmic->regmap_rtc = devm_regmap_init_i2c(sec_pmic->rtc,
&sec_rtc_regmap_config);
if (IS_ERR(sec_pmic->regmap_rtc)) {
ret = PTR_ERR(sec_pmic->regmap_rtc);
dev_err(&i2c->dev, "Failed to allocate RTC register map: %d\n",
ret);
return ret;
}
if (pdata && pdata->cfg_pmic_irq)
pdata->cfg_pmic_irq();
sec_irq_init(sec_pmic);
pm_runtime_set_active(sec_pmic->dev);
switch (sec_pmic->device_type) {
case S5M8751X:
ret = mfd_add_devices(sec_pmic->dev, -1, s5m8751_devs,
ARRAY_SIZE(s5m8751_devs), NULL, 0, NULL);
break;
case S5M8763X:
ret = mfd_add_devices(sec_pmic->dev, -1, s5m8763_devs,
ARRAY_SIZE(s5m8763_devs), NULL, 0, NULL);
break;
case S5M8767X:
ret = mfd_add_devices(sec_pmic->dev, -1, s5m8767_devs,
ARRAY_SIZE(s5m8767_devs), NULL, 0, NULL);
break;
case S2MPS11X:
ret = mfd_add_devices(sec_pmic->dev, -1, s2mps11_devs,
ARRAY_SIZE(s2mps11_devs), NULL, 0, NULL);
break;
default:
/* If this happens the probe function is problem */
BUG();
}
if (ret)
goto err;
device_init_wakeup(sec_pmic->dev, sec_pmic->wakeup);
return ret;
err:
sec_irq_exit(sec_pmic);
i2c_unregister_device(sec_pmic->rtc);
return ret;
}
static int sec_pmic_remove(struct i2c_client *i2c)
{
struct sec_pmic_dev *sec_pmic = i2c_get_clientdata(i2c);
mfd_remove_devices(sec_pmic->dev);
sec_irq_exit(sec_pmic);
i2c_unregister_device(sec_pmic->rtc);
return 0;
}
static int sec_pmic_suspend(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct sec_pmic_dev *sec_pmic = i2c_get_clientdata(i2c);
if (device_may_wakeup(dev)) {
enable_irq_wake(sec_pmic->irq);
/*
* PMIC IRQ must be disabled during suspend for RTC alarm
* to work properly.
* When device is woken up from suspend by RTC Alarm, an
* interrupt occurs before resuming I2C bus controller.
* The interrupt is handled by regmap_irq_thread which tries
* to read RTC registers. This read fails (I2C is still
* suspended) and RTC Alarm interrupt is disabled.
*/
disable_irq(sec_pmic->irq);
}
return 0;
}
static int sec_pmic_resume(struct device *dev)
{
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
struct sec_pmic_dev *sec_pmic = i2c_get_clientdata(i2c);
if (device_may_wakeup(dev)) {
disable_irq_wake(sec_pmic->irq);
enable_irq(sec_pmic->irq);
}
return 0;
}
static SIMPLE_DEV_PM_OPS(sec_pmic_pm_ops, sec_pmic_suspend, sec_pmic_resume);
static const struct i2c_device_id sec_pmic_id[] = {
{ "sec_pmic", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sec_pmic_id);
static struct i2c_driver sec_pmic_driver = {
.driver = {
.name = "sec_pmic",
.owner = THIS_MODULE,
.pm = &sec_pmic_pm_ops,
.of_match_table = of_match_ptr(sec_dt_match),
},
.probe = sec_pmic_probe,
.remove = sec_pmic_remove,
.id_table = sec_pmic_id,
};
static int __init sec_pmic_init(void)
{
return i2c_add_driver(&sec_pmic_driver);
}
subsys_initcall(sec_pmic_init);
static void __exit sec_pmic_exit(void)
{
i2c_del_driver(&sec_pmic_driver);
}
module_exit(sec_pmic_exit);
MODULE_AUTHOR("Sangbeom Kim <sbkim73@samsung.com>");
MODULE_DESCRIPTION("Core support for the S5M MFD");
MODULE_LICENSE("GPL");