// SPDX-License-Identifier: GPL-2.0
//
// Copyright (C) 2018 BayLibre SAS
// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
//
// Core MFD driver for MAXIM 77650/77651 charger/power-supply.
// Programming manual: https://pdfserv.maximintegrated.com/en/an/AN6428.pdf

#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/mfd/core.h>
#include <linux/mfd/max77650.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>

#define MAX77650_INT_GPI_F_MSK		BIT(0)
#define MAX77650_INT_GPI_R_MSK		BIT(1)
#define MAX77650_INT_GPI_MSK \
			(MAX77650_INT_GPI_F_MSK | MAX77650_INT_GPI_R_MSK)
#define MAX77650_INT_nEN_F_MSK		BIT(2)
#define MAX77650_INT_nEN_R_MSK		BIT(3)
#define MAX77650_INT_TJAL1_R_MSK	BIT(4)
#define MAX77650_INT_TJAL2_R_MSK	BIT(5)
#define MAX77650_INT_DOD_R_MSK		BIT(6)

#define MAX77650_INT_THM_MSK		BIT(0)
#define MAX77650_INT_CHG_MSK		BIT(1)
#define MAX77650_INT_CHGIN_MSK		BIT(2)
#define MAX77650_INT_TJ_REG_MSK		BIT(3)
#define MAX77650_INT_CHGIN_CTRL_MSK	BIT(4)
#define MAX77650_INT_SYS_CTRL_MSK	BIT(5)
#define MAX77650_INT_SYS_CNFG_MSK	BIT(6)

#define MAX77650_INT_GLBL_OFFSET	0
#define MAX77650_INT_CHG_OFFSET		1

#define MAX77650_SBIA_LPM_MASK		BIT(5)
#define MAX77650_SBIA_LPM_DISABLED	0x00

enum {
	MAX77650_INT_GPI,
	MAX77650_INT_nEN_F,
	MAX77650_INT_nEN_R,
	MAX77650_INT_TJAL1_R,
	MAX77650_INT_TJAL2_R,
	MAX77650_INT_DOD_R,
	MAX77650_INT_THM,
	MAX77650_INT_CHG,
	MAX77650_INT_CHGIN,
	MAX77650_INT_TJ_REG,
	MAX77650_INT_CHGIN_CTRL,
	MAX77650_INT_SYS_CTRL,
	MAX77650_INT_SYS_CNFG,
};

static const struct resource max77650_charger_resources[] = {
	DEFINE_RES_IRQ_NAMED(MAX77650_INT_CHG, "CHG"),
	DEFINE_RES_IRQ_NAMED(MAX77650_INT_CHGIN, "CHGIN"),
};

static const struct resource max77650_gpio_resources[] = {
	DEFINE_RES_IRQ_NAMED(MAX77650_INT_GPI, "GPI"),
};

static const struct resource max77650_onkey_resources[] = {
	DEFINE_RES_IRQ_NAMED(MAX77650_INT_nEN_F, "nEN_F"),
	DEFINE_RES_IRQ_NAMED(MAX77650_INT_nEN_R, "nEN_R"),
};

static const struct mfd_cell max77650_cells[] = {
	{
		.name		= "max77650-regulator",
		.of_compatible	= "maxim,max77650-regulator",
	}, {
		.name		= "max77650-charger",
		.of_compatible	= "maxim,max77650-charger",
		.resources	= max77650_charger_resources,
		.num_resources	= ARRAY_SIZE(max77650_charger_resources),
	}, {
		.name		= "max77650-gpio",
		.of_compatible	= "maxim,max77650-gpio",
		.resources	= max77650_gpio_resources,
		.num_resources	= ARRAY_SIZE(max77650_gpio_resources),
	}, {
		.name		= "max77650-led",
		.of_compatible	= "maxim,max77650-led",
	}, {
		.name		= "max77650-onkey",
		.of_compatible	= "maxim,max77650-onkey",
		.resources	= max77650_onkey_resources,
		.num_resources	= ARRAY_SIZE(max77650_onkey_resources),
	},
};

static const struct regmap_irq max77650_irqs[] = {
	[MAX77650_INT_GPI] = {
		.reg_offset = MAX77650_INT_GLBL_OFFSET,
		.mask = MAX77650_INT_GPI_MSK,
		.type = {
			.type_falling_val = MAX77650_INT_GPI_F_MSK,
			.type_rising_val = MAX77650_INT_GPI_R_MSK,
			.types_supported = IRQ_TYPE_EDGE_BOTH,
		},
	},
	REGMAP_IRQ_REG(MAX77650_INT_nEN_F,
		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_nEN_F_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_nEN_R,
		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_nEN_R_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_TJAL1_R,
		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_TJAL1_R_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_TJAL2_R,
		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_TJAL2_R_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_DOD_R,
		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_DOD_R_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_THM,
		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_THM_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_CHG,
		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHG_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_CHGIN,
		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHGIN_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_TJ_REG,
		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_TJ_REG_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_CHGIN_CTRL,
		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHGIN_CTRL_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_SYS_CTRL,
		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_SYS_CTRL_MSK),
	REGMAP_IRQ_REG(MAX77650_INT_SYS_CNFG,
		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_SYS_CNFG_MSK),
};

static const struct regmap_irq_chip max77650_irq_chip = {
	.name			= "max77650-irq",
	.irqs			= max77650_irqs,
	.num_irqs		= ARRAY_SIZE(max77650_irqs),
	.num_regs		= 2,
	.status_base		= MAX77650_REG_INT_GLBL,
	.mask_base		= MAX77650_REG_INTM_GLBL,
	.type_in_mask		= true,
	.type_invert		= true,
	.init_ack_masked	= true,
	.clear_on_unmask	= true,
};

static const struct regmap_config max77650_regmap_config = {
	.name		= "max77650",
	.reg_bits	= 8,
	.val_bits	= 8,
};

static int max77650_i2c_probe(struct i2c_client *i2c)
{
	struct regmap_irq_chip_data *irq_data;
	struct device *dev = &i2c->dev;
	struct irq_domain *domain;
	struct regmap *map;
	unsigned int val;
	int rv, id;

	map = devm_regmap_init_i2c(i2c, &max77650_regmap_config);
	if (IS_ERR(map)) {
		dev_err(dev, "Unable to initialise I2C Regmap\n");
		return PTR_ERR(map);
	}

	rv = regmap_read(map, MAX77650_REG_CID, &val);
	if (rv) {
		dev_err(dev, "Unable to read Chip ID\n");
		return rv;
	}

	id = MAX77650_CID_BITS(val);
	switch (id) {
	case MAX77650_CID_77650A:
	case MAX77650_CID_77650C:
	case MAX77650_CID_77651A:
	case MAX77650_CID_77651B:
		break;
	default:
		dev_err(dev, "Chip not supported - ID: 0x%02x\n", id);
		return -ENODEV;
	}

	/*
	 * This IC has a low-power mode which reduces the quiescent current
	 * consumption to ~5.6uA but is only suitable for systems consuming
	 * less than ~2mA. Since this is not likely the case even on
	 * linux-based wearables - keep the chip in normal power mode.
	 */
	rv = regmap_update_bits(map,
				MAX77650_REG_CNFG_GLBL,
				MAX77650_SBIA_LPM_MASK,
				MAX77650_SBIA_LPM_DISABLED);
	if (rv) {
		dev_err(dev, "Unable to change the power mode\n");
		return rv;
	}

	rv = devm_regmap_add_irq_chip(dev, map, i2c->irq,
				      IRQF_ONESHOT | IRQF_SHARED, 0,
				      &max77650_irq_chip, &irq_data);
	if (rv) {
		dev_err(dev, "Unable to add Regmap IRQ chip\n");
		return rv;
	}

	domain = regmap_irq_get_domain(irq_data);

	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
				    max77650_cells, ARRAY_SIZE(max77650_cells),
				    NULL, 0, domain);
}

static const struct of_device_id max77650_of_match[] = {
	{ .compatible = "maxim,max77650" },
	{ }
};
MODULE_DEVICE_TABLE(of, max77650_of_match);

static struct i2c_driver max77650_i2c_driver = {
	.driver = {
		.name = "max77650",
		.of_match_table = max77650_of_match,
	},
	.probe_new = max77650_i2c_probe,
};
module_i2c_driver(max77650_i2c_driver);

MODULE_DESCRIPTION("MAXIM 77650/77651 multi-function core driver");
MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
MODULE_LICENSE("GPL v2");
