1// SPDX-License-Identifier: GPL-2.0-only
2
3/*
4 * Copyright (c) Rajat Khandelwal <rajat.khandelwal@linux.intel.com>
5 *
6 * Maxim MAX30208 digital temperature sensor with 0.1°C accuracy
7 * (7-bit I2C slave address (0x50 - 0x53))
8 */
9
10#include <linux/bitops.h>
11#include <linux/delay.h>
12#include <linux/iio/iio.h>
13#include <linux/i2c.h>
14#include <linux/module.h>
15#include <linux/types.h>
16
17#define MAX30208_STATUS			0x00
18#define MAX30208_STATUS_TEMP_RDY	BIT(0)
19#define MAX30208_INT_ENABLE		0x01
20#define MAX30208_INT_ENABLE_TEMP_RDY	BIT(0)
21
22#define MAX30208_FIFO_OVF_CNTR		0x06
23#define MAX30208_FIFO_DATA_CNTR		0x07
24#define MAX30208_FIFO_DATA		0x08
25
26#define MAX30208_FIFO_CONFIG		0x0a
27#define MAX30208_FIFO_CONFIG_RO		BIT(1)
28
29#define MAX30208_SYSTEM_CTRL		0x0c
30#define MAX30208_SYSTEM_CTRL_RESET	0x01
31
32#define MAX30208_TEMP_SENSOR_SETUP	0x14
33#define MAX30208_TEMP_SENSOR_SETUP_CONV	BIT(0)
34
35struct max30208_data {
36	struct i2c_client *client;
37	struct iio_dev *indio_dev;
38	struct mutex lock; /* Lock to prevent concurrent reads of temperature readings */
39};
40
41static const struct iio_chan_spec max30208_channels[] = {
42	{
43		.type = IIO_TEMP,
44		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
45	},
46};
47
48/**
49 * max30208_request() - Request a reading
50 * @data: Struct comprising member elements of the device
51 *
52 * Requests a reading from the device and waits until the conversion is ready.
53 */
54static int max30208_request(struct max30208_data *data)
55{
56	/*
57	 * Sensor can take up to 500 ms to respond so execute a total of
58	 * 10 retries to give the device sufficient time.
59	 */
60	int retries = 10;
61	u8 regval;
62	int ret;
63
64	ret = i2c_smbus_read_byte_data(data->client, MAX30208_TEMP_SENSOR_SETUP);
65	if (ret < 0)
66		return ret;
67
68	regval = ret | MAX30208_TEMP_SENSOR_SETUP_CONV;
69
70	ret = i2c_smbus_write_byte_data(data->client, MAX30208_TEMP_SENSOR_SETUP, regval);
71	if (ret)
72		return ret;
73
74	while (retries--) {
75		ret = i2c_smbus_read_byte_data(data->client, MAX30208_STATUS);
76		if (ret < 0)
77			return ret;
78
79		if (ret & MAX30208_STATUS_TEMP_RDY)
80			return 0;
81
82		msleep(50);
83	}
84	dev_err(&data->client->dev, "Temperature conversion failed\n");
85
86	return -ETIMEDOUT;
87}
88
89static int max30208_update_temp(struct max30208_data *data)
90{
91	u8 data_count;
92	int ret;
93
94	mutex_lock(&data->lock);
95
96	ret = max30208_request(data);
97	if (ret)
98		goto unlock;
99
100	ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_OVF_CNTR);
101	if (ret < 0)
102		goto unlock;
103	else if (!ret) {
104		ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_DATA_CNTR);
105		if (ret < 0)
106			goto unlock;
107
108		data_count = ret;
109	} else
110		data_count = 1;
111
112	while (data_count) {
113		ret = i2c_smbus_read_word_swapped(data->client, MAX30208_FIFO_DATA);
114		if (ret < 0)
115			goto unlock;
116
117		data_count--;
118	}
119
120unlock:
121	mutex_unlock(&data->lock);
122	return ret;
123}
124
125/**
126 * max30208_config_setup() - Set up FIFO configuration register
127 * @data: Struct comprising member elements of the device
128 *
129 * Sets the rollover bit to '1' to enable overwriting FIFO during overflow.
130 */
131static int max30208_config_setup(struct max30208_data *data)
132{
133	u8 regval;
134	int ret;
135
136	ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_CONFIG);
137	if (ret < 0)
138		return ret;
139
140	regval = ret | MAX30208_FIFO_CONFIG_RO;
141
142	ret = i2c_smbus_write_byte_data(data->client, MAX30208_FIFO_CONFIG, regval);
143	if (ret)
144		return ret;
145
146	return 0;
147}
148
149static int max30208_read(struct iio_dev *indio_dev,
150			 struct iio_chan_spec const *chan,
151			 int *val, int *val2, long mask)
152{
153	struct max30208_data *data = iio_priv(indio_dev);
154	int ret;
155
156	switch (mask) {
157	case IIO_CHAN_INFO_RAW:
158		ret = max30208_update_temp(data);
159		if (ret < 0)
160			return ret;
161
162		*val = sign_extend32(ret, 15);
163		return IIO_VAL_INT;
164
165	case IIO_CHAN_INFO_SCALE:
166		*val = 5;
167		return IIO_VAL_INT;
168
169	default:
170		return -EINVAL;
171	}
172}
173
174static const struct iio_info max30208_info = {
175	.read_raw = max30208_read,
176};
177
178static int max30208_probe(struct i2c_client *i2c)
179{
180	struct device *dev = &i2c->dev;
181	struct max30208_data *data;
182	struct iio_dev *indio_dev;
183	int ret;
184
185	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
186	if (!indio_dev)
187		return -ENOMEM;
188
189	data = iio_priv(indio_dev);
190	data->client = i2c;
191	mutex_init(&data->lock);
192
193	indio_dev->name = "max30208";
194	indio_dev->channels = max30208_channels;
195	indio_dev->num_channels = ARRAY_SIZE(max30208_channels);
196	indio_dev->info = &max30208_info;
197	indio_dev->modes = INDIO_DIRECT_MODE;
198
199	ret = i2c_smbus_write_byte_data(data->client, MAX30208_SYSTEM_CTRL,
200					MAX30208_SYSTEM_CTRL_RESET);
201	if (ret) {
202		dev_err(dev, "Failure in performing reset\n");
203		return ret;
204	}
205
206	msleep(50);
207
208	ret = max30208_config_setup(data);
209	if (ret)
210		return ret;
211
212	ret = devm_iio_device_register(dev, indio_dev);
213	if (ret) {
214		dev_err(dev, "Failed to register IIO device\n");
215		return ret;
216	}
217
218	return 0;
219}
220
221static const struct i2c_device_id max30208_id_table[] = {
222	{ "max30208" },
223	{ }
224};
225MODULE_DEVICE_TABLE(i2c, max30208_id_table);
226
227static const struct acpi_device_id max30208_acpi_match[] = {
228	{ "MAX30208" },
229	{ }
230};
231MODULE_DEVICE_TABLE(acpi, max30208_acpi_match);
232
233static const struct of_device_id max30208_of_match[] = {
234	{ .compatible = "maxim,max30208" },
235	{ }
236};
237MODULE_DEVICE_TABLE(of, max30208_of_match);
238
239static struct i2c_driver max30208_driver = {
240	.driver = {
241		.name = "max30208",
242		.of_match_table = max30208_of_match,
243		.acpi_match_table = max30208_acpi_match,
244	},
245	.probe = max30208_probe,
246	.id_table = max30208_id_table,
247};
248module_i2c_driver(max30208_driver);
249
250MODULE_AUTHOR("Rajat Khandelwal <rajat.khandelwal@linux.intel.com>");
251MODULE_DESCRIPTION("Maxim MAX30208 digital temperature sensor");
252MODULE_LICENSE("GPL");
253