| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright 2020 Google Inc | |
| * Copyright 2025 Linaro Ltd. | |
| * | |
| * Core driver for Maxim MAX77759 companion PMIC for USB Type-C | |
| */ | |
| #include <linux/array_size.h> | |
| #include <linux/bitfield.h> | |
| #include <linux/bits.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/completion.h> | |
| #include <linux/dev_printk.h> | |
| #include <linux/device.h> | |
| #include <linux/err.h> | |
| #include <linux/i2c.h> | |
| #include <linux/init.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/irq.h> | |
| #include <linux/jiffies.h> | |
| #include <linux/mfd/core.h> | |
| #include <linux/mfd/max77759.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/of.h> | |
| #include <linux/overflow.h> | |
| #include <linux/regmap.h> | |
| /* Chip ID as per MAX77759_PMIC_REG_PMIC_ID */ | |
| enum { | |
| MAX77759_CHIP_ID = 59, | |
| }; | |
| enum max77759_i2c_subdev_id { | |
| /* | |
| * These are arbitrary and simply used to match struct | |
| * max77759_i2c_subdev entries to the regmap pointers in struct | |
| * max77759 during probe(). | |
| */ | |
| MAX77759_I2C_SUBDEV_ID_MAXQ, | |
| MAX77759_I2C_SUBDEV_ID_CHARGER, | |
| }; | |
| struct max77759_i2c_subdev { | |
| enum max77759_i2c_subdev_id id; | |
| const struct regmap_config *cfg; | |
| u16 i2c_address; | |
| }; | |
| static const struct regmap_range max77759_top_registers[] = { | |
| regmap_reg_range(0x00, 0x02), /* PMIC_ID / PMIC_REVISION / OTP_REVISION */ | |
| regmap_reg_range(0x22, 0x24), /* INTSRC / INTSRCMASK / TOPSYS_INT */ | |
| regmap_reg_range(0x26, 0x26), /* TOPSYS_INT_MASK */ | |
| regmap_reg_range(0x40, 0x40), /* I2C_CNFG */ | |
| regmap_reg_range(0x50, 0x51), /* SWRESET / CONTROL_FG */ | |
| }; | |
| static const struct regmap_range max77759_top_ro_registers[] = { | |
| regmap_reg_range(0x00, 0x02), | |
| regmap_reg_range(0x22, 0x22), | |
| }; | |
| static const struct regmap_range max77759_top_volatile_registers[] = { | |
| regmap_reg_range(0x22, 0x22), | |
| regmap_reg_range(0x24, 0x24), | |
| }; | |
| static const struct regmap_access_table max77759_top_wr_table = { | |
| .yes_ranges = max77759_top_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_top_registers), | |
| .no_ranges = max77759_top_ro_registers, | |
| .n_no_ranges = ARRAY_SIZE(max77759_top_ro_registers), | |
| }; | |
| static const struct regmap_access_table max77759_top_rd_table = { | |
| .yes_ranges = max77759_top_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_top_registers), | |
| }; | |
| static const struct regmap_access_table max77759_top_volatile_table = { | |
| .yes_ranges = max77759_top_volatile_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_top_volatile_registers), | |
| }; | |
| static const struct regmap_config max77759_regmap_config_top = { | |
| .name = "top", | |
| .reg_bits = 8, | |
| .val_bits = 8, | |
| .max_register = MAX77759_PMIC_REG_CONTROL_FG, | |
| .wr_table = &max77759_top_wr_table, | |
| .rd_table = &max77759_top_rd_table, | |
| .volatile_table = &max77759_top_volatile_table, | |
| .num_reg_defaults_raw = MAX77759_PMIC_REG_CONTROL_FG + 1, | |
| .cache_type = REGCACHE_FLAT, | |
| }; | |
| static const struct regmap_range max77759_maxq_registers[] = { | |
| regmap_reg_range(0x60, 0x73), /* Device ID, Rev, INTx, STATUSx, MASKx */ | |
| regmap_reg_range(0x81, 0xa1), /* AP_DATAOUTx */ | |
| regmap_reg_range(0xb1, 0xd1), /* AP_DATAINx */ | |
| regmap_reg_range(0xe0, 0xe0), /* UIC_SWRST */ | |
| }; | |
| static const struct regmap_range max77759_maxq_ro_registers[] = { | |
| regmap_reg_range(0x60, 0x63), /* Device ID, Rev */ | |
| regmap_reg_range(0x68, 0x6f), /* STATUSx */ | |
| regmap_reg_range(0xb1, 0xd1), | |
| }; | |
| static const struct regmap_range max77759_maxq_volatile_registers[] = { | |
| regmap_reg_range(0x64, 0x6f), /* INTx, STATUSx */ | |
| regmap_reg_range(0xb1, 0xd1), | |
| regmap_reg_range(0xe0, 0xe0), | |
| }; | |
| static const struct regmap_access_table max77759_maxq_wr_table = { | |
| .yes_ranges = max77759_maxq_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers), | |
| .no_ranges = max77759_maxq_ro_registers, | |
| .n_no_ranges = ARRAY_SIZE(max77759_maxq_ro_registers), | |
| }; | |
| static const struct regmap_access_table max77759_maxq_rd_table = { | |
| .yes_ranges = max77759_maxq_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers), | |
| }; | |
| static const struct regmap_access_table max77759_maxq_volatile_table = { | |
| .yes_ranges = max77759_maxq_volatile_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_maxq_volatile_registers), | |
| }; | |
| static const struct regmap_config max77759_regmap_config_maxq = { | |
| .name = "maxq", | |
| .reg_bits = 8, | |
| .val_bits = 8, | |
| .max_register = MAX77759_MAXQ_REG_UIC_SWRST, | |
| .wr_table = &max77759_maxq_wr_table, | |
| .rd_table = &max77759_maxq_rd_table, | |
| .volatile_table = &max77759_maxq_volatile_table, | |
| .num_reg_defaults_raw = MAX77759_MAXQ_REG_UIC_SWRST + 1, | |
| .cache_type = REGCACHE_FLAT, | |
| }; | |
| static const struct regmap_range max77759_charger_registers[] = { | |
| regmap_reg_range(0xb0, 0xcc), | |
| }; | |
| static const struct regmap_range max77759_charger_ro_registers[] = { | |
| regmap_reg_range(0xb4, 0xb8), /* INT_OK, DETAILS_0x */ | |
| }; | |
| static const struct regmap_range max77759_charger_volatile_registers[] = { | |
| regmap_reg_range(0xb0, 0xb1), /* INTx */ | |
| regmap_reg_range(0xb4, 0xb8), | |
| }; | |
| static const struct regmap_access_table max77759_charger_wr_table = { | |
| .yes_ranges = max77759_charger_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_charger_registers), | |
| .no_ranges = max77759_charger_ro_registers, | |
| .n_no_ranges = ARRAY_SIZE(max77759_charger_ro_registers), | |
| }; | |
| static const struct regmap_access_table max77759_charger_rd_table = { | |
| .yes_ranges = max77759_charger_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_charger_registers), | |
| }; | |
| static const struct regmap_access_table max77759_charger_volatile_table = { | |
| .yes_ranges = max77759_charger_volatile_registers, | |
| .n_yes_ranges = ARRAY_SIZE(max77759_charger_volatile_registers), | |
| }; | |
| static const struct regmap_config max77759_regmap_config_charger = { | |
| .name = "charger", | |
| .reg_bits = 8, | |
| .val_bits = 8, | |
| .max_register = MAX77759_CHGR_REG_CHG_CNFG_19, | |
| .wr_table = &max77759_charger_wr_table, | |
| .rd_table = &max77759_charger_rd_table, | |
| .volatile_table = &max77759_charger_volatile_table, | |
| .num_reg_defaults_raw = MAX77759_CHGR_REG_CHG_CNFG_19 + 1, | |
| .cache_type = REGCACHE_FLAT, | |
| }; | |
| /* | |
| * Interrupts - with the following interrupt hierarchy: | |
| * pmic IRQs (INTSRC) | |
| * - MAXQ_INT: MaxQ IRQs | |
| * - UIC_INT1 | |
| * - APCmdResI | |
| * - SysMsgI | |
| * - GPIOxI | |
| * - TOPSYS_INT: topsys | |
| * - TOPSYS_INT | |
| * - TSHDN_INT | |
| * - SYSOVLO_INT | |
| * - SYSUVLO_INT | |
| * - FSHIP_NOT_RD | |
| * - CHGR_INT: charger | |
| * - INT1 | |
| * - AICL | |
| * - CHGIN | |
| * - WCIN | |
| * - CHG | |
| * - BAT | |
| * - INLIM | |
| * - THM2 | |
| * - BYP | |
| * - INT2 | |
| * - INSEL | |
| * - SYS_UVLO1 | |
| * - SYS_UVLO2 | |
| * - BAT_OILO | |
| * - CHG_STA_CC | |
| * - CHG_STA_CV | |
| * - CHG_STA_TO | |
| * - CHG_STA_DONE | |
| */ | |
| enum { | |
| MAX77759_INT_MAXQ, | |
| MAX77759_INT_TOPSYS, | |
| MAX77759_INT_CHGR, | |
| }; | |
| enum { | |
| MAX77759_TOPSYS_INT_TSHDN, | |
| MAX77759_TOPSYS_INT_SYSOVLO, | |
| MAX77759_TOPSYS_INT_SYSUVLO, | |
| MAX77759_TOPSYS_INT_FSHIP_NOT_RD, | |
| }; | |
| enum { | |
| MAX77759_MAXQ_INT_APCMDRESI, | |
| MAX77759_MAXQ_INT_SYSMSGI, | |
| MAX77759_MAXQ_INT_GPIO, | |
| MAX77759_MAXQ_INT_UIC1, | |
| MAX77759_MAXQ_INT_UIC2, | |
| MAX77759_MAXQ_INT_UIC3, | |
| MAX77759_MAXQ_INT_UIC4, | |
| }; | |
| enum { | |
| MAX77759_CHGR_INT1_AICL, | |
| MAX77759_CHGR_INT1_CHGIN, | |
| MAX77759_CHGR_INT1_WCIN, | |
| MAX77759_CHGR_INT1_CHG, | |
| MAX77759_CHGR_INT1_BAT, | |
| MAX77759_CHGR_INT1_INLIM, | |
| MAX77759_CHGR_INT1_THM2, | |
| MAX77759_CHGR_INT1_BYP, | |
| MAX77759_CHGR_INT2_INSEL, | |
| MAX77759_CHGR_INT2_SYS_UVLO1, | |
| MAX77759_CHGR_INT2_SYS_UVLO2, | |
| MAX77759_CHGR_INT2_BAT_OILO, | |
| MAX77759_CHGR_INT2_CHG_STA_CC, | |
| MAX77759_CHGR_INT2_CHG_STA_CV, | |
| MAX77759_CHGR_INT2_CHG_STA_TO, | |
| MAX77759_CHGR_INT2_CHG_STA_DONE, | |
| }; | |
| static const struct regmap_irq max77759_pmic_irqs[] = { | |
| REGMAP_IRQ_REG(MAX77759_INT_MAXQ, 0, MAX77759_PMIC_REG_INTSRC_MAXQ), | |
| REGMAP_IRQ_REG(MAX77759_INT_TOPSYS, 0, MAX77759_PMIC_REG_INTSRC_TOPSYS), | |
| REGMAP_IRQ_REG(MAX77759_INT_CHGR, 0, MAX77759_PMIC_REG_INTSRC_CHGR), | |
| }; | |
| static const struct regmap_irq max77759_maxq_irqs[] = { | |
| REGMAP_IRQ_REG(MAX77759_MAXQ_INT_APCMDRESI, 0, MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI), | |
| REGMAP_IRQ_REG(MAX77759_MAXQ_INT_SYSMSGI, 0, MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI), | |
| REGMAP_IRQ_REG(MAX77759_MAXQ_INT_GPIO, 0, GENMASK(1, 0)), | |
| REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC1, 0, GENMASK(5, 2)), | |
| REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC2, 1, GENMASK(7, 0)), | |
| REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC3, 2, GENMASK(7, 0)), | |
| REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC4, 3, GENMASK(7, 0)), | |
| }; | |
| static const struct regmap_irq max77759_topsys_irqs[] = { | |
| REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_TSHDN, 0, MAX77759_PMIC_REG_TOPSYS_INT_TSHDN), | |
| REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSOVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO), | |
| REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSUVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO), | |
| REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_FSHIP_NOT_RD, 0, MAX77759_PMIC_REG_TOPSYS_INT_FSHIP), | |
| }; | |
| static const struct regmap_irq max77759_chgr_irqs[] = { | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT1_AICL, 0, MAX77759_CHGR_REG_CHG_INT_AICL), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT1_CHGIN, 0, MAX77759_CHGR_REG_CHG_INT_CHGIN), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT1_WCIN, 0, MAX77759_CHGR_REG_CHG_INT_WCIN), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT1_CHG, 0, MAX77759_CHGR_REG_CHG_INT_CHG), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT1_BAT, 0, MAX77759_CHGR_REG_CHG_INT_BAT), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT1_INLIM, 0, MAX77759_CHGR_REG_CHG_INT_INLIM), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT1_THM2, 0, MAX77759_CHGR_REG_CHG_INT_THM2), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT1_BYP, 0, MAX77759_CHGR_REG_CHG_INT_BYP), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT2_INSEL, 1, MAX77759_CHGR_REG_CHG_INT2_INSEL), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT2_SYS_UVLO1, 1, MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO1), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT2_SYS_UVLO2, 1, MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO2), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT2_BAT_OILO, 1, MAX77759_CHGR_REG_CHG_INT2_BAT_OILO), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_CC, 1, MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CC), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_CV, 1, MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CV), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_TO, 1, MAX77759_CHGR_REG_CHG_INT2_CHG_STA_TO), | |
| REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_DONE, 1, MAX77759_CHGR_REG_CHG_INT2_CHG_STA_DONE), | |
| }; | |
| static const struct regmap_irq_chip max77759_pmic_irq_chip = { | |
| .name = "max77759-pmic", | |
| /* INTSRC is read-only and doesn't require clearing */ | |
| .status_base = MAX77759_PMIC_REG_INTSRC, | |
| .mask_base = MAX77759_PMIC_REG_INTSRCMASK, | |
| .num_regs = 1, | |
| .irqs = max77759_pmic_irqs, | |
| .num_irqs = ARRAY_SIZE(max77759_pmic_irqs), | |
| }; | |
| /* | |
| * We can let regmap-irq auto-ack the topsys interrupt bits as required, but | |
| * for all others the individual drivers need to know which interrupt bit | |
| * exactly is set inside their interrupt handlers, and therefore we can not set | |
| * .ack_base for those. | |
| */ | |
| static const struct regmap_irq_chip max77759_maxq_irq_chip = { | |
| .name = "max77759-maxq", | |
| .domain_suffix = "MAXQ", | |
| .status_base = MAX77759_MAXQ_REG_UIC_INT1, | |
| .mask_base = MAX77759_MAXQ_REG_UIC_INT1_M, | |
| .num_regs = 4, | |
| .irqs = max77759_maxq_irqs, | |
| .num_irqs = ARRAY_SIZE(max77759_maxq_irqs), | |
| }; | |
| static const struct regmap_irq_chip max77759_topsys_irq_chip = { | |
| .name = "max77759-topsys", | |
| .domain_suffix = "TOPSYS", | |
| .status_base = MAX77759_PMIC_REG_TOPSYS_INT, | |
| .mask_base = MAX77759_PMIC_REG_TOPSYS_INT_MASK, | |
| .ack_base = MAX77759_PMIC_REG_TOPSYS_INT, | |
| .num_regs = 1, | |
| .irqs = max77759_topsys_irqs, | |
| .num_irqs = ARRAY_SIZE(max77759_topsys_irqs), | |
| }; | |
| static const struct regmap_irq_chip max77759_chgr_irq_chip = { | |
| .name = "max77759-chgr", | |
| .domain_suffix = "CHGR", | |
| .status_base = MAX77759_CHGR_REG_CHG_INT, | |
| .mask_base = MAX77759_CHGR_REG_CHG_INT_MASK, | |
| .ack_base = MAX77759_CHGR_REG_CHG_INT, | |
| .num_regs = 2, | |
| .irqs = max77759_chgr_irqs, | |
| .num_irqs = ARRAY_SIZE(max77759_chgr_irqs), | |
| }; | |
| static const struct max77759_i2c_subdev max77759_i2c_subdevs[] = { | |
| { | |
| .id = MAX77759_I2C_SUBDEV_ID_MAXQ, | |
| .cfg = &max77759_regmap_config_maxq, | |
| /* I2C address is same as for sub-block 'top' */ | |
| }, | |
| { | |
| .id = MAX77759_I2C_SUBDEV_ID_CHARGER, | |
| .cfg = &max77759_regmap_config_charger, | |
| .i2c_address = 0x69, | |
| }, | |
| }; | |
| static const struct resource max77759_gpio_resources[] = { | |
| DEFINE_RES_IRQ_NAMED(MAX77759_MAXQ_INT_GPIO, "GPI"), | |
| }; | |
| static const struct resource max77759_charger_resources[] = { | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_AICL, "AICL"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_CHGIN, "CHGIN"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_WCIN, "WCIN"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_CHG, "CHG"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_BAT, "BAT"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_INLIM, "INLIM"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_THM2, "THM2"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_BYP, "BYP"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_INSEL, "INSEL"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_SYS_UVLO1, "SYS_UVLO1"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_SYS_UVLO2, "SYS_UVLO2"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_BAT_OILO, "BAT_OILO"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_CC, "CHG_STA_CC"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_CV, "CHG_STA_CV"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_TO, "CHG_STA_TO"), | |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_DONE, "CHG_STA_DONE"), | |
| }; | |
| static const struct mfd_cell max77759_cells[] = { | |
| MFD_CELL_OF("max77759-nvmem", NULL, NULL, 0, 0, | |
| "maxim,max77759-nvmem"), | |
| }; | |
| static const struct mfd_cell max77759_maxq_cells[] = { | |
| MFD_CELL_OF("max77759-gpio", max77759_gpio_resources, NULL, 0, 0, | |
| "maxim,max77759-gpio"), | |
| }; | |
| static const struct mfd_cell max77759_charger_cells[] = { | |
| MFD_CELL_RES("max77759-charger", max77759_charger_resources), | |
| }; | |
| int max77759_maxq_command(struct max77759 *max77759, | |
| const struct max77759_maxq_command *cmd, | |
| struct max77759_maxq_response *rsp) | |
| { | |
| DEFINE_FLEX(struct max77759_maxq_response, _rsp, rsp, length, 1); | |
| struct device *dev = regmap_get_device(max77759->regmap_maxq); | |
| static const unsigned int timeout_ms = 200; | |
| int ret; | |
| if (cmd->length > MAX77759_MAXQ_OPCODE_MAXLENGTH) | |
| return -EINVAL; | |
| /* | |
| * As a convenience for API users when issuing simple commands, rsp is | |
| * allowed to be NULL. In that case we need a temporary here to write | |
| * the response to, as we need to verify that the command was indeed | |
| * completed correctly. | |
| */ | |
| if (!rsp) | |
| rsp = _rsp; | |
| if (!rsp->length || rsp->length > MAX77759_MAXQ_OPCODE_MAXLENGTH) | |
| return -EINVAL; | |
| guard(mutex)(&max77759->maxq_lock); | |
| reinit_completion(&max77759->cmd_done); | |
| /* | |
| * MaxQ latches the message when the DATAOUT32 register is written. If | |
| * cmd->length is shorter we still need to write 0 to it. | |
| */ | |
| ret = regmap_bulk_write(max77759->regmap_maxq, | |
| MAX77759_MAXQ_REG_AP_DATAOUT0, cmd->cmd, | |
| cmd->length); | |
| if (!ret && cmd->length < MAX77759_MAXQ_OPCODE_MAXLENGTH) | |
| ret = regmap_write(max77759->regmap_maxq, | |
| MAX77759_MAXQ_REG_AP_DATAOUT32, 0); | |
| if (ret) { | |
| dev_err(dev, "writing command failed: %d\n", ret); | |
| return ret; | |
| } | |
| /* Wait for response from MaxQ */ | |
| if (!wait_for_completion_timeout(&max77759->cmd_done, | |
| msecs_to_jiffies(timeout_ms))) { | |
| dev_err(dev, "timed out waiting for response\n"); | |
| return -ETIMEDOUT; | |
| } | |
| ret = regmap_bulk_read(max77759->regmap_maxq, | |
| MAX77759_MAXQ_REG_AP_DATAIN0, | |
| rsp->rsp, rsp->length); | |
| if (ret) { | |
| dev_err(dev, "reading response failed: %d\n", ret); | |
| return ret; | |
| } | |
| /* | |
| * As per the protocol, the first byte of the reply will match the | |
| * request. | |
| */ | |
| if (cmd->cmd[0] != rsp->rsp[0]) { | |
| dev_err(dev, "unexpected opcode response for %#.2x: %*ph\n", | |
| cmd->cmd[0], (int)rsp->length, rsp->rsp); | |
| return -EIO; | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(max77759_maxq_command); | |
| static irqreturn_t apcmdres_irq_handler(int irq, void *irq_data) | |
| { | |
| struct max77759 *max77759 = irq_data; | |
| regmap_write(max77759->regmap_maxq, MAX77759_MAXQ_REG_UIC_INT1, | |
| MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI); | |
| complete(&max77759->cmd_done); | |
| return IRQ_HANDLED; | |
| } | |
| static int max77759_create_i2c_subdev(struct i2c_client *client, | |
| struct max77759 *max77759, | |
| const struct max77759_i2c_subdev *sd) | |
| { | |
| struct i2c_client *sub; | |
| struct regmap *regmap; | |
| int ret; | |
| /* | |
| * If 'sd' has an I2C address, 'sub' will be assigned a new 'dummy' | |
| * device, otherwise use it as-is. | |
| */ | |
| sub = client; | |
| if (sd->i2c_address) { | |
| sub = devm_i2c_new_dummy_device(&client->dev, | |
| client->adapter, | |
| sd->i2c_address); | |
| if (IS_ERR(sub)) | |
| return dev_err_probe(&client->dev, PTR_ERR(sub), | |
| "failed to claim I2C device %s\n", | |
| sd->cfg->name); | |
| } | |
| regmap = devm_regmap_init_i2c(sub, sd->cfg); | |
| if (IS_ERR(regmap)) | |
| return dev_err_probe(&sub->dev, PTR_ERR(regmap), | |
| "regmap init for '%s' failed\n", | |
| sd->cfg->name); | |
| ret = regmap_attach_dev(&client->dev, regmap, sd->cfg); | |
| if (ret) | |
| return dev_err_probe(&client->dev, ret, | |
| "regmap attach of '%s' failed\n", | |
| sd->cfg->name); | |
| if (sd->id == MAX77759_I2C_SUBDEV_ID_MAXQ) | |
| max77759->regmap_maxq = regmap; | |
| else if (sd->id == MAX77759_I2C_SUBDEV_ID_CHARGER) | |
| max77759->regmap_charger = regmap; | |
| return 0; | |
| } | |
| static int max77759_add_chained_irq_chip(struct device *dev, | |
| struct regmap *regmap, | |
| int pirq, | |
| struct regmap_irq_chip_data *parent, | |
| const struct regmap_irq_chip *chip, | |
| struct regmap_irq_chip_data **data) | |
| { | |
| int irq, ret; | |
| irq = regmap_irq_get_virq(parent, pirq); | |
| if (irq < 0) | |
| return dev_err_probe(dev, irq, | |
| "failed to get parent vIRQ(%d) for chip %s\n", | |
| pirq, chip->name); | |
| ret = devm_regmap_add_irq_chip(dev, regmap, irq, | |
| IRQF_ONESHOT | IRQF_SHARED, 0, chip, | |
| data); | |
| if (ret) | |
| return dev_err_probe(dev, ret, "failed to add %s IRQ chip\n", | |
| chip->name); | |
| return 0; | |
| } | |
| static int max77759_add_chained_maxq(struct i2c_client *client, | |
| struct max77759 *max77759, | |
| struct regmap_irq_chip_data *parent) | |
| { | |
| struct regmap_irq_chip_data *irq_chip_data; | |
| int apcmdres_irq; | |
| int ret; | |
| ret = max77759_add_chained_irq_chip(&client->dev, | |
| max77759->regmap_maxq, | |
| MAX77759_INT_MAXQ, | |
| parent, | |
| &max77759_maxq_irq_chip, | |
| &irq_chip_data); | |
| if (ret) | |
| return ret; | |
| init_completion(&max77759->cmd_done); | |
| apcmdres_irq = regmap_irq_get_virq(irq_chip_data, | |
| MAX77759_MAXQ_INT_APCMDRESI); | |
| ret = devm_request_threaded_irq(&client->dev, apcmdres_irq, | |
| NULL, apcmdres_irq_handler, | |
| IRQF_ONESHOT | IRQF_SHARED, | |
| dev_name(&client->dev), max77759); | |
| if (ret) | |
| return dev_err_probe(&client->dev, ret, | |
| "MAX77759_MAXQ_INT_APCMDRESI failed\n"); | |
| ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, | |
| max77759_maxq_cells, | |
| ARRAY_SIZE(max77759_maxq_cells), | |
| NULL, 0, | |
| regmap_irq_get_domain(irq_chip_data)); | |
| if (ret) | |
| return dev_err_probe(&client->dev, ret, | |
| "failed to add child devices (MaxQ)\n"); | |
| return 0; | |
| } | |
| static int max77759_add_chained_topsys(struct i2c_client *client, | |
| struct max77759 *max77759, | |
| struct regmap_irq_chip_data *parent) | |
| { | |
| struct regmap_irq_chip_data *irq_chip_data; | |
| int ret; | |
| ret = max77759_add_chained_irq_chip(&client->dev, | |
| max77759->regmap_top, | |
| MAX77759_INT_TOPSYS, | |
| parent, | |
| &max77759_topsys_irq_chip, | |
| &irq_chip_data); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| static int max77759_add_chained_charger(struct i2c_client *client, | |
| struct max77759 *max77759, | |
| struct regmap_irq_chip_data *parent) | |
| { | |
| struct regmap_irq_chip_data *irq_chip_data; | |
| int ret; | |
| ret = max77759_add_chained_irq_chip(&client->dev, | |
| max77759->regmap_charger, | |
| MAX77759_INT_CHGR, | |
| parent, | |
| &max77759_chgr_irq_chip, | |
| &irq_chip_data); | |
| if (ret) | |
| return ret; | |
| ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, | |
| max77759_charger_cells, | |
| ARRAY_SIZE(max77759_charger_cells), | |
| NULL, 0, | |
| regmap_irq_get_domain(irq_chip_data)); | |
| if (ret) | |
| return dev_err_probe(&client->dev, ret, | |
| "failed to add child devices (charger)\n"); | |
| return 0; | |
| } | |
| static int max77759_probe(struct i2c_client *client) | |
| { | |
| struct regmap_irq_chip_data *irq_chip_data_pmic; | |
| struct max77759 *max77759; | |
| unsigned int pmic_id; | |
| int ret; | |
| max77759 = devm_kzalloc(&client->dev, sizeof(*max77759), GFP_KERNEL); | |
| if (!max77759) | |
| return -ENOMEM; | |
| i2c_set_clientdata(client, max77759); | |
| max77759->regmap_top = devm_regmap_init_i2c(client, | |
| &max77759_regmap_config_top); | |
| if (IS_ERR(max77759->regmap_top)) | |
| return dev_err_probe(&client->dev, PTR_ERR(max77759->regmap_top), | |
| "regmap init for '%s' failed\n", | |
| max77759_regmap_config_top.name); | |
| ret = regmap_read(max77759->regmap_top, | |
| MAX77759_PMIC_REG_PMIC_ID, &pmic_id); | |
| if (ret) | |
| return dev_err_probe(&client->dev, ret, | |
| "unable to read device ID\n"); | |
| if (pmic_id != MAX77759_CHIP_ID) | |
| return dev_err_probe(&client->dev, -ENODEV, | |
| "unsupported device ID %#.2x (%d)\n", | |
| pmic_id, pmic_id); | |
| ret = devm_mutex_init(&client->dev, &max77759->maxq_lock); | |
| if (ret) | |
| return ret; | |
| for (int i = 0; i < ARRAY_SIZE(max77759_i2c_subdevs); i++) { | |
| ret = max77759_create_i2c_subdev(client, max77759, | |
| &max77759_i2c_subdevs[i]); | |
| if (ret) | |
| return ret; | |
| } | |
| ret = devm_regmap_add_irq_chip(&client->dev, max77759->regmap_top, | |
| client->irq, IRQF_ONESHOT | IRQF_SHARED, 0, | |
| &max77759_pmic_irq_chip, | |
| &irq_chip_data_pmic); | |
| if (ret) | |
| return dev_err_probe(&client->dev, ret, | |
| "failed to add IRQ chip '%s'\n", | |
| max77759_pmic_irq_chip.name); | |
| ret = max77759_add_chained_maxq(client, max77759, irq_chip_data_pmic); | |
| if (ret) | |
| return ret; | |
| ret = max77759_add_chained_topsys(client, max77759, irq_chip_data_pmic); | |
| if (ret) | |
| return ret; | |
| ret = max77759_add_chained_charger(client, max77759, irq_chip_data_pmic); | |
| if (ret) | |
| return ret; | |
| return devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, | |
| max77759_cells, ARRAY_SIZE(max77759_cells), | |
| NULL, 0, | |
| regmap_irq_get_domain(irq_chip_data_pmic)); | |
| } | |
| static const struct i2c_device_id max77759_i2c_id[] = { | |
| { "max77759" }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(i2c, max77759_i2c_id); | |
| static const struct of_device_id max77759_of_id[] = { | |
| { .compatible = "maxim,max77759", }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(of, max77759_of_id); | |
| static struct i2c_driver max77759_i2c_driver = { | |
| .driver = { | |
| .name = "max77759", | |
| .of_match_table = max77759_of_id, | |
| }, | |
| .probe = max77759_probe, | |
| .id_table = max77759_i2c_id, | |
| }; | |
| module_i2c_driver(max77759_i2c_driver); | |
| MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>"); | |
| MODULE_DESCRIPTION("Maxim MAX77759 core driver"); | |
| MODULE_LICENSE("GPL"); |