/* * max14577.c - mfd core driver for the Maxim 14577 * * Copyright (C) 2013 Samsung Electrnoics * Chanwoo Choi * Krzysztof Kozlowski * * 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. * * This program is distributed in the hope that 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. * * This driver is based on max8997.c */ #include #include #include #include #include static struct mfd_cell max14577_devs[] = { { .name = "max14577-muic", }, { .name = "max14577-regulator", .of_compatible = "maxim,max14577-regulator", }, { .name = "max14577-charger", }, }; static bool max14577_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case MAX14577_REG_INT1 ... MAX14577_REG_STATUS3: return true; default: break; } return false; } static const struct regmap_config max14577_regmap_config = { .reg_bits = 8, .val_bits = 8, .volatile_reg = max14577_volatile_reg, .max_register = MAX14577_REG_END, }; static const struct regmap_irq max14577_irqs[] = { /* INT1 interrupts */ { .reg_offset = 0, .mask = INT1_ADC_MASK, }, { .reg_offset = 0, .mask = INT1_ADCLOW_MASK, }, { .reg_offset = 0, .mask = INT1_ADCERR_MASK, }, /* INT2 interrupts */ { .reg_offset = 1, .mask = INT2_CHGTYP_MASK, }, { .reg_offset = 1, .mask = INT2_CHGDETRUN_MASK, }, { .reg_offset = 1, .mask = INT2_DCDTMR_MASK, }, { .reg_offset = 1, .mask = INT2_DBCHG_MASK, }, { .reg_offset = 1, .mask = INT2_VBVOLT_MASK, }, /* INT3 interrupts */ { .reg_offset = 2, .mask = INT3_EOC_MASK, }, { .reg_offset = 2, .mask = INT3_CGMBC_MASK, }, { .reg_offset = 2, .mask = INT3_OVP_MASK, }, { .reg_offset = 2, .mask = INT3_MBCCHGERR_MASK, }, }; static const struct regmap_irq_chip max14577_irq_chip = { .name = "max14577", .status_base = MAX14577_REG_INT1, .mask_base = MAX14577_REG_INTMASK1, .mask_invert = 1, .num_regs = 3, .irqs = max14577_irqs, .num_irqs = ARRAY_SIZE(max14577_irqs), }; static int max14577_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct max14577 *max14577; struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev); struct device_node *np = i2c->dev.of_node; u8 reg_data; int ret = 0; if (np) { pdata = devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; i2c->dev.platform_data = pdata; } if (!pdata) { dev_err(&i2c->dev, "No platform data found.\n"); return -EINVAL; } max14577 = devm_kzalloc(&i2c->dev, sizeof(*max14577), GFP_KERNEL); if (!max14577) return -ENOMEM; i2c_set_clientdata(i2c, max14577); max14577->dev = &i2c->dev; max14577->i2c = i2c; max14577->irq = i2c->irq; max14577->regmap = devm_regmap_init_i2c(i2c, &max14577_regmap_config); if (IS_ERR(max14577->regmap)) { ret = PTR_ERR(max14577->regmap); dev_err(max14577->dev, "Failed to allocate register map: %d\n", ret); return ret; } ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, ®_data); if (ret) { dev_err(max14577->dev, "Device not found on this channel: %d\n", ret); return ret; } max14577->vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> DEVID_VENDORID_SHIFT); max14577->device_id = ((reg_data & DEVID_DEVICEID_MASK) >> DEVID_DEVICEID_SHIFT); dev_info(max14577->dev, "Device ID: 0x%x, vendor: 0x%x\n", max14577->device_id, max14577->vendor_id); ret = regmap_add_irq_chip(max14577->regmap, max14577->irq, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, &max14577_irq_chip, &max14577->irq_data); if (ret != 0) { dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", max14577->irq, ret); return ret; } ret = mfd_add_devices(max14577->dev, -1, max14577_devs, ARRAY_SIZE(max14577_devs), NULL, 0, regmap_irq_get_domain(max14577->irq_data)); if (ret < 0) goto err_mfd; device_init_wakeup(max14577->dev, 1); return 0; err_mfd: regmap_del_irq_chip(max14577->irq, max14577->irq_data); return ret; } static int max14577_i2c_remove(struct i2c_client *i2c) { struct max14577 *max14577 = i2c_get_clientdata(i2c); mfd_remove_devices(max14577->dev); regmap_del_irq_chip(max14577->irq, max14577->irq_data); return 0; } static const struct i2c_device_id max14577_i2c_id[] = { { "max14577", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, max14577_i2c_id); #ifdef CONFIG_PM_SLEEP static int max14577_suspend(struct device *dev) { struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); struct max14577 *max14577 = i2c_get_clientdata(i2c); if (device_may_wakeup(dev)) { enable_irq_wake(max14577->irq); /* * MUIC IRQ must be disabled during suspend if this is * a wake up source because it will be handled before * resuming I2C. * * When device is woken up from suspend (e.g. by ADC change), * an interrupt occurs before resuming I2C bus controller. * Interrupt handler tries to read registers but this read * will fail because I2C is still suspended. */ disable_irq(max14577->irq); } return 0; } static int max14577_resume(struct device *dev) { struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); struct max14577 *max14577 = i2c_get_clientdata(i2c); if (device_may_wakeup(dev)) { disable_irq_wake(max14577->irq); enable_irq(max14577->irq); } return 0; } #endif /* CONFIG_PM_SLEEP */ static struct of_device_id max14577_dt_match[] = { { .compatible = "maxim,max14577", }, {}, }; static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume); static struct i2c_driver max14577_i2c_driver = { .driver = { .name = "max14577", .owner = THIS_MODULE, .pm = &max14577_pm, .of_match_table = max14577_dt_match, }, .probe = max14577_i2c_probe, .remove = max14577_i2c_remove, .id_table = max14577_i2c_id, }; static int __init max14577_i2c_init(void) { return i2c_add_driver(&max14577_i2c_driver); } subsys_initcall(max14577_i2c_init); static void __exit max14577_i2c_exit(void) { i2c_del_driver(&max14577_i2c_driver); } module_exit(max14577_i2c_exit); MODULE_AUTHOR("Chanwoo Choi , Krzysztof Kozlowski "); MODULE_DESCRIPTION("MAXIM 14577 multi-function core driver"); MODULE_LICENSE("GPL");