android_kernel_samsung_msm8976/drivers/mfd/sm5703_core.c

567 lines
14 KiB
C

/* drivers/mfd/sm5703_core.c
* SM5703 Multifunction Device Driver
* Charger / Buck / LDOs / FlashLED
*
* Copyright (C) 2013 Siliconmitus Technology Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*/
#include <linux/mfd/core.h>
#include <linux/mfd/sm5703.h>
#include <linux/mfd/sm5703_irq.h>
#include <linux/battery/charger/sm5703_charger.h>
#include <linux/errno.h>
#include <linux/version.h>
#include <linux/device.h>
#include <linux/pm.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#if defined(CONFIG_MFD_SM5703_USE_DT) || (LINUX_VERSION_CODE>=KERNEL_VERSION(3,10,0))
#define SM5703_USE_NEW_MFD_DT_API
#endif
#define SM5703_DECLARE_IRQ(irq) { \
irq, irq, \
irq##_NAME, IORESOURCE_IRQ }
#ifdef CONFIG_CHARGER_SM5703
const static struct resource sm5703_charger_res[] = {
SM5703_DECLARE_IRQ(SM5703_AICL_IRQ),
SM5703_DECLARE_IRQ(SM5703_BATOVP_IRQ),
SM5703_DECLARE_IRQ(SM5703_LOWBATT_IRQ),
SM5703_DECLARE_IRQ(SM5703_VBUSLIMIT_IRQ),
SM5703_DECLARE_IRQ(SM5703_DISLIMIT_IRQ),
SM5703_DECLARE_IRQ(SM5703_VSYSOLP_IRQ),
SM5703_DECLARE_IRQ(SM5703_OTGFAIL_IRQ),
SM5703_DECLARE_IRQ(SM5703_THEMREG_IRQ),
SM5703_DECLARE_IRQ(SM5703_THEMSHDN_IRQ),
SM5703_DECLARE_IRQ(SM5703_VSYSNG_IRQ),
SM5703_DECLARE_IRQ(SM5703_VSYSOK_IRQ),
SM5703_DECLARE_IRQ(SM5703_NOBAT_IRQ),
SM5703_DECLARE_IRQ(SM5703_PRETMROFF_IRQ),
SM5703_DECLARE_IRQ(SM5703_FASTTMROFF_IRQ),
SM5703_DECLARE_IRQ(SM5703_CHGON_IRQ),
SM5703_DECLARE_IRQ(SM5703_Q4FULLON_IRQ),
SM5703_DECLARE_IRQ(SM5703_TOPOFF_IRQ),
SM5703_DECLARE_IRQ(SM5703_DONE_IRQ),
SM5703_DECLARE_IRQ(SM5703_CHGRSTF_IRQ),
};
static struct mfd_cell sm5703_charger_devs[] = {
{
.name = "sm5703-charger",
.num_resources = ARRAY_SIZE(sm5703_charger_res),
.id = -1,
.resources = sm5703_charger_res,
#ifdef SM5703_USE_NEW_MFD_DT_API
.of_compatible = "siliconmitus,sm5703-charger"
#endif /* SM5703_USE_NEW_MFD_DT_API */
},
};
#endif /*CONFIG_CHARGER_SM5703*/
#ifdef CONFIG_FLED_SM5703
const static struct resource sm5703_fled_res[] = {
SM5703_DECLARE_IRQ(SM5703_FLEDSHORT_IRQ),
SM5703_DECLARE_IRQ(SM5703_FLEDOPEN_IRQ),
SM5703_DECLARE_IRQ(SM5703_BOOSTPOK_NG_IRQ),
SM5703_DECLARE_IRQ(SM5703_BOOSTPOK_IRQ),
SM5703_DECLARE_IRQ(SM5703_ABSTMRON_IRQ),
};
static struct mfd_cell sm5703_fled_devs[] = {
{
.name = "sm5703-fled",
.num_resources = ARRAY_SIZE(sm5703_fled_res),
.id = -1,
.resources = sm5703_fled_res,
#ifdef SM5703_USE_NEW_MFD_DT_API
.of_compatible = "siliconmitus,sm5703-fled"
#endif /* SM5703_USE_NEW_MFD_DT_API */
},
};
#endif /*CONFIG_FLED_SM5703*/
#ifdef CONFIG_REGULATOR_SM5703
#define SM5703_OF_COMPATIBLE_USBLDO1 "siliconmitus,sm5703-usbldo1"
#define SM5703_OF_COMPATIBLE_USBLDO2 "siliconmitus,sm5703-usbldo2"
#define SM5703_OF_COMPATIBLE_LDO1 "siliconmitus,sm5703-ldo1"
#define SM5703_OF_COMPATIBLE_LDO2 "siliconmitus,sm5703-ldo2"
#define SM5703_OF_COMPATIBLE_LDO3 "siliconmitus,sm5703-ldo3"
#define SM5703_OF_COMPATIBLE_BUCK "siliconmitus,sm5703-buck"
#ifdef SM5703_USE_NEW_MFD_DT_API
#define REG_OF_COMP(_id) .of_compatible = SM5703_OF_COMPATIBLE_##_id,
#else
#define REG_OF_COMP(_id)
#endif /* SM5703_USE_NEW_MFD_DT_API */
#define SM5703_VR_DEVS(_id) \
{ \
.name = "sm5703-regulator", \
.id = SM5703_ID_##_id, \
REG_OF_COMP(_id) \
}
static struct mfd_cell sm5703_regulator_devs[] = {
SM5703_VR_DEVS(USBLDO1),
SM5703_VR_DEVS(USBLDO2),
SM5703_VR_DEVS(LDO1),
SM5703_VR_DEVS(LDO2),
SM5703_VR_DEVS(LDO3),
SM5703_VR_DEVS(BUCK),
};
#endif
inline static int sm5703_read_device(struct i2c_client *i2c,
int reg, int bytes, void *dest)
{
int ret;
if (bytes > 1) {
ret = i2c_smbus_read_i2c_block_data(i2c, reg, bytes, dest);
} else {
ret = i2c_smbus_read_byte_data(i2c, reg);
if (ret < 0)
return ret;
*(unsigned char *)dest = (unsigned char)ret;
}
return ret;
}
inline static int sm5703_write_device(struct i2c_client *i2c,
int reg, int bytes, const void *src)
{
int ret;
const uint8_t *data;
if (bytes > 1)
ret = i2c_smbus_write_i2c_block_data(i2c, reg, bytes, src);
else {
data = src;
ret = i2c_smbus_write_byte_data(i2c, reg, *data);
}
return ret;
}
int sm5703_block_read_device(struct i2c_client *i2c,
int reg, int bytes, void *dest)
{
struct sm5703_mfd_chip *chip = i2c_get_clientdata(i2c);
int ret;
mutex_lock(&chip->io_lock);
ret = sm5703_read_device(i2c, reg, bytes, dest);
mutex_unlock(&chip->io_lock);
return ret;
}
EXPORT_SYMBOL(sm5703_block_read_device);
int sm5703_block_write_device(struct i2c_client *i2c,
int reg, int bytes, const void *src)
{
struct sm5703_mfd_chip *chip = i2c_get_clientdata(i2c);
int ret;
mutex_lock(&chip->io_lock);
ret = sm5703_write_device(i2c, reg, bytes, src);
mutex_unlock(&chip->io_lock);
return ret;
}
EXPORT_SYMBOL(sm5703_block_write_device);
int sm5703_reg_read(struct i2c_client *i2c, int reg)
{
struct sm5703_mfd_chip *chip = i2c_get_clientdata(i2c);
unsigned char data = 0;
int ret;
mutex_lock(&chip->io_lock);
ret = sm5703_read_device(i2c, reg, 1, &data);
mutex_unlock(&chip->io_lock);
if (ret < 0)
return ret;
else
return (int)data;
}
EXPORT_SYMBOL(sm5703_reg_read);
int sm5703_reg_write(struct i2c_client *i2c, int reg,
unsigned char data)
{
struct sm5703_mfd_chip *chip = i2c_get_clientdata(i2c);
int ret;
mutex_lock(&chip->io_lock);
ret = i2c_smbus_write_byte_data(i2c, reg, data);
mutex_unlock(&chip->io_lock);
return ret;
}
EXPORT_SYMBOL(sm5703_reg_write);
int sm5703_assign_bits(struct i2c_client *i2c, int reg,
unsigned char mask, unsigned char data)
{
struct sm5703_mfd_chip *chip = i2c_get_clientdata(i2c);
unsigned char value;
int ret;
mutex_lock(&chip->io_lock);
ret = sm5703_read_device(i2c, reg, 1, &value);
if (ret < 0)
goto out;
value &= ~mask;
value |= data;
ret = i2c_smbus_write_byte_data(i2c, reg, value);
out:
mutex_unlock(&chip->io_lock);
return ret;
}
EXPORT_SYMBOL(sm5703_assign_bits);
int sm5703_set_bits(struct i2c_client *i2c, int reg,
unsigned char mask)
{
return sm5703_assign_bits(i2c,reg,mask,mask);
}
EXPORT_SYMBOL(sm5703_set_bits);
int sm5703_clr_bits(struct i2c_client *i2c, int reg,
unsigned char mask)
{
return sm5703_assign_bits(i2c,reg,mask,0);
}
EXPORT_SYMBOL(sm5703_clr_bits);
extern int sm5703_init_irq(sm5703_mfd_chip_t *chip);
extern int sm5703_exit_irq(sm5703_mfd_chip_t *chip);
static int sm5703mfd_parse_dt(struct device *dev,
sm5703_mfd_platform_data_t *pdata)
{
int ret;
struct device_node *np = dev->of_node;
enum of_gpio_flags irq_gpio_flags;
ret = pdata->irq_gpio = of_get_named_gpio_flags(np, "sm5703,irq-gpio",
0, &irq_gpio_flags);
if (ret < 0) {
dev_err(dev, "%s : can't get irq-gpio\r\n", __FUNCTION__);
return ret;
}
pdata->irq_base = -1;
ret = of_property_read_u32(np, "sm5703,irq-base", (u32*)&pdata->irq_base);
if (ret < 0 || pdata->irq_base == -1) {
dev_info(dev, "%s : no assignment of irq_base, use irq_alloc_descs()\r\n",
__FUNCTION__);
ret = pdata->mrstb_gpio = of_get_named_gpio_flags(np,
"sm5703,mrstb-gpio", 0, NULL);
if (ret < 0) {
dev_err(dev, "%s : can't get mrstb-gpio\r\n", __FUNCTION__);
return ret;
}
}
return 0;
}
static int sm5703_mfd_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
int ret = 0;
sm5703_mfd_chip_t *chip;
sm5703_mfd_platform_data_t *pdata = i2c->dev.platform_data;
pr_info("%s : SM5703 MFD Driver %s start probing\n",
__func__, SM5703_DRV_VER);
//if (of_node)
if(1)
{
pdata = devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
dev_err(&i2c->dev, "Failed to allocate memory\n");
ret = -ENOMEM;
goto err_dt_nomem;
}
ret = sm5703mfd_parse_dt(&i2c->dev, pdata);
if (ret < 0)
goto err_parse_dt;
} else {
pdata = i2c->dev.platform_data;
}
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (chip == NULL) {
dev_err(&i2c->dev, "Memory is not enough.\n");
ret = -ENOMEM;
goto err_mfd_nomem;
}
chip->dev = &i2c->dev;
ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK);
if (!ret) {
ret = i2c_get_functionality(i2c->adapter);
dev_err(chip->dev, "I2C functionality is not supported.\n");
ret = -ENOSYS;
goto err_i2cfunc_not_support;
}
chip->i2c_client = i2c;
chip->pdata = pdata;
pr_info("%s:%s pdata->irq_base = %d\n",
"sm5703-mfd", __func__, pdata->irq_base);
/* if board-init had already assigned irq_base (>=0) ,
no need to allocate it;
assign -1 to let this driver allocate resource by itself*/
if (pdata->irq_base < 0)
pdata->irq_base = irq_alloc_descs(-1, 0, SM5703_IRQS_NR, 0);
if (pdata->irq_base < 0) {
pr_err("%s:%s irq_alloc_descs Fail! ret(%d)\n",
"sm5703-mfd", __func__, pdata->irq_base);
ret = -EINVAL;
goto irq_base_err;
} else {
chip->irq_base = pdata->irq_base;
pr_info("%s:%s irq_base = %d\n",
"sm5703-mfd", __func__, chip->irq_base);
}
i2c_set_clientdata(i2c, chip);
mutex_init(&chip->io_lock);
mutex_init(&chip->suspend_flag_lock);
/* Set MRSTB GPIO pin to high level to indicate that
* system is alive (do NOT do reset) */
ret = gpio_request(pdata->mrstb_gpio, "sm5703_mrstb");
if (ret == 0) {
ret = gpio_direction_output(pdata->mrstb_gpio, 1);
if (ret < 0)
pr_err("%s : cannot set GPIO%d output direction(%d)\n",
__func__, pdata->mrstb_gpio, ret);
} else
pr_info("%s : Request GPIO %d failed\n",
__func__, (int)pdata->mrstb_gpio);
wake_lock_init(&(chip->irq_wake_lock), WAKE_LOCK_SUSPEND,
"sm5703mfd_wakelock");
ret = sm5703_init_irq(chip);
if (ret < 0) {
dev_err(chip->dev,
"Error : can't initialize SM5703 MFD irq\n");
goto err_init_irq;
}
#ifdef CONFIG_REGULATOR_SM5703
#if (LINUX_VERSION_CODE>=KERNEL_VERSION(3,6,0))
ret = mfd_add_devices(chip->dev, 0, &sm5703_regulator_devs[0],
ARRAY_SIZE(sm5703_regulator_devs),
NULL, chip->irq_base, NULL);
#else
ret = mfd_add_devices(chip->dev, 0, &sm5703_regulator_devs[0],
ARRAY_SIZE(sm5703_regulator_devs),
NULL, chip->irq_base);
#endif
if (ret < 0) {
dev_err(chip->dev,
"Error : can't add regulator\n");
goto err_add_regulator_devs;
}
#endif /*CONFIG_REGULATOR_SM5703*/
#ifdef CONFIG_FLED_SM5703
#if (LINUX_VERSION_CODE>=KERNEL_VERSION(3,6,0))
ret = mfd_add_devices(chip->dev, 0, &sm5703_fled_devs[0],
ARRAY_SIZE(sm5703_fled_devs),
NULL, chip->irq_base, NULL);
#else
ret = mfd_add_devices(chip->dev, 0, &sm5703_fled_devs[0],
ARRAY_SIZE(sm5703_fled_devs),
NULL, chip->irq_base);
#endif
if (ret < 0) {
dev_err(chip->dev, "Failed : add FlashLED devices");
goto err_add_fled_devs;
}
#endif /*CONFIG_FLED_SM5703*/
#ifdef CONFIG_CHARGER_SM5703
#if (LINUX_VERSION_CODE>=KERNEL_VERSION(3,6,0))
ret = mfd_add_devices(chip->dev, 0, &sm5703_charger_devs[0],
ARRAY_SIZE(sm5703_charger_devs),
NULL, chip->irq_base, NULL);
#else
ret = mfd_add_devices(chip->dev, 0, &sm5703_charger_devs[0],
ARRAY_SIZE(sm5703_charger_devs),
NULL, chip->irq_base);
#endif
if (ret<0) {
dev_err(chip->dev, "Failed : add charger devices\n");
goto err_add_chg_devs;
}
#endif /*CONFIG_CHARGER_SM5703*/
pr_info("%s : SM5703 MFD Driver Fin probe\n", __func__);
return ret;
#ifdef CONFIG_CHARGER_SM5703
err_add_chg_devs:
#endif /*CONFIG_CHARGER_SM5703*/
#ifdef CONFIG_FLED_SM5703
err_add_fled_devs:
#endif /*CONFIG_FLED_SM5703*/
mfd_remove_devices(chip->dev);
#ifdef CONFIG_REGULATOR_SM5703
err_add_regulator_devs:
#endif /*CONFIG_REGULATOR_SM5703*/
err_init_irq:
wake_lock_destroy(&(chip->irq_wake_lock));
mutex_destroy(&chip->io_lock);
kfree(chip);
irq_base_err:
err_mfd_nomem:
err_i2cfunc_not_support:
err_parse_dt:
err_dt_nomem:
return ret;
}
static int sm5703_mfd_remove(struct i2c_client *i2c)
{
sm5703_mfd_chip_t *chip = i2c_get_clientdata(i2c);
pr_info("%s : SM5703 MFD Driver remove\n", __func__);
gpio_free(chip->pdata->mrstb_gpio);
mfd_remove_devices(chip->dev);
wake_lock_destroy(&(chip->irq_wake_lock));
mutex_destroy(&chip->suspend_flag_lock);
mutex_destroy(&chip->io_lock);
kfree(chip);
return 0;
}
#ifdef CONFIG_PM
extern int sm5703_irq_suspend(sm5703_mfd_chip_t *chip);
extern int sm5703_irq_resume(sm5703_mfd_chip_t *chip);
int sm5703_mfd_suspend(struct device *dev)
{
struct i2c_client *i2c =
container_of(dev, struct i2c_client, dev);
sm5703_mfd_chip_t *chip = i2c_get_clientdata(i2c);
BUG_ON(chip == NULL);
return sm5703_irq_suspend(chip);
}
int sm5703_mfd_resume(struct device *dev)
{
struct i2c_client *i2c =
container_of(dev, struct i2c_client, dev);
sm5703_mfd_chip_t *chip = i2c_get_clientdata(i2c);
BUG_ON(chip == NULL);
return sm5703_irq_resume(chip);
}
#endif /* CONFIG_PM */
static void sm5703_mfd_shutdown(struct i2c_client *client)
{
struct i2c_client *i2c = client;
sm5703_assign_bits(i2c, SM5703_CNTL, SM5703_OPERATION_MODE, SM5703_OPERATION_MODE_CHARGING_ON);
}
static const struct i2c_device_id sm5703_mfd_id_table[] = {
{ "sm5703-mfd", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, sm5703_id_table);
#ifdef CONFIG_PM
const struct dev_pm_ops sm5703_pm = {
.suspend = sm5703_mfd_suspend,
.resume = sm5703_mfd_resume,
};
#endif
#ifdef CONFIG_OF
static struct of_device_id sm5703_match_table[] = {
{ .compatible = "siliconmitus,sm5703mfd",},
{},
};
#else
#define sm5703_match_table NULL
#endif
static struct i2c_driver sm5703_mfd_driver = {
.driver = {
.name = "sm5703-mfd",
.owner = THIS_MODULE,
.of_match_table = sm5703_match_table,
#ifdef CONFIG_PM
.pm = &sm5703_pm,
#endif
},
.shutdown = sm5703_mfd_shutdown,
.probe = sm5703_mfd_probe,
.remove = sm5703_mfd_remove,
.id_table = sm5703_mfd_id_table,
};
static int __init sm5703_mfd_i2c_init(void)
{
int ret;
pr_info("%s : SM5703 init\n", __func__);
ret = i2c_add_driver(&sm5703_mfd_driver);
if (ret != 0)
pr_info("%s : Failed to register SM5703 MFD I2C driver\n",
__func__);
return ret;
}
subsys_initcall(sm5703_mfd_i2c_init);
static void __exit sm5703_mfd_i2c_exit(void)
{
i2c_del_driver(&sm5703_mfd_driver);
}
module_exit(sm5703_mfd_i2c_exit);
MODULE_DESCRIPTION("Siliconmitus SM5703 MFD I2C Driver");
MODULE_VERSION(SM5703_DRV_VER);
MODULE_LICENSE("GPL");