xref: /openbmc/linux/drivers/mfd/88pm80x.c (revision 0c5dc500)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
270c6cce0SQiao Zhou /*
370c6cce0SQiao Zhou  * I2C driver for Marvell 88PM80x
470c6cce0SQiao Zhou  *
570c6cce0SQiao Zhou  * Copyright (C) 2012 Marvell International Ltd.
670c6cce0SQiao Zhou  * Haojian Zhuang <haojian.zhuang@marvell.com>
770c6cce0SQiao Zhou  * Joseph(Yossi) Hanin <yhanin@marvell.com>
870c6cce0SQiao Zhou  * Qiao Zhou <zhouqiao@marvell.com>
970c6cce0SQiao Zhou  */
1070c6cce0SQiao Zhou #include <linux/kernel.h>
1170c6cce0SQiao Zhou #include <linux/module.h>
1270c6cce0SQiao Zhou #include <linux/i2c.h>
1370c6cce0SQiao Zhou #include <linux/mfd/88pm80x.h>
1470c6cce0SQiao Zhou #include <linux/slab.h>
1570c6cce0SQiao Zhou #include <linux/uaccess.h>
1670c6cce0SQiao Zhou #include <linux/err.h>
1770c6cce0SQiao Zhou 
1803dcc544SChao Xie /* 88pm80x chips have same definition for chip id register. */
1903dcc544SChao Xie #define PM80X_CHIP_ID			(0x00)
2003dcc544SChao Xie #define PM80X_CHIP_ID_NUM(x)		(((x) >> 5) & 0x7)
2103dcc544SChao Xie #define PM80X_CHIP_ID_REVISION(x)	((x) & 0x1F)
2203dcc544SChao Xie 
2303dcc544SChao Xie struct pm80x_chip_mapping {
2403dcc544SChao Xie 	unsigned int	id;
2503dcc544SChao Xie 	int		type;
2603dcc544SChao Xie };
2703dcc544SChao Xie 
2803dcc544SChao Xie static struct pm80x_chip_mapping chip_mapping[] = {
2903dcc544SChao Xie 	/* 88PM800 chip id number */
3003dcc544SChao Xie 	{0x3,	CHIP_PM800},
3103dcc544SChao Xie 	/* 88PM805 chip id number */
3203dcc544SChao Xie 	{0x0,	CHIP_PM805},
3362a2e633SVaibhav Hiremath 	/* 88PM860 chip id number */
3462a2e633SVaibhav Hiremath 	{0x4,	CHIP_PM860},
3503dcc544SChao Xie };
3603dcc544SChao Xie 
375500e396SQiao Zhou /*
385500e396SQiao Zhou  * workaround: some registers needed by pm805 are defined in pm800, so
395500e396SQiao Zhou  * need to use this global variable to maintain the relation between
405500e396SQiao Zhou  * pm800 and pm805. would remove it after HW chip fixes the issue.
415500e396SQiao Zhou  */
425500e396SQiao Zhou static struct pm80x_chip *g_pm80x_chip;
4370c6cce0SQiao Zhou 
4470c6cce0SQiao Zhou const struct regmap_config pm80x_regmap_config = {
4570c6cce0SQiao Zhou 	.reg_bits = 8,
4670c6cce0SQiao Zhou 	.val_bits = 8,
4770c6cce0SQiao Zhou };
4878a73e59SAxel Lin EXPORT_SYMBOL_GPL(pm80x_regmap_config);
4970c6cce0SQiao Zhou 
5003dcc544SChao Xie 
pm80x_init(struct i2c_client * client)5103dcc544SChao Xie int pm80x_init(struct i2c_client *client)
5270c6cce0SQiao Zhou {
5370c6cce0SQiao Zhou 	struct pm80x_chip *chip;
5470c6cce0SQiao Zhou 	struct regmap *map;
5503dcc544SChao Xie 	unsigned int val;
5603dcc544SChao Xie 	int i, ret = 0;
5770c6cce0SQiao Zhou 
5870c6cce0SQiao Zhou 	chip =
5970c6cce0SQiao Zhou 	    devm_kzalloc(&client->dev, sizeof(struct pm80x_chip), GFP_KERNEL);
6070c6cce0SQiao Zhou 	if (!chip)
6170c6cce0SQiao Zhou 		return -ENOMEM;
6270c6cce0SQiao Zhou 
6370c6cce0SQiao Zhou 	map = devm_regmap_init_i2c(client, &pm80x_regmap_config);
6470c6cce0SQiao Zhou 	if (IS_ERR(map)) {
6570c6cce0SQiao Zhou 		ret = PTR_ERR(map);
6670c6cce0SQiao Zhou 		dev_err(&client->dev, "Failed to allocate register map: %d\n",
6770c6cce0SQiao Zhou 			ret);
68306df798SYi Zhang 		return ret;
6970c6cce0SQiao Zhou 	}
7070c6cce0SQiao Zhou 
7170c6cce0SQiao Zhou 	chip->client = client;
7270c6cce0SQiao Zhou 	chip->regmap = map;
7370c6cce0SQiao Zhou 
7470c6cce0SQiao Zhou 	chip->irq = client->irq;
7570c6cce0SQiao Zhou 
7670c6cce0SQiao Zhou 	chip->dev = &client->dev;
7770c6cce0SQiao Zhou 	i2c_set_clientdata(chip->client, chip);
7870c6cce0SQiao Zhou 
7903dcc544SChao Xie 	ret = regmap_read(chip->regmap, PM80X_CHIP_ID, &val);
8003dcc544SChao Xie 	if (ret < 0) {
8103dcc544SChao Xie 		dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
8203dcc544SChao Xie 		return ret;
8303dcc544SChao Xie 	}
8403dcc544SChao Xie 
8503dcc544SChao Xie 	for (i = 0; i < ARRAY_SIZE(chip_mapping); i++) {
8603dcc544SChao Xie 		if (chip_mapping[i].id == PM80X_CHIP_ID_NUM(val)) {
8703dcc544SChao Xie 			chip->type = chip_mapping[i].type;
8803dcc544SChao Xie 			break;
8903dcc544SChao Xie 		}
9003dcc544SChao Xie 	}
9103dcc544SChao Xie 
9203dcc544SChao Xie 	if (i == ARRAY_SIZE(chip_mapping)) {
9303dcc544SChao Xie 		dev_err(chip->dev,
9403dcc544SChao Xie 			"Failed to detect Marvell 88PM800:ChipID[0x%x]\n", val);
9503dcc544SChao Xie 		return -EINVAL;
9603dcc544SChao Xie 	}
9703dcc544SChao Xie 
9870c6cce0SQiao Zhou 	device_init_wakeup(&client->dev, 1);
9970c6cce0SQiao Zhou 
1005500e396SQiao Zhou 	/*
1015500e396SQiao Zhou 	 * workaround: set g_pm80x_chip to the first probed chip. if the
1025500e396SQiao Zhou 	 * second chip is probed, just point to the companion to each
1035500e396SQiao Zhou 	 * other so that pm805 can access those specific register. would
1045500e396SQiao Zhou 	 * remove it after HW chip fixes the issue.
1055500e396SQiao Zhou 	 */
1065500e396SQiao Zhou 	if (!g_pm80x_chip)
1075500e396SQiao Zhou 		g_pm80x_chip = chip;
1085500e396SQiao Zhou 	else {
1095500e396SQiao Zhou 		chip->companion = g_pm80x_chip->client;
1105500e396SQiao Zhou 		g_pm80x_chip->companion = chip->client;
1115500e396SQiao Zhou 	}
1125500e396SQiao Zhou 
11370c6cce0SQiao Zhou 	return 0;
11470c6cce0SQiao Zhou }
11570c6cce0SQiao Zhou EXPORT_SYMBOL_GPL(pm80x_init);
11670c6cce0SQiao Zhou 
pm80x_deinit(void)117306df798SYi Zhang int pm80x_deinit(void)
11870c6cce0SQiao Zhou {
1195500e396SQiao Zhou 	/*
1205500e396SQiao Zhou 	 * workaround: clear the dependency between pm800 and pm805.
1215500e396SQiao Zhou 	 * would remove it after HW chip fixes the issue.
1225500e396SQiao Zhou 	 */
1235500e396SQiao Zhou 	if (g_pm80x_chip->companion)
1245500e396SQiao Zhou 		g_pm80x_chip->companion = NULL;
1255500e396SQiao Zhou 	else
1265500e396SQiao Zhou 		g_pm80x_chip = NULL;
12770c6cce0SQiao Zhou 	return 0;
12870c6cce0SQiao Zhou }
12970c6cce0SQiao Zhou EXPORT_SYMBOL_GPL(pm80x_deinit);
13070c6cce0SQiao Zhou 
pm80x_suspend(struct device * dev)13170c6cce0SQiao Zhou static int pm80x_suspend(struct device *dev)
13270c6cce0SQiao Zhou {
1331b5420e1SGeliang Tang 	struct i2c_client *client = to_i2c_client(dev);
13470c6cce0SQiao Zhou 	struct pm80x_chip *chip = i2c_get_clientdata(client);
13570c6cce0SQiao Zhou 
13670c6cce0SQiao Zhou 	if (chip && chip->wu_flag)
13770c6cce0SQiao Zhou 		if (device_may_wakeup(chip->dev))
13870c6cce0SQiao Zhou 			enable_irq_wake(chip->irq);
13970c6cce0SQiao Zhou 
14070c6cce0SQiao Zhou 	return 0;
14170c6cce0SQiao Zhou }
14270c6cce0SQiao Zhou 
pm80x_resume(struct device * dev)14370c6cce0SQiao Zhou static int pm80x_resume(struct device *dev)
14470c6cce0SQiao Zhou {
1451b5420e1SGeliang Tang 	struct i2c_client *client = to_i2c_client(dev);
14670c6cce0SQiao Zhou 	struct pm80x_chip *chip = i2c_get_clientdata(client);
14770c6cce0SQiao Zhou 
14870c6cce0SQiao Zhou 	if (chip && chip->wu_flag)
14970c6cce0SQiao Zhou 		if (device_may_wakeup(chip->dev))
15070c6cce0SQiao Zhou 			disable_irq_wake(chip->irq);
15170c6cce0SQiao Zhou 
15270c6cce0SQiao Zhou 	return 0;
15370c6cce0SQiao Zhou }
15470c6cce0SQiao Zhou 
155*19755a0aSPaul Cercueil EXPORT_GPL_SIMPLE_DEV_PM_OPS(pm80x_pm_ops, pm80x_suspend, pm80x_resume);
15670c6cce0SQiao Zhou 
15770c6cce0SQiao Zhou MODULE_DESCRIPTION("I2C Driver for Marvell 88PM80x");
15870c6cce0SQiao Zhou MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
15970c6cce0SQiao Zhou MODULE_LICENSE("GPL");
160