mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
691 lines
20 KiB
C
691 lines
20 KiB
C
![]() |
// 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");
|