summaryrefslogtreecommitdiffstats
path: root/drivers/iio/pressure/t5403.c
blob: 2667e71721f5100f0c7bdf7659bd3e32ce80a409 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
 * t5403.c - Support for EPCOS T5403 pressure/temperature sensor
 *
 * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net>
 *
 * This file is subject to the terms and conditions of version 2 of
 * the GNU General Public License.  See the file COPYING in the main
 * directory of this archive for more details.
 *
 * (7-bit I2C slave address 0x77)
 *
 * TODO: end-of-conversion irq
 */

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/delay.h>

#define T5403_DATA 0xf5 /* data, LSB first, 16 bit */
#define T5403_CALIB_DATA 0x8e /* 10 calibration coeff., LSB first, 16 bit */
#define T5403_SLAVE_ADDR 0x88 /* I2C slave address, 0x77 */
#define T5403_COMMAND 0xf1

/* command bits */
#define T5403_MODE_SHIFT 3 /* conversion time: 2, 8, 16, 66 ms */
#define T5403_PT BIT(1) /* 0 .. pressure, 1 .. temperature measurement */
#define T5403_SCO BIT(0) /* start conversion */

#define T5403_MODE_LOW 0
#define T5403_MODE_STANDARD 1
#define T5403_MODE_HIGH 2
#define T5403_MODE_ULTRA_HIGH 3

#define T5403_I2C_MASK (~BIT(7))
#define T5403_I2C_ADDR 0x77

static const int t5403_pressure_conv_ms[] = {2, 8, 16, 66};

struct t5403_data {
	struct i2c_client *client;
	struct mutex lock;
	int mode;
	__le16 c[10];
};

#define T5403_C_U16(i) le16_to_cpu(data->c[(i) - 1])
#define T5403_C(i) sign_extend32(T5403_C_U16(i), 15)

static int t5403_read(struct t5403_data *data, bool pressure)
{
	int wait_time = 3;  /* wakeup time in ms */

	int ret = i2c_smbus_write_byte_data(data->client, T5403_COMMAND,
		(pressure ? (data->mode << T5403_MODE_SHIFT) : T5403_PT) |
		T5403_SCO);
	if (ret < 0)
		return ret;

	wait_time += pressure ? t5403_pressure_conv_ms[data->mode] : 2;

	msleep(wait_time);

	return i2c_smbus_read_word_data(data->client, T5403_DATA);
}

static int t5403_comp_pressure(struct t5403_data *data, int *val, int *val2)
{
	int ret;
	s16 t_r;
	u16 p_r;
	s32 S, O, X;

	mutex_lock(&data->lock);

	ret = t5403_read(data, false);
	if (ret < 0)
		goto done;
	t_r = ret;

	ret = t5403_read(data, true);
	if (ret < 0)
		goto done;
	p_r = ret;

	/* see EPCOS application note */
	S = T5403_C_U16(3) + (s32) T5403_C_U16(4) * t_r / 0x20000 +
		T5403_C(5) * t_r / 0x8000 * t_r / 0x80000 +
		T5403_C(9) * t_r / 0x8000 * t_r / 0x8000 * t_r / 0x10000;

	O = T5403_C(6) * 0x4000 + T5403_C(7) * t_r / 8 +
		T5403_C(8) * t_r / 0x8000 * t_r / 16 +
		T5403_C(9) * t_r / 0x8000 * t_r / 0x10000 * t_r;

	X = (S * p_r + O) / 0x4000;

	X += ((X - 75000) * (X - 75000) / 0x10000 - 9537) *
	    T5403_C(10) / 0x10000;

	*val = X / 1000;
	*val2 = (X % 1000) * 1000;

done:
	mutex_unlock(&data->lock);
	return ret;
}

static int t5403_comp_temp(struct t5403_data *data, int *val)
{
	int ret;
	s16 t_r;

	mutex_lock(&data->lock);
	ret = t5403_read(data, false);
	if (ret < 0)
		goto done;
	t_r = ret;

	/* see EPCOS application note */
	*val = ((s32) T5403_C_U16(1) * t_r / 0x100 +
		(s32) T5403_C_U16(2) * 0x40) * 1000 / 0x10000;

done:
	mutex_unlock(&data->lock);
	return ret;
}

static int t5403_read_raw(struct iio_dev *indio_dev,
			  struct iio_chan_spec const *chan,
			  int *val, int *val2, long mask)
{
	struct t5403_data *data = iio_priv(indio_dev);
	int ret;

	switch (mask) {
	case IIO_CHAN_INFO_PROCESSED:
		switch (chan->type) {
		case IIO_PRESSURE:
			ret = t5403_comp_pressure(data, val, val2);
			if (ret < 0)
				return ret;
			return IIO_VAL_INT_PLUS_MICRO;
		case IIO_TEMP:
			ret = t5403_comp_temp(data, val);
			if (ret < 0)
				return ret;
			return IIO_VAL_INT;
		default:
			return -EINVAL;
	    }
	case IIO_CHAN_INFO_INT_TIME:
		*val = 0;
		*val2 = t5403_pressure_conv_ms[data->mode] * 1000;
		return IIO_VAL_INT_PLUS_MICRO;
	default:
		return -EINVAL;
	}
}

static int t5403_write_raw(struct iio_dev *indio_dev,
			   struct iio_chan_spec const *chan,
			   int val, int val2, long mask)
{
	struct t5403_data *data = iio_priv(indio_dev);
	int i;

	switch (mask) {
	case IIO_CHAN_INFO_INT_TIME:
		if (val != 0)
			return -EINVAL;
		for (i = 0; i < ARRAY_SIZE(t5403_pressure_conv_ms); i++)
			if (val2 == t5403_pressure_conv_ms[i] * 1000) {
				mutex_lock(&data->lock);
				data->mode = i;
				mutex_unlock(&data->lock);
				return 0;
			}
		return -EINVAL;
	default:
		return -EINVAL;
	}
}

static const struct iio_chan_spec t5403_channels[] = {
	{
		.type = IIO_PRESSURE,
		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
		    BIT(IIO_CHAN_INFO_INT_TIME),
	},
	{
		.type = IIO_TEMP,
		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
	},
};

static IIO_CONST_ATTR_INT_TIME_AVAIL("0.002 0.008 0.016 0.066");

static struct attribute *t5403_attributes[] = {
	&iio_const_attr_integration_time_available.dev_attr.attr,
	NULL
};

static const struct attribute_group t5403_attribute_group = {
	.attrs = t5403_attributes,
};

static const struct iio_info t5403_info = {
	.read_raw = &t5403_read_raw,
	.write_raw = &t5403_write_raw,
	.attrs = &t5403_attribute_group,
	.driver_module = THIS_MODULE,
};

static int t5403_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct t5403_data *data;
	struct iio_dev *indio_dev;
	int ret;

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA |
	    I2C_FUNC_SMBUS_I2C_BLOCK))
		return -EOPNOTSUPP;

	ret = i2c_smbus_read_byte_data(client, T5403_SLAVE_ADDR);
	if (ret < 0)
		return ret;
	if ((ret & T5403_I2C_MASK) != T5403_I2C_ADDR)
		return -ENODEV;

	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
	if (!indio_dev)
		return -ENOMEM;

	data = iio_priv(indio_dev);
	data->client = client;
	mutex_init(&data->lock);

	i2c_set_clientdata(client, indio_dev);
	indio_dev->info = &t5403_info;
	indio_dev->name = id->name;
	indio_dev->dev.parent = &client->dev;
	indio_dev->modes = INDIO_DIRECT_MODE;
	indio_dev->channels = t5403_channels;
	indio_dev->num_channels = ARRAY_SIZE(t5403_channels);

	data->mode = T5403_MODE_STANDARD;

	ret = i2c_smbus_read_i2c_block_data(data->client, T5403_CALIB_DATA,
	    sizeof(data->c), (u8 *) data->c);
	if (ret < 0)
		return ret;

	return devm_iio_device_register(&client->dev, indio_dev);
}

static const struct i2c_device_id t5403_id[] = {
	{ "t5403", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, t5403_id);

static struct i2c_driver t5403_driver = {
	.driver = {
		.name	= "t5403",
	},
	.probe = t5403_probe,
	.id_table = t5403_id,
};
module_i2c_driver(t5403_driver);

MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
MODULE_DESCRIPTION("EPCOS T5403 pressure/temperature sensor driver");
MODULE_LICENSE("GPL");
OpenPOWER on IntegriCloud