18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/* NXP PCF50633 Power Management Unit (PMU) driver
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * (C) 2006-2008 by Openmoko, Inc.
58c2ecf20Sopenharmony_ci * Author: Harald Welte <laforge@openmoko.org>
68c2ecf20Sopenharmony_ci * 	   Balaji Rao <balajirrao@openmoko.org>
78c2ecf20Sopenharmony_ci * All rights reserved.
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/device.h>
128c2ecf20Sopenharmony_ci#include <linux/sysfs.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci#include <linux/types.h>
158c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
168c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
178c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
188c2ecf20Sopenharmony_ci#include <linux/i2c.h>
198c2ecf20Sopenharmony_ci#include <linux/pm.h>
208c2ecf20Sopenharmony_ci#include <linux/slab.h>
218c2ecf20Sopenharmony_ci#include <linux/regmap.h>
228c2ecf20Sopenharmony_ci#include <linux/err.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#include <linux/mfd/pcf50633/core.h>
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/* Read a block of up to 32 regs  */
278c2ecf20Sopenharmony_ciint pcf50633_read_block(struct pcf50633 *pcf, u8 reg,
288c2ecf20Sopenharmony_ci					int nr_regs, u8 *data)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	int ret;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	ret = regmap_raw_read(pcf->regmap, reg, data, nr_regs);
338c2ecf20Sopenharmony_ci	if (ret != 0)
348c2ecf20Sopenharmony_ci		return ret;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	return nr_regs;
378c2ecf20Sopenharmony_ci}
388c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcf50633_read_block);
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci/* Write a block of up to 32 regs  */
418c2ecf20Sopenharmony_ciint pcf50633_write_block(struct pcf50633 *pcf , u8 reg,
428c2ecf20Sopenharmony_ci					int nr_regs, u8 *data)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	return regmap_raw_write(pcf->regmap, reg, data, nr_regs);
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcf50633_write_block);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ciu8 pcf50633_reg_read(struct pcf50633 *pcf, u8 reg)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	unsigned int val;
518c2ecf20Sopenharmony_ci	int ret;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	ret = regmap_read(pcf->regmap, reg, &val);
548c2ecf20Sopenharmony_ci	if (ret < 0)
558c2ecf20Sopenharmony_ci		return -1;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	return val;
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcf50633_reg_read);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ciint pcf50633_reg_write(struct pcf50633 *pcf, u8 reg, u8 val)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	return regmap_write(pcf->regmap, reg, val);
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcf50633_reg_write);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ciint pcf50633_reg_set_bit_mask(struct pcf50633 *pcf, u8 reg, u8 mask, u8 val)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	return regmap_update_bits(pcf->regmap, reg, mask, val);
708c2ecf20Sopenharmony_ci}
718c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcf50633_reg_set_bit_mask);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ciint pcf50633_reg_clear_bits(struct pcf50633 *pcf, u8 reg, u8 val)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	return regmap_update_bits(pcf->regmap, reg, val, 0);
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcf50633_reg_clear_bits);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci/* sysfs attributes */
808c2ecf20Sopenharmony_cistatic ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr,
818c2ecf20Sopenharmony_ci			    char *buf)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct pcf50633 *pcf = dev_get_drvdata(dev);
848c2ecf20Sopenharmony_ci	u8 dump[16];
858c2ecf20Sopenharmony_ci	int n, n1, idx = 0;
868c2ecf20Sopenharmony_ci	char *buf1 = buf;
878c2ecf20Sopenharmony_ci	static u8 address_no_read[] = { /* must be ascending */
888c2ecf20Sopenharmony_ci		PCF50633_REG_INT1,
898c2ecf20Sopenharmony_ci		PCF50633_REG_INT2,
908c2ecf20Sopenharmony_ci		PCF50633_REG_INT3,
918c2ecf20Sopenharmony_ci		PCF50633_REG_INT4,
928c2ecf20Sopenharmony_ci		PCF50633_REG_INT5,
938c2ecf20Sopenharmony_ci		0 /* terminator */
948c2ecf20Sopenharmony_ci	};
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	for (n = 0; n < 256; n += sizeof(dump)) {
978c2ecf20Sopenharmony_ci		for (n1 = 0; n1 < sizeof(dump); n1++)
988c2ecf20Sopenharmony_ci			if (n == address_no_read[idx]) {
998c2ecf20Sopenharmony_ci				idx++;
1008c2ecf20Sopenharmony_ci				dump[n1] = 0x00;
1018c2ecf20Sopenharmony_ci			} else
1028c2ecf20Sopenharmony_ci				dump[n1] = pcf50633_reg_read(pcf, n + n1);
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci		buf1 += sprintf(buf1, "%*ph\n", (int)sizeof(dump), dump);
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	return buf1 - buf;
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_cistatic DEVICE_ATTR(dump_regs, 0400, show_dump_regs, NULL);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic ssize_t show_resume_reason(struct device *dev,
1128c2ecf20Sopenharmony_ci				struct device_attribute *attr, char *buf)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct pcf50633 *pcf = dev_get_drvdata(dev);
1158c2ecf20Sopenharmony_ci	int n;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	n = sprintf(buf, "%02x%02x%02x%02x%02x\n",
1188c2ecf20Sopenharmony_ci				pcf->resume_reason[0],
1198c2ecf20Sopenharmony_ci				pcf->resume_reason[1],
1208c2ecf20Sopenharmony_ci				pcf->resume_reason[2],
1218c2ecf20Sopenharmony_ci				pcf->resume_reason[3],
1228c2ecf20Sopenharmony_ci				pcf->resume_reason[4]);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	return n;
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_cistatic DEVICE_ATTR(resume_reason, 0400, show_resume_reason, NULL);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic struct attribute *pcf_sysfs_entries[] = {
1298c2ecf20Sopenharmony_ci	&dev_attr_dump_regs.attr,
1308c2ecf20Sopenharmony_ci	&dev_attr_resume_reason.attr,
1318c2ecf20Sopenharmony_ci	NULL,
1328c2ecf20Sopenharmony_ci};
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic struct attribute_group pcf_attr_group = {
1358c2ecf20Sopenharmony_ci	.name	= NULL,			/* put in device directory */
1368c2ecf20Sopenharmony_ci	.attrs	= pcf_sysfs_entries,
1378c2ecf20Sopenharmony_ci};
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_cistatic void
1408c2ecf20Sopenharmony_cipcf50633_client_dev_register(struct pcf50633 *pcf, const char *name,
1418c2ecf20Sopenharmony_ci						struct platform_device **pdev)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	int ret;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	*pdev = platform_device_alloc(name, -1);
1468c2ecf20Sopenharmony_ci	if (!*pdev) {
1478c2ecf20Sopenharmony_ci		dev_err(pcf->dev, "Failed to allocate %s\n", name);
1488c2ecf20Sopenharmony_ci		return;
1498c2ecf20Sopenharmony_ci	}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	(*pdev)->dev.parent = pcf->dev;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	ret = platform_device_add(*pdev);
1548c2ecf20Sopenharmony_ci	if (ret) {
1558c2ecf20Sopenharmony_ci		dev_err(pcf->dev, "Failed to register %s: %d\n", name, ret);
1568c2ecf20Sopenharmony_ci		platform_device_put(*pdev);
1578c2ecf20Sopenharmony_ci		*pdev = NULL;
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
1628c2ecf20Sopenharmony_cistatic int pcf50633_suspend(struct device *dev)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
1658c2ecf20Sopenharmony_ci	struct pcf50633 *pcf = i2c_get_clientdata(client);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	return pcf50633_irq_suspend(pcf);
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic int pcf50633_resume(struct device *dev)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
1738c2ecf20Sopenharmony_ci	struct pcf50633 *pcf = i2c_get_clientdata(client);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	return pcf50633_irq_resume(pcf);
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci#endif
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(pcf50633_pm, pcf50633_suspend, pcf50633_resume);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_cistatic const struct regmap_config pcf50633_regmap_config = {
1828c2ecf20Sopenharmony_ci	.reg_bits = 8,
1838c2ecf20Sopenharmony_ci	.val_bits = 8,
1848c2ecf20Sopenharmony_ci};
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic int pcf50633_probe(struct i2c_client *client,
1878c2ecf20Sopenharmony_ci				const struct i2c_device_id *ids)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	struct pcf50633 *pcf;
1908c2ecf20Sopenharmony_ci	struct platform_device *pdev;
1918c2ecf20Sopenharmony_ci	struct pcf50633_platform_data *pdata = dev_get_platdata(&client->dev);
1928c2ecf20Sopenharmony_ci	int i, j, ret;
1938c2ecf20Sopenharmony_ci	int version, variant;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	if (!client->irq) {
1968c2ecf20Sopenharmony_ci		dev_err(&client->dev, "Missing IRQ\n");
1978c2ecf20Sopenharmony_ci		return -ENOENT;
1988c2ecf20Sopenharmony_ci	}
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	pcf = devm_kzalloc(&client->dev, sizeof(*pcf), GFP_KERNEL);
2018c2ecf20Sopenharmony_ci	if (!pcf)
2028c2ecf20Sopenharmony_ci		return -ENOMEM;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	i2c_set_clientdata(client, pcf);
2058c2ecf20Sopenharmony_ci	pcf->dev = &client->dev;
2068c2ecf20Sopenharmony_ci	pcf->pdata = pdata;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	mutex_init(&pcf->lock);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	pcf->regmap = devm_regmap_init_i2c(client, &pcf50633_regmap_config);
2118c2ecf20Sopenharmony_ci	if (IS_ERR(pcf->regmap)) {
2128c2ecf20Sopenharmony_ci		ret = PTR_ERR(pcf->regmap);
2138c2ecf20Sopenharmony_ci		dev_err(pcf->dev, "Failed to allocate register map: %d\n", ret);
2148c2ecf20Sopenharmony_ci		return ret;
2158c2ecf20Sopenharmony_ci	}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	version = pcf50633_reg_read(pcf, 0);
2188c2ecf20Sopenharmony_ci	variant = pcf50633_reg_read(pcf, 1);
2198c2ecf20Sopenharmony_ci	if (version < 0 || variant < 0) {
2208c2ecf20Sopenharmony_ci		dev_err(pcf->dev, "Unable to probe pcf50633\n");
2218c2ecf20Sopenharmony_ci		ret = -ENODEV;
2228c2ecf20Sopenharmony_ci		return ret;
2238c2ecf20Sopenharmony_ci	}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	dev_info(pcf->dev, "Probed device version %d variant %d\n",
2268c2ecf20Sopenharmony_ci							version, variant);
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	pcf50633_irq_init(pcf, client->irq);
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	/* Create sub devices */
2318c2ecf20Sopenharmony_ci	pcf50633_client_dev_register(pcf, "pcf50633-input", &pcf->input_pdev);
2328c2ecf20Sopenharmony_ci	pcf50633_client_dev_register(pcf, "pcf50633-rtc", &pcf->rtc_pdev);
2338c2ecf20Sopenharmony_ci	pcf50633_client_dev_register(pcf, "pcf50633-mbc", &pcf->mbc_pdev);
2348c2ecf20Sopenharmony_ci	pcf50633_client_dev_register(pcf, "pcf50633-adc", &pcf->adc_pdev);
2358c2ecf20Sopenharmony_ci	pcf50633_client_dev_register(pcf, "pcf50633-backlight", &pcf->bl_pdev);
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	for (i = 0; i < PCF50633_NUM_REGULATORS; i++) {
2398c2ecf20Sopenharmony_ci		pdev = platform_device_alloc("pcf50633-regulator", i);
2408c2ecf20Sopenharmony_ci		if (!pdev) {
2418c2ecf20Sopenharmony_ci			ret = -ENOMEM;
2428c2ecf20Sopenharmony_ci			goto err2;
2438c2ecf20Sopenharmony_ci		}
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci		pdev->dev.parent = pcf->dev;
2468c2ecf20Sopenharmony_ci		ret = platform_device_add_data(pdev, &pdata->reg_init_data[i],
2478c2ecf20Sopenharmony_ci					       sizeof(pdata->reg_init_data[i]));
2488c2ecf20Sopenharmony_ci		if (ret)
2498c2ecf20Sopenharmony_ci			goto err;
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci		ret = platform_device_add(pdev);
2528c2ecf20Sopenharmony_ci		if (ret)
2538c2ecf20Sopenharmony_ci			goto err;
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci		pcf->regulator_pdev[i] = pdev;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group);
2598c2ecf20Sopenharmony_ci	if (ret)
2608c2ecf20Sopenharmony_ci		dev_warn(pcf->dev, "error creating sysfs entries\n");
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	if (pdata->probe_done)
2638c2ecf20Sopenharmony_ci		pdata->probe_done(pcf);
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	return 0;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_cierr:
2688c2ecf20Sopenharmony_ci	platform_device_put(pdev);
2698c2ecf20Sopenharmony_cierr2:
2708c2ecf20Sopenharmony_ci	for (j = 0; j < i; j++)
2718c2ecf20Sopenharmony_ci		platform_device_put(pcf->regulator_pdev[j]);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return ret;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cistatic int pcf50633_remove(struct i2c_client *client)
2778c2ecf20Sopenharmony_ci{
2788c2ecf20Sopenharmony_ci	struct pcf50633 *pcf = i2c_get_clientdata(client);
2798c2ecf20Sopenharmony_ci	int i;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	sysfs_remove_group(&client->dev.kobj, &pcf_attr_group);
2828c2ecf20Sopenharmony_ci	pcf50633_irq_free(pcf);
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	platform_device_unregister(pcf->input_pdev);
2858c2ecf20Sopenharmony_ci	platform_device_unregister(pcf->rtc_pdev);
2868c2ecf20Sopenharmony_ci	platform_device_unregister(pcf->mbc_pdev);
2878c2ecf20Sopenharmony_ci	platform_device_unregister(pcf->adc_pdev);
2888c2ecf20Sopenharmony_ci	platform_device_unregister(pcf->bl_pdev);
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	for (i = 0; i < PCF50633_NUM_REGULATORS; i++)
2918c2ecf20Sopenharmony_ci		platform_device_unregister(pcf->regulator_pdev[i]);
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	return 0;
2948c2ecf20Sopenharmony_ci}
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_cistatic const struct i2c_device_id pcf50633_id_table[] = {
2978c2ecf20Sopenharmony_ci	{"pcf50633", 0x73},
2988c2ecf20Sopenharmony_ci	{/* end of list */}
2998c2ecf20Sopenharmony_ci};
3008c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, pcf50633_id_table);
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_cistatic struct i2c_driver pcf50633_driver = {
3038c2ecf20Sopenharmony_ci	.driver = {
3048c2ecf20Sopenharmony_ci		.name	= "pcf50633",
3058c2ecf20Sopenharmony_ci		.pm	= &pcf50633_pm,
3068c2ecf20Sopenharmony_ci	},
3078c2ecf20Sopenharmony_ci	.id_table = pcf50633_id_table,
3088c2ecf20Sopenharmony_ci	.probe = pcf50633_probe,
3098c2ecf20Sopenharmony_ci	.remove = pcf50633_remove,
3108c2ecf20Sopenharmony_ci};
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_cistatic int __init pcf50633_init(void)
3138c2ecf20Sopenharmony_ci{
3148c2ecf20Sopenharmony_ci	return i2c_add_driver(&pcf50633_driver);
3158c2ecf20Sopenharmony_ci}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_cistatic void __exit pcf50633_exit(void)
3188c2ecf20Sopenharmony_ci{
3198c2ecf20Sopenharmony_ci	i2c_del_driver(&pcf50633_driver);
3208c2ecf20Sopenharmony_ci}
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("I2C chip driver for NXP PCF50633 PMU");
3238c2ecf20Sopenharmony_ciMODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
3248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_cisubsys_initcall(pcf50633_init);
3278c2ecf20Sopenharmony_cimodule_exit(pcf50633_exit);
328