18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * mcp3021.c - driver for Microchip MCP3021 and MCP3221
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2008-2009, 2012 Freescale Semiconductor, Inc.
68c2ecf20Sopenharmony_ci * Author: Mingkai Hu <Mingkai.hu@freescale.com>
78c2ecf20Sopenharmony_ci * Reworked by Sven Schuchmann <schuchmann@schleissheimer.de>
88c2ecf20Sopenharmony_ci * DT support added by Clemens Gruber <clemens.gruber@pqgruber.com>
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * This driver export the value of analog input voltage to sysfs, the
118c2ecf20Sopenharmony_ci * voltage unit is mV. Through the sysfs interface, lm-sensors tool
128c2ecf20Sopenharmony_ci * can also display the input voltage.
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/kernel.h>
168c2ecf20Sopenharmony_ci#include <linux/module.h>
178c2ecf20Sopenharmony_ci#include <linux/hwmon.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <linux/i2c.h>
208c2ecf20Sopenharmony_ci#include <linux/err.h>
218c2ecf20Sopenharmony_ci#include <linux/device.h>
228c2ecf20Sopenharmony_ci#include <linux/of.h>
238c2ecf20Sopenharmony_ci#include <linux/of_device.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/* Vdd / reference voltage in millivolt */
268c2ecf20Sopenharmony_ci#define MCP3021_VDD_REF_MAX	5500
278c2ecf20Sopenharmony_ci#define MCP3021_VDD_REF_MIN	2700
288c2ecf20Sopenharmony_ci#define MCP3021_VDD_REF_DEFAULT	3300
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci/* output format */
318c2ecf20Sopenharmony_ci#define MCP3021_SAR_SHIFT	2
328c2ecf20Sopenharmony_ci#define MCP3021_SAR_MASK	0x3ff
338c2ecf20Sopenharmony_ci#define MCP3021_OUTPUT_RES	10	/* 10-bit resolution */
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define MCP3221_SAR_SHIFT	0
368c2ecf20Sopenharmony_ci#define MCP3221_SAR_MASK	0xfff
378c2ecf20Sopenharmony_ci#define MCP3221_OUTPUT_RES	12	/* 12-bit resolution */
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cienum chips {
408c2ecf20Sopenharmony_ci	mcp3021,
418c2ecf20Sopenharmony_ci	mcp3221
428c2ecf20Sopenharmony_ci};
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci/*
458c2ecf20Sopenharmony_ci * Client data (each client gets its own)
468c2ecf20Sopenharmony_ci */
478c2ecf20Sopenharmony_cistruct mcp3021_data {
488c2ecf20Sopenharmony_ci	struct device *hwmon_dev;
498c2ecf20Sopenharmony_ci	u32 vdd;        /* supply and reference voltage in millivolt */
508c2ecf20Sopenharmony_ci	u16 sar_shift;
518c2ecf20Sopenharmony_ci	u16 sar_mask;
528c2ecf20Sopenharmony_ci	u8 output_res;
538c2ecf20Sopenharmony_ci};
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic int mcp3021_read16(struct i2c_client *client)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	struct mcp3021_data *data = i2c_get_clientdata(client);
588c2ecf20Sopenharmony_ci	int ret;
598c2ecf20Sopenharmony_ci	u16 reg;
608c2ecf20Sopenharmony_ci	__be16 buf;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	ret = i2c_master_recv(client, (char *)&buf, 2);
638c2ecf20Sopenharmony_ci	if (ret < 0)
648c2ecf20Sopenharmony_ci		return ret;
658c2ecf20Sopenharmony_ci	if (ret != 2)
668c2ecf20Sopenharmony_ci		return -EIO;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	/* The output code of the MCP3021 is transmitted with MSB first. */
698c2ecf20Sopenharmony_ci	reg = be16_to_cpu(buf);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	/*
728c2ecf20Sopenharmony_ci	 * The ten-bit output code is composed of the lower 4-bit of the
738c2ecf20Sopenharmony_ci	 * first byte and the upper 6-bit of the second byte.
748c2ecf20Sopenharmony_ci	 */
758c2ecf20Sopenharmony_ci	reg = (reg >> data->sar_shift) & data->sar_mask;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	return reg;
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic inline u16 volts_from_reg(struct mcp3021_data *data, u16 val)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	return DIV_ROUND_CLOSEST(data->vdd * val, 1 << data->output_res);
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic ssize_t in0_input_show(struct device *dev,
868c2ecf20Sopenharmony_ci			      struct device_attribute *attr, char *buf)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
898c2ecf20Sopenharmony_ci	struct mcp3021_data *data = i2c_get_clientdata(client);
908c2ecf20Sopenharmony_ci	int reg, in_input;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	reg = mcp3021_read16(client);
938c2ecf20Sopenharmony_ci	if (reg < 0)
948c2ecf20Sopenharmony_ci		return reg;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	in_input = volts_from_reg(data, reg);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", in_input);
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(in0_input);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic const struct i2c_device_id mcp3021_id[];
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic int mcp3021_probe(struct i2c_client *client)
1068c2ecf20Sopenharmony_ci{
1078c2ecf20Sopenharmony_ci	int err;
1088c2ecf20Sopenharmony_ci	struct mcp3021_data *data = NULL;
1098c2ecf20Sopenharmony_ci	struct device_node *np = client->dev.of_node;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
1128c2ecf20Sopenharmony_ci		return -ENODEV;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	data = devm_kzalloc(&client->dev, sizeof(struct mcp3021_data),
1158c2ecf20Sopenharmony_ci			    GFP_KERNEL);
1168c2ecf20Sopenharmony_ci	if (!data)
1178c2ecf20Sopenharmony_ci		return -ENOMEM;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	i2c_set_clientdata(client, data);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	if (np) {
1228c2ecf20Sopenharmony_ci		if (!of_property_read_u32(np, "reference-voltage-microvolt",
1238c2ecf20Sopenharmony_ci					  &data->vdd))
1248c2ecf20Sopenharmony_ci			data->vdd /= 1000;
1258c2ecf20Sopenharmony_ci		else
1268c2ecf20Sopenharmony_ci			data->vdd = MCP3021_VDD_REF_DEFAULT;
1278c2ecf20Sopenharmony_ci	} else {
1288c2ecf20Sopenharmony_ci		u32 *pdata = dev_get_platdata(&client->dev);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci		if (pdata)
1318c2ecf20Sopenharmony_ci			data->vdd = *pdata;
1328c2ecf20Sopenharmony_ci		else
1338c2ecf20Sopenharmony_ci			data->vdd = MCP3021_VDD_REF_DEFAULT;
1348c2ecf20Sopenharmony_ci	}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	switch (i2c_match_id(mcp3021_id, client)->driver_data) {
1378c2ecf20Sopenharmony_ci	case mcp3021:
1388c2ecf20Sopenharmony_ci		data->sar_shift = MCP3021_SAR_SHIFT;
1398c2ecf20Sopenharmony_ci		data->sar_mask = MCP3021_SAR_MASK;
1408c2ecf20Sopenharmony_ci		data->output_res = MCP3021_OUTPUT_RES;
1418c2ecf20Sopenharmony_ci		break;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	case mcp3221:
1448c2ecf20Sopenharmony_ci		data->sar_shift = MCP3221_SAR_SHIFT;
1458c2ecf20Sopenharmony_ci		data->sar_mask = MCP3221_SAR_MASK;
1468c2ecf20Sopenharmony_ci		data->output_res = MCP3221_OUTPUT_RES;
1478c2ecf20Sopenharmony_ci		break;
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	if (data->vdd > MCP3021_VDD_REF_MAX || data->vdd < MCP3021_VDD_REF_MIN)
1518c2ecf20Sopenharmony_ci		return -EINVAL;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	err = sysfs_create_file(&client->dev.kobj, &dev_attr_in0_input.attr);
1548c2ecf20Sopenharmony_ci	if (err)
1558c2ecf20Sopenharmony_ci		return err;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	data->hwmon_dev = hwmon_device_register(&client->dev);
1588c2ecf20Sopenharmony_ci	if (IS_ERR(data->hwmon_dev)) {
1598c2ecf20Sopenharmony_ci		err = PTR_ERR(data->hwmon_dev);
1608c2ecf20Sopenharmony_ci		goto exit_remove;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	return 0;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ciexit_remove:
1668c2ecf20Sopenharmony_ci	sysfs_remove_file(&client->dev.kobj, &dev_attr_in0_input.attr);
1678c2ecf20Sopenharmony_ci	return err;
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic int mcp3021_remove(struct i2c_client *client)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct mcp3021_data *data = i2c_get_clientdata(client);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	hwmon_device_unregister(data->hwmon_dev);
1758c2ecf20Sopenharmony_ci	sysfs_remove_file(&client->dev.kobj, &dev_attr_in0_input.attr);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	return 0;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic const struct i2c_device_id mcp3021_id[] = {
1818c2ecf20Sopenharmony_ci	{ "mcp3021", mcp3021 },
1828c2ecf20Sopenharmony_ci	{ "mcp3221", mcp3221 },
1838c2ecf20Sopenharmony_ci	{ }
1848c2ecf20Sopenharmony_ci};
1858c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, mcp3021_id);
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
1888c2ecf20Sopenharmony_cistatic const struct of_device_id of_mcp3021_match[] = {
1898c2ecf20Sopenharmony_ci	{ .compatible = "microchip,mcp3021", .data = (void *)mcp3021 },
1908c2ecf20Sopenharmony_ci	{ .compatible = "microchip,mcp3221", .data = (void *)mcp3221 },
1918c2ecf20Sopenharmony_ci	{ }
1928c2ecf20Sopenharmony_ci};
1938c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_mcp3021_match);
1948c2ecf20Sopenharmony_ci#endif
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cistatic struct i2c_driver mcp3021_driver = {
1978c2ecf20Sopenharmony_ci	.driver = {
1988c2ecf20Sopenharmony_ci		.name = "mcp3021",
1998c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(of_mcp3021_match),
2008c2ecf20Sopenharmony_ci	},
2018c2ecf20Sopenharmony_ci	.probe_new = mcp3021_probe,
2028c2ecf20Sopenharmony_ci	.remove = mcp3021_remove,
2038c2ecf20Sopenharmony_ci	.id_table = mcp3021_id,
2048c2ecf20Sopenharmony_ci};
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cimodule_i2c_driver(mcp3021_driver);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mingkai Hu <Mingkai.hu@freescale.com>");
2098c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Microchip MCP3021/MCP3221 driver");
2108c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
211