/* * Maxim MAX77818 MFD Core * * Copyright (C) 2014 Maxim Integrated Product * * Copyright (C) 2019 reMarkable AS - http://www.remarkable.com/ * * Author: Steinar Bakkemo * Author: Shawn Guo * Author: Lars Ivar Miljeteig * * 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 version 2. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any * kind, whether express or implied; 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 max77843.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define I2C_ADDR_PMIC (0xcc >> 1) /* PMIC (CLOGIC/SAFELDOs) */ #define I2C_ADDR_CHARGER (0xd2 >> 1) /* Charger */ #define I2C_ADDR_FUEL_GAUGE (0x6c >> 1) /* Fuel Gauge */ #define REG_PMICID 0x20 #define REG_PMICREV 0x21 static int max77818_chg_handle_pre_irq(void *irq_drv_data) { struct max77818_dev *max77818_dev = (struct max77818_dev*) irq_drv_data; bool restore_state = 0; int ret = 0; if (!max77818_dev) { printk("%s: No driver data, unable to disable FGCC\n", __func__); return -EINVAL; } ret = MAX77818_START_NON_FGCC_OP( max77818_dev, restore_state, "Disabling FGCC before handling charger interrupt"); if (ret) dev_err(max77818_dev->dev, "Failed to disable FGCC\n"); return ret; } static int max77818_chg_handle_post_irq(void *irq_drv_data) { struct max77818_dev *max77818_dev = (struct max77818_dev*) irq_drv_data; bool restore_state = 1; int ret = 0; if (!max77818_dev) { printk("%s: No driver data, unable to disable FGCC\n", __func__); return -EINVAL; } ret = MAX77818_FINISH_NON_FGCC_OP( max77818_dev, restore_state, "Enabling FGCC after handling charger interrupt"); if (ret) dev_err(max77818_dev->dev, "Failed to enable FGCC\n"); return ret; } static const struct regmap_config max77818_regmap_config = { .reg_bits = 8, .val_bits = 8, .cache_type = REGCACHE_NONE, }; static const struct regmap_config max77818_regmap_config_fg = { .reg_bits = 8, .val_bits = 16, .cache_type = REGCACHE_NONE, .val_format_endian = REGMAP_ENDIAN_NATIVE, }; /* Declare Interrupt */ static const struct regmap_irq max77818_intsrc_irqs[] = { { .reg_offset = 0, .mask = BIT_CHGR_INT, }, { .reg_offset = 0, .mask = BIT_FG_INT, }, { .reg_offset = 0, .mask = BIT_SYS_INT, }, }; static const struct regmap_irq_chip max77818_intsrc_irq_chip = { .name = "max77818 intsrc", .status_base = REG_INTSRC, .mask_base = REG_INTSRCMASK, .num_regs = 1, .irqs = max77818_intsrc_irqs, .num_irqs = ARRAY_SIZE(max77818_intsrc_irqs), }; static const struct regmap_irq max77818_sys_irqs[] = { { .reg_offset = 0, .mask = BIT_SYSUVLO_INT, }, { .reg_offset = 0, .mask = BIT_SYSOVLO_INT, }, { .reg_offset = 0, .mask = BIT_TSHDN_INT, }, { .reg_offset = 0, .mask = BIT_TM_INT, }, }; static const struct regmap_irq_chip max77818_sys_irq_chip = { .name = "max77818 system", .status_base = REG_SYSINTSRC, .mask_base = REG_SYSINTMASK, .num_regs = 1, .irqs = max77818_sys_irqs, .num_irqs = ARRAY_SIZE(max77818_sys_irqs), }; static const struct regmap_irq max77818_chg_irqs[] = { { .reg_offset = 0, .mask = BIT_CHG_BYP_I, }, { .reg_offset = 0, .mask = BIT_CHG_BATP_I, }, { .reg_offset = 0, .mask = BIT_CHG_BAT_I, }, { .reg_offset = 0, .mask = BIT_CHG_CHG_I, }, { .reg_offset = 0, .mask = BIT_CHG_WCIN_I, }, { .reg_offset = 0, .mask = BIT_CHG_CHGIN_I, }, { .reg_offset = 0, .mask = BIT_CHG_AICL_I, }, }; static struct regmap_irq_chip max77818_chg_irq_chip = { .name = "max77818 chg", .status_base = REG_CHARGER_INT, .mask_base = REG_CHARGER_INT_MASK, .num_regs = 1, .irqs = max77818_chg_irqs, .num_irqs = ARRAY_SIZE(max77818_chg_irqs), .handle_pre_irq = max77818_chg_handle_pre_irq, .handle_post_irq = max77818_chg_handle_post_irq, }; static struct mfd_cell max77818_devices[] = { { .name = MAX77818_REGULATOR_NAME, .of_compatible = "maxim,"MAX77818_REGULATOR_NAME, }, { .name = MAX77818_CHARGER_NAME, .of_compatible = "maxim,"MAX77818_CHARGER_NAME, }, { .name = MAX77818_FUELGAUGE_NAME, .of_compatible = "maxim,"MAX77818_FUELGAUGE_NAME, }, }; static int max77818_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct max77818_dev *me; u32 chip_id, chip_rev; int ret; me = devm_kzalloc(&client->dev, sizeof(*me), GFP_KERNEL); if (!me) return -ENOMEM; i2c_set_clientdata(client, me); mutex_init(&me->lock); me->dev = &client->dev; me->irq = client->irq; me->pmic = client; me->regmap_pmic = devm_regmap_init_i2c(client, &max77818_regmap_config); if (IS_ERR(me->regmap_pmic)) { ret = PTR_ERR(me->regmap_pmic); dev_err(me->dev, "failed to initialize PMIC regmap: %d\n", ret); return ret; } ret = regmap_read(me->regmap_pmic, REG_PMICID, &chip_id); if (ret < 0) { dev_err(me->dev, "failed to read chip id: %d\n", ret); return ret; } else { regmap_read(me->regmap_pmic, REG_PMICREV, &chip_rev); dev_info(me->dev, "device ID: 0x%x, REV: 0x%x\n", chip_id, chip_rev); } me->chg = i2c_new_dummy(client->adapter, I2C_ADDR_CHARGER); if (!me->chg) { dev_err(me->dev, "failed to allocate I2C device for CHG\n"); return ret; } i2c_set_clientdata(me->chg, me); me->fg = i2c_new_dummy(client->adapter, I2C_ADDR_FUEL_GAUGE); if (!me->fg) { dev_err(me->dev, "failed to allocate I2C device for FG\n"); goto unreg_chg; } i2c_set_clientdata(me->fg, me); me->regmap_chg = devm_regmap_init_i2c(me->chg, &max77818_regmap_config); if (IS_ERR_OR_NULL(me->regmap_chg)) { ret = PTR_ERR(me->regmap_chg); dev_warn(me->dev, "failed to initialize CHG regmap: %d\n", ret); goto unreg_fg; } me->regmap_fg= devm_regmap_init_i2c(me->fg, &max77818_regmap_config_fg); if (IS_ERR_OR_NULL(me->regmap_fg)) { ret = PTR_ERR(me->regmap_fg); dev_err(me->dev, "failed to initialize FG regmap: %d\n", ret); goto unreg_fg; } /* Disable all interrupt source */ regmap_write(me->regmap_pmic, REG_INTSRCMASK, 0xff); /* Register overall interrupt source (sys, fg, chg) */ ret = regmap_add_irq_chip(me->regmap_pmic, me->irq, IRQF_ONESHOT | IRQF_SHARED, 0, &max77818_intsrc_irq_chip, &me->irqc_intsrc); if (ret) { dev_err(me->dev, "failed to add intsrc irq chip: %d\n", ret); goto unreg_fg; } /* Register system chip irq */ ret = regmap_add_irq_chip(me->regmap_pmic, me->irq, IRQF_ONESHOT | IRQF_SHARED, 0, &max77818_sys_irq_chip, &me->irqc_sys); if (ret) { dev_err(me->dev, "failed to add system irq chip: %d\n", ret); goto del_irqc_intsrc; } /* Register charger chip irq */ max77818_chg_irq_chip.irq_drv_data = me; ret = MAX77818_DO_NON_FGCC_OP( me, regmap_add_irq_chip(me->regmap_chg, me->irq, IRQF_ONESHOT | IRQF_SHARED, 0, &max77818_chg_irq_chip, &me->irqc_chg), "adding charger chip irq\n"); if (ret) { dev_warn(me->dev, "failed to add chg irq chip: %d\n", ret); goto del_irqc_sys; } pm_runtime_set_active(me->dev); ret = mfd_add_devices(me->dev, -1, max77818_devices, ARRAY_SIZE(max77818_devices), NULL, 0, NULL); if (ret < 0) { dev_err(me->dev, "failed to add mfd devices: %d\n", ret); goto del_irqc_chg; } device_init_wakeup(me->dev, true); return 0; del_irqc_chg: regmap_del_irq_chip(me->irq, me->irqc_chg); del_irqc_sys: regmap_del_irq_chip(me->irq, me->irqc_sys); del_irqc_intsrc: regmap_del_irq_chip(me->irq, me->irqc_intsrc); unreg_fg: i2c_unregister_device(me->fg); unreg_chg: i2c_unregister_device(me->chg); return ret; } static int max77818_i2c_remove(struct i2c_client *client) { struct max77818_dev *me = i2c_get_clientdata(client); mfd_remove_devices(me->dev); regmap_del_irq_chip(me->irq, me->irqc_chg); regmap_del_irq_chip(me->irq, me->irqc_sys); regmap_del_irq_chip(me->irq, me->irqc_intsrc); i2c_unregister_device(me->fg); if (me->chg) { i2c_unregister_device(me->chg); } return 0; } static int max77818_suspend(struct device *dev) { struct i2c_client *i2c = to_i2c_client(dev); struct max77818_dev *me = i2c_get_clientdata(i2c); if (device_may_wakeup(dev)) { enable_irq_wake(me->irq); disable_irq(me->irq); } return 0; } static int max77818_resume(struct device *dev) { struct i2c_client *i2c = to_i2c_client(dev); struct max77818_dev *me = i2c_get_clientdata(i2c); if (device_may_wakeup(dev)) { disable_irq_wake(me->irq); enable_irq(me->irq); } return 0; } static SIMPLE_DEV_PM_OPS(max77818_pm, max77818_suspend, max77818_resume); static struct of_device_id max77818_of_id[] = { { .compatible = "maxim,max77818" }, { }, }; MODULE_DEVICE_TABLE(of, max77818_of_id); static const struct i2c_device_id max77818_i2c_id[] = { { "max77818" }, { }, }; MODULE_DEVICE_TABLE(i2c, max77818_i2c_id); static struct i2c_driver max77818_i2c_driver = { .driver = { .name = "max77818", .pm = &max77818_pm, .of_match_table = max77818_of_id, }, .probe = max77818_i2c_probe, .remove = max77818_i2c_remove, .id_table = max77818_i2c_id, }; module_i2c_driver(max77818_i2c_driver); MODULE_DESCRIPTION("MAX77818 MFD Driver"); MODULE_LICENSE("GPL v2");