| /* |
| * Core driver for TI TPS65090 PMIC family |
| * |
| * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. |
| |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/mfd/core.h> |
| #include <linux/mfd/tps65090.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/err.h> |
| |
| #define NUM_INT_REG 2 |
| |
| #define TPS65090_INT1_MASK_VAC_STATUS_CHANGE 1 |
| #define TPS65090_INT1_MASK_VSYS_STATUS_CHANGE 2 |
| #define TPS65090_INT1_MASK_BAT_STATUS_CHANGE 3 |
| #define TPS65090_INT1_MASK_CHARGING_STATUS_CHANGE 4 |
| #define TPS65090_INT1_MASK_CHARGING_COMPLETE 5 |
| #define TPS65090_INT1_MASK_OVERLOAD_DCDC1 6 |
| #define TPS65090_INT1_MASK_OVERLOAD_DCDC2 7 |
| #define TPS65090_INT2_MASK_OVERLOAD_DCDC3 0 |
| #define TPS65090_INT2_MASK_OVERLOAD_FET1 1 |
| #define TPS65090_INT2_MASK_OVERLOAD_FET2 2 |
| #define TPS65090_INT2_MASK_OVERLOAD_FET3 3 |
| #define TPS65090_INT2_MASK_OVERLOAD_FET4 4 |
| #define TPS65090_INT2_MASK_OVERLOAD_FET5 5 |
| #define TPS65090_INT2_MASK_OVERLOAD_FET6 6 |
| #define TPS65090_INT2_MASK_OVERLOAD_FET7 7 |
| |
| static struct resource charger_resources[] = { |
| { |
| .start = TPS65090_IRQ_VAC_STATUS_CHANGE, |
| .end = TPS65090_IRQ_VAC_STATUS_CHANGE, |
| .flags = IORESOURCE_IRQ, |
| } |
| }; |
| |
| enum tps65090_cells { |
| PMIC = 0, |
| CHARGER = 1, |
| }; |
| |
| static struct mfd_cell tps65090s[] = { |
| [PMIC] = { |
| .name = "tps65090-pmic", |
| }, |
| [CHARGER] = { |
| .name = "tps65090-charger", |
| .num_resources = ARRAY_SIZE(charger_resources), |
| .resources = &charger_resources[0], |
| .of_compatible = "ti,tps65090-charger", |
| }, |
| }; |
| |
| static const struct regmap_irq tps65090_irqs[] = { |
| /* INT1 IRQs*/ |
| [TPS65090_IRQ_VAC_STATUS_CHANGE] = { |
| .mask = TPS65090_INT1_MASK_VAC_STATUS_CHANGE, |
| }, |
| [TPS65090_IRQ_VSYS_STATUS_CHANGE] = { |
| .mask = TPS65090_INT1_MASK_VSYS_STATUS_CHANGE, |
| }, |
| [TPS65090_IRQ_BAT_STATUS_CHANGE] = { |
| .mask = TPS65090_INT1_MASK_BAT_STATUS_CHANGE, |
| }, |
| [TPS65090_IRQ_CHARGING_STATUS_CHANGE] = { |
| .mask = TPS65090_INT1_MASK_CHARGING_STATUS_CHANGE, |
| }, |
| [TPS65090_IRQ_CHARGING_COMPLETE] = { |
| .mask = TPS65090_INT1_MASK_CHARGING_COMPLETE, |
| }, |
| [TPS65090_IRQ_OVERLOAD_DCDC1] = { |
| .mask = TPS65090_INT1_MASK_OVERLOAD_DCDC1, |
| }, |
| [TPS65090_IRQ_OVERLOAD_DCDC2] = { |
| .mask = TPS65090_INT1_MASK_OVERLOAD_DCDC2, |
| }, |
| /* INT2 IRQs*/ |
| [TPS65090_IRQ_OVERLOAD_DCDC3] = { |
| .reg_offset = 1, |
| .mask = TPS65090_INT2_MASK_OVERLOAD_DCDC3, |
| }, |
| [TPS65090_IRQ_OVERLOAD_FET1] = { |
| .reg_offset = 1, |
| .mask = TPS65090_INT2_MASK_OVERLOAD_FET1, |
| }, |
| [TPS65090_IRQ_OVERLOAD_FET2] = { |
| .reg_offset = 1, |
| .mask = TPS65090_INT2_MASK_OVERLOAD_FET2, |
| }, |
| [TPS65090_IRQ_OVERLOAD_FET3] = { |
| .reg_offset = 1, |
| .mask = TPS65090_INT2_MASK_OVERLOAD_FET3, |
| }, |
| [TPS65090_IRQ_OVERLOAD_FET4] = { |
| .reg_offset = 1, |
| .mask = TPS65090_INT2_MASK_OVERLOAD_FET4, |
| }, |
| [TPS65090_IRQ_OVERLOAD_FET5] = { |
| .reg_offset = 1, |
| .mask = TPS65090_INT2_MASK_OVERLOAD_FET5, |
| }, |
| [TPS65090_IRQ_OVERLOAD_FET6] = { |
| .reg_offset = 1, |
| .mask = TPS65090_INT2_MASK_OVERLOAD_FET6, |
| }, |
| [TPS65090_IRQ_OVERLOAD_FET7] = { |
| .reg_offset = 1, |
| .mask = TPS65090_INT2_MASK_OVERLOAD_FET7, |
| }, |
| }; |
| |
| static struct regmap_irq_chip tps65090_irq_chip = { |
| .name = "tps65090", |
| .irqs = tps65090_irqs, |
| .num_irqs = ARRAY_SIZE(tps65090_irqs), |
| .num_regs = NUM_INT_REG, |
| .status_base = TPS65090_REG_INTR_STS, |
| .mask_base = TPS65090_REG_INTR_MASK, |
| .mask_invert = true, |
| }; |
| |
| static bool is_volatile_reg(struct device *dev, unsigned int reg) |
| { |
| /* Nearly all registers have status bits mixed in, except a few */ |
| switch (reg) { |
| case TPS65090_REG_INTR_MASK: |
| case TPS65090_REG_INTR_MASK2: |
| case TPS65090_REG_CG_CTRL0: |
| case TPS65090_REG_CG_CTRL1: |
| case TPS65090_REG_CG_CTRL2: |
| case TPS65090_REG_CG_CTRL3: |
| case TPS65090_REG_CG_CTRL4: |
| case TPS65090_REG_CG_CTRL5: |
| return false; |
| } |
| return true; |
| } |
| |
| static const struct regmap_config tps65090_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = TPS65090_MAX_REG, |
| .num_reg_defaults_raw = TPS65090_NUM_REGS, |
| .cache_type = REGCACHE_RBTREE, |
| .volatile_reg = is_volatile_reg, |
| }; |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id tps65090_of_match[] = { |
| { .compatible = "ti,tps65090",}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, tps65090_of_match); |
| #endif |
| |
| static int tps65090_i2c_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct tps65090_platform_data *pdata = dev_get_platdata(&client->dev); |
| int irq_base = 0; |
| struct tps65090 *tps65090; |
| int ret; |
| |
| if (!pdata && !client->dev.of_node) { |
| dev_err(&client->dev, |
| "tps65090 requires platform data or of_node\n"); |
| return -EINVAL; |
| } |
| |
| if (pdata) |
| irq_base = pdata->irq_base; |
| |
| tps65090 = devm_kzalloc(&client->dev, sizeof(*tps65090), GFP_KERNEL); |
| if (!tps65090) { |
| dev_err(&client->dev, "mem alloc for tps65090 failed\n"); |
| return -ENOMEM; |
| } |
| |
| tps65090->dev = &client->dev; |
| i2c_set_clientdata(client, tps65090); |
| |
| tps65090->rmap = devm_regmap_init_i2c(client, &tps65090_regmap_config); |
| if (IS_ERR(tps65090->rmap)) { |
| ret = PTR_ERR(tps65090->rmap); |
| dev_err(&client->dev, "regmap_init failed with err: %d\n", ret); |
| return ret; |
| } |
| |
| if (client->irq) { |
| ret = regmap_add_irq_chip(tps65090->rmap, client->irq, |
| IRQF_ONESHOT | IRQF_TRIGGER_LOW, irq_base, |
| &tps65090_irq_chip, &tps65090->irq_data); |
| if (ret) { |
| dev_err(&client->dev, |
| "IRQ init failed with err: %d\n", ret); |
| return ret; |
| } |
| } else { |
| /* Don't tell children they have an IRQ that'll never fire */ |
| tps65090s[CHARGER].num_resources = 0; |
| } |
| |
| ret = mfd_add_devices(tps65090->dev, -1, tps65090s, |
| ARRAY_SIZE(tps65090s), NULL, |
| 0, regmap_irq_get_domain(tps65090->irq_data)); |
| if (ret) { |
| dev_err(&client->dev, "add mfd devices failed with err: %d\n", |
| ret); |
| goto err_irq_exit; |
| } |
| |
| return 0; |
| |
| err_irq_exit: |
| if (client->irq) |
| regmap_del_irq_chip(client->irq, tps65090->irq_data); |
| return ret; |
| } |
| |
| static int tps65090_i2c_remove(struct i2c_client *client) |
| { |
| struct tps65090 *tps65090 = i2c_get_clientdata(client); |
| |
| mfd_remove_devices(tps65090->dev); |
| if (client->irq) |
| regmap_del_irq_chip(client->irq, tps65090->irq_data); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id tps65090_id_table[] = { |
| { "tps65090", 0 }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(i2c, tps65090_id_table); |
| |
| static struct i2c_driver tps65090_driver = { |
| .driver = { |
| .name = "tps65090", |
| .of_match_table = of_match_ptr(tps65090_of_match), |
| }, |
| .probe = tps65090_i2c_probe, |
| .remove = tps65090_i2c_remove, |
| .id_table = tps65090_id_table, |
| }; |
| |
| static int __init tps65090_init(void) |
| { |
| return i2c_add_driver(&tps65090_driver); |
| } |
| subsys_initcall(tps65090_init); |
| |
| static void __exit tps65090_exit(void) |
| { |
| i2c_del_driver(&tps65090_driver); |
| } |
| module_exit(tps65090_exit); |
| |
| MODULE_DESCRIPTION("TPS65090 core driver"); |
| MODULE_AUTHOR("Venu Byravarasu <vbyravarasu@nvidia.com>"); |
| MODULE_LICENSE("GPL v2"); |