1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * tmp006.c - Support for TI TMP006 IR thermopile sensor |
4 | * |
5 | * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net> |
6 | * |
7 | * Driver for the Texas Instruments I2C 16-bit IR thermopile sensor |
8 | * |
9 | * (7-bit I2C slave address 0x40, changeable via ADR pins) |
10 | * |
11 | * TODO: data ready irq |
12 | */ |
13 | |
14 | #include <linux/err.h> |
15 | #include <linux/i2c.h> |
16 | #include <linux/delay.h> |
17 | #include <linux/module.h> |
18 | #include <linux/mod_devicetable.h> |
19 | #include <linux/pm.h> |
20 | #include <linux/bitops.h> |
21 | |
22 | #include <linux/iio/iio.h> |
23 | #include <linux/iio/sysfs.h> |
24 | |
25 | #define TMP006_VOBJECT 0x00 |
26 | #define TMP006_TAMBIENT 0x01 |
27 | #define TMP006_CONFIG 0x02 |
28 | #define TMP006_MANUFACTURER_ID 0xfe |
29 | #define TMP006_DEVICE_ID 0xff |
30 | |
31 | #define TMP006_TAMBIENT_SHIFT 2 |
32 | |
33 | #define TMP006_CONFIG_RESET BIT(15) |
34 | #define TMP006_CONFIG_DRDY_EN BIT(8) |
35 | #define TMP006_CONFIG_DRDY BIT(7) |
36 | |
37 | #define TMP006_CONFIG_MOD_MASK GENMASK(14, 12) |
38 | |
39 | #define TMP006_CONFIG_CR_MASK GENMASK(11, 9) |
40 | #define TMP006_CONFIG_CR_SHIFT 9 |
41 | |
42 | #define TMP006_MANUFACTURER_MAGIC 0x5449 |
43 | #define TMP006_DEVICE_MAGIC 0x0067 |
44 | |
45 | struct tmp006_data { |
46 | struct i2c_client *client; |
47 | u16 config; |
48 | }; |
49 | |
50 | static int tmp006_read_measurement(struct tmp006_data *data, u8 reg) |
51 | { |
52 | s32 ret; |
53 | int tries = 50; |
54 | |
55 | while (tries-- > 0) { |
56 | ret = i2c_smbus_read_word_swapped(client: data->client, |
57 | TMP006_CONFIG); |
58 | if (ret < 0) |
59 | return ret; |
60 | if (ret & TMP006_CONFIG_DRDY) |
61 | break; |
62 | msleep(msecs: 100); |
63 | } |
64 | |
65 | if (tries < 0) |
66 | return -EIO; |
67 | |
68 | return i2c_smbus_read_word_swapped(client: data->client, command: reg); |
69 | } |
70 | |
71 | static const int tmp006_freqs[5][2] = { {4, 0}, {2, 0}, {1, 0}, |
72 | {0, 500000}, {0, 250000} }; |
73 | |
74 | static int tmp006_read_raw(struct iio_dev *indio_dev, |
75 | struct iio_chan_spec const *channel, int *val, |
76 | int *val2, long mask) |
77 | { |
78 | struct tmp006_data *data = iio_priv(indio_dev); |
79 | s32 ret; |
80 | int cr; |
81 | |
82 | switch (mask) { |
83 | case IIO_CHAN_INFO_RAW: |
84 | if (channel->type == IIO_VOLTAGE) { |
85 | /* LSB is 156.25 nV */ |
86 | ret = tmp006_read_measurement(data, TMP006_VOBJECT); |
87 | if (ret < 0) |
88 | return ret; |
89 | *val = sign_extend32(value: ret, index: 15); |
90 | } else if (channel->type == IIO_TEMP) { |
91 | /* LSB is 0.03125 degrees Celsius */ |
92 | ret = tmp006_read_measurement(data, TMP006_TAMBIENT); |
93 | if (ret < 0) |
94 | return ret; |
95 | *val = sign_extend32(value: ret, index: 15) >> TMP006_TAMBIENT_SHIFT; |
96 | } else { |
97 | break; |
98 | } |
99 | return IIO_VAL_INT; |
100 | case IIO_CHAN_INFO_SCALE: |
101 | if (channel->type == IIO_VOLTAGE) { |
102 | *val = 0; |
103 | *val2 = 156250; |
104 | } else if (channel->type == IIO_TEMP) { |
105 | *val = 31; |
106 | *val2 = 250000; |
107 | } else { |
108 | break; |
109 | } |
110 | return IIO_VAL_INT_PLUS_MICRO; |
111 | case IIO_CHAN_INFO_SAMP_FREQ: |
112 | cr = (data->config & TMP006_CONFIG_CR_MASK) |
113 | >> TMP006_CONFIG_CR_SHIFT; |
114 | *val = tmp006_freqs[cr][0]; |
115 | *val2 = tmp006_freqs[cr][1]; |
116 | return IIO_VAL_INT_PLUS_MICRO; |
117 | default: |
118 | break; |
119 | } |
120 | |
121 | return -EINVAL; |
122 | } |
123 | |
124 | static int tmp006_write_raw(struct iio_dev *indio_dev, |
125 | struct iio_chan_spec const *chan, |
126 | int val, |
127 | int val2, |
128 | long mask) |
129 | { |
130 | struct tmp006_data *data = iio_priv(indio_dev); |
131 | int i; |
132 | |
133 | if (mask != IIO_CHAN_INFO_SAMP_FREQ) |
134 | return -EINVAL; |
135 | |
136 | for (i = 0; i < ARRAY_SIZE(tmp006_freqs); i++) |
137 | if ((val == tmp006_freqs[i][0]) && |
138 | (val2 == tmp006_freqs[i][1])) { |
139 | data->config &= ~TMP006_CONFIG_CR_MASK; |
140 | data->config |= i << TMP006_CONFIG_CR_SHIFT; |
141 | |
142 | return i2c_smbus_write_word_swapped(client: data->client, |
143 | TMP006_CONFIG, |
144 | value: data->config); |
145 | |
146 | } |
147 | return -EINVAL; |
148 | } |
149 | |
150 | static IIO_CONST_ATTR(sampling_frequency_available, "4 2 1 0.5 0.25" ); |
151 | |
152 | static struct attribute *tmp006_attributes[] = { |
153 | &iio_const_attr_sampling_frequency_available.dev_attr.attr, |
154 | NULL |
155 | }; |
156 | |
157 | static const struct attribute_group tmp006_attribute_group = { |
158 | .attrs = tmp006_attributes, |
159 | }; |
160 | |
161 | static const struct iio_chan_spec tmp006_channels[] = { |
162 | { |
163 | .type = IIO_VOLTAGE, |
164 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
165 | BIT(IIO_CHAN_INFO_SCALE), |
166 | .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), |
167 | }, |
168 | { |
169 | .type = IIO_TEMP, |
170 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
171 | BIT(IIO_CHAN_INFO_SCALE), |
172 | .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), |
173 | } |
174 | }; |
175 | |
176 | static const struct iio_info tmp006_info = { |
177 | .read_raw = tmp006_read_raw, |
178 | .write_raw = tmp006_write_raw, |
179 | .attrs = &tmp006_attribute_group, |
180 | }; |
181 | |
182 | static bool tmp006_check_identification(struct i2c_client *client) |
183 | { |
184 | int mid, did; |
185 | |
186 | mid = i2c_smbus_read_word_swapped(client, TMP006_MANUFACTURER_ID); |
187 | if (mid < 0) |
188 | return false; |
189 | |
190 | did = i2c_smbus_read_word_swapped(client, TMP006_DEVICE_ID); |
191 | if (did < 0) |
192 | return false; |
193 | |
194 | return mid == TMP006_MANUFACTURER_MAGIC && did == TMP006_DEVICE_MAGIC; |
195 | } |
196 | |
197 | static int tmp006_power(struct device *dev, bool up) |
198 | { |
199 | struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); |
200 | struct tmp006_data *data = iio_priv(indio_dev); |
201 | |
202 | if (up) |
203 | data->config |= TMP006_CONFIG_MOD_MASK; |
204 | else |
205 | data->config &= ~TMP006_CONFIG_MOD_MASK; |
206 | |
207 | return i2c_smbus_write_word_swapped(client: data->client, TMP006_CONFIG, |
208 | value: data->config); |
209 | } |
210 | |
211 | static void tmp006_powerdown_cleanup(void *dev) |
212 | { |
213 | tmp006_power(dev, up: false); |
214 | } |
215 | |
216 | static int tmp006_probe(struct i2c_client *client) |
217 | { |
218 | struct iio_dev *indio_dev; |
219 | struct tmp006_data *data; |
220 | int ret; |
221 | |
222 | if (!i2c_check_functionality(adap: client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) |
223 | return -EOPNOTSUPP; |
224 | |
225 | if (!tmp006_check_identification(client)) { |
226 | dev_err(&client->dev, "no TMP006 sensor\n" ); |
227 | return -ENODEV; |
228 | } |
229 | |
230 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
231 | if (!indio_dev) |
232 | return -ENOMEM; |
233 | |
234 | data = iio_priv(indio_dev); |
235 | i2c_set_clientdata(client, data: indio_dev); |
236 | data->client = client; |
237 | |
238 | indio_dev->name = dev_name(dev: &client->dev); |
239 | indio_dev->modes = INDIO_DIRECT_MODE; |
240 | indio_dev->info = &tmp006_info; |
241 | |
242 | indio_dev->channels = tmp006_channels; |
243 | indio_dev->num_channels = ARRAY_SIZE(tmp006_channels); |
244 | |
245 | ret = i2c_smbus_read_word_swapped(client: data->client, TMP006_CONFIG); |
246 | if (ret < 0) |
247 | return ret; |
248 | data->config = ret; |
249 | |
250 | if ((ret & TMP006_CONFIG_MOD_MASK) != TMP006_CONFIG_MOD_MASK) { |
251 | ret = tmp006_power(dev: &client->dev, up: true); |
252 | if (ret < 0) |
253 | return ret; |
254 | } |
255 | |
256 | ret = devm_add_action_or_reset(&client->dev, tmp006_powerdown_cleanup, |
257 | &client->dev); |
258 | if (ret < 0) |
259 | return ret; |
260 | |
261 | return devm_iio_device_register(&client->dev, indio_dev); |
262 | } |
263 | |
264 | static int tmp006_suspend(struct device *dev) |
265 | { |
266 | return tmp006_power(dev, up: false); |
267 | } |
268 | |
269 | static int tmp006_resume(struct device *dev) |
270 | { |
271 | return tmp006_power(dev, up: true); |
272 | } |
273 | |
274 | static DEFINE_SIMPLE_DEV_PM_OPS(tmp006_pm_ops, tmp006_suspend, tmp006_resume); |
275 | |
276 | static const struct of_device_id tmp006_of_match[] = { |
277 | { .compatible = "ti,tmp006" }, |
278 | { } |
279 | }; |
280 | MODULE_DEVICE_TABLE(of, tmp006_of_match); |
281 | |
282 | static const struct i2c_device_id tmp006_id[] = { |
283 | { "tmp006" , 0 }, |
284 | { } |
285 | }; |
286 | MODULE_DEVICE_TABLE(i2c, tmp006_id); |
287 | |
288 | static struct i2c_driver tmp006_driver = { |
289 | .driver = { |
290 | .name = "tmp006" , |
291 | .of_match_table = tmp006_of_match, |
292 | .pm = pm_sleep_ptr(&tmp006_pm_ops), |
293 | }, |
294 | .probe = tmp006_probe, |
295 | .id_table = tmp006_id, |
296 | }; |
297 | module_i2c_driver(tmp006_driver); |
298 | |
299 | MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>" ); |
300 | MODULE_DESCRIPTION("TI TMP006 IR thermopile sensor driver" ); |
301 | MODULE_LICENSE("GPL" ); |
302 | |