| // 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/mod_devicetable.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 |
| * - CHG_INT |
| * - CHG_INT2 |
| */ |
| 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_CHARGER_INT_1, |
| MAX77759_CHARGER_INT_2, |
| }; |
| |
| 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_CHARGER_INT_1, 0, GENMASK(7, 0)), |
| REGMAP_IRQ_REG(MAX77759_CHARGER_INT_2, 1, GENMASK(7, 0)), |
| }; |
| |
| 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_chrg_irq_chip = { |
| .name = "max77759-chgr", |
| .domain_suffix = "CHGR", |
| .status_base = MAX77759_CHGR_REG_CHG_INT, |
| .mask_base = MAX77759_CHGR_REG_CHG_INT_MASK, |
| .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_CHARGER_INT_1, "INT1"), |
| DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_2, "INT2"), |
| }; |
| |
| 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_chrg_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 irq_data *irq_data; |
| struct max77759 *max77759; |
| unsigned long irq_flags; |
| 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; |
| } |
| |
| irq_data = irq_get_irq_data(client->irq); |
| if (!irq_data) |
| return dev_err_probe(&client->dev, -EINVAL, |
| "invalid IRQ: %d\n", client->irq); |
| |
| irq_flags = IRQF_ONESHOT | IRQF_SHARED; |
| irq_flags |= irqd_get_trigger_type(irq_data); |
| |
| ret = devm_regmap_add_irq_chip(&client->dev, max77759->regmap_top, |
| client->irq, irq_flags, 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"); |