1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Digital temperature sensor with integrated Non-volatile memory |
4 | * Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com> |
5 | * |
6 | * Driver for the Texas Instruments TMP117 Temperature Sensor |
7 | * (7-bit I2C slave address (0x48 - 0x4B), changeable via ADD pins) |
8 | * |
9 | * Note: This driver assumes that the sensor has been calibrated beforehand. |
10 | */ |
11 | |
12 | #include <linux/delay.h> |
13 | #include <linux/err.h> |
14 | #include <linux/i2c.h> |
15 | #include <linux/module.h> |
16 | #include <linux/bitops.h> |
17 | #include <linux/types.h> |
18 | #include <linux/kernel.h> |
19 | #include <linux/limits.h> |
20 | #include <linux/property.h> |
21 | #include <linux/regulator/consumer.h> |
22 | |
23 | #include <linux/iio/iio.h> |
24 | |
25 | #define TMP117_REG_TEMP 0x0 |
26 | #define TMP117_REG_CFGR 0x1 |
27 | #define TMP117_REG_HIGH_LIM 0x2 |
28 | #define TMP117_REG_LOW_LIM 0x3 |
29 | #define TMP117_REG_EEPROM_UL 0x4 |
30 | #define TMP117_REG_EEPROM1 0x5 |
31 | #define TMP117_REG_EEPROM2 0x6 |
32 | #define TMP117_REG_TEMP_OFFSET 0x7 |
33 | #define TMP117_REG_EEPROM3 0x8 |
34 | #define TMP117_REG_DEVICE_ID 0xF |
35 | |
36 | #define TMP117_RESOLUTION_10UC 78125 |
37 | #define MICRODEGREE_PER_10MILLIDEGREE 10000 |
38 | |
39 | #define TMP116_DEVICE_ID 0x1116 |
40 | #define TMP117_DEVICE_ID 0x0117 |
41 | |
42 | struct tmp117_data { |
43 | struct i2c_client *client; |
44 | s16 calibbias; |
45 | }; |
46 | |
47 | struct tmp11x_info { |
48 | const char *name; |
49 | struct iio_chan_spec const *channels; |
50 | int num_channels; |
51 | }; |
52 | |
53 | static int tmp117_read_raw(struct iio_dev *indio_dev, |
54 | struct iio_chan_spec const *channel, int *val, |
55 | int *val2, long mask) |
56 | { |
57 | struct tmp117_data *data = iio_priv(indio_dev); |
58 | s32 ret; |
59 | |
60 | switch (mask) { |
61 | case IIO_CHAN_INFO_RAW: |
62 | ret = i2c_smbus_read_word_swapped(client: data->client, |
63 | TMP117_REG_TEMP); |
64 | if (ret < 0) |
65 | return ret; |
66 | *val = sign_extend32(value: ret, index: 15); |
67 | return IIO_VAL_INT; |
68 | |
69 | case IIO_CHAN_INFO_CALIBBIAS: |
70 | ret = i2c_smbus_read_word_swapped(client: data->client, |
71 | TMP117_REG_TEMP_OFFSET); |
72 | if (ret < 0) |
73 | return ret; |
74 | *val = sign_extend32(value: ret, index: 15); |
75 | return IIO_VAL_INT; |
76 | |
77 | case IIO_CHAN_INFO_SCALE: |
78 | /* |
79 | * Conversion from 10s of uC to mC |
80 | * as IIO reports temperature in mC |
81 | */ |
82 | *val = TMP117_RESOLUTION_10UC / MICRODEGREE_PER_10MILLIDEGREE; |
83 | *val2 = (TMP117_RESOLUTION_10UC % |
84 | MICRODEGREE_PER_10MILLIDEGREE) * 100; |
85 | |
86 | return IIO_VAL_INT_PLUS_MICRO; |
87 | |
88 | default: |
89 | return -EINVAL; |
90 | } |
91 | } |
92 | |
93 | static int tmp117_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec |
94 | const *channel, int val, int val2, long mask) |
95 | { |
96 | struct tmp117_data *data = iio_priv(indio_dev); |
97 | s16 off; |
98 | |
99 | switch (mask) { |
100 | case IIO_CHAN_INFO_CALIBBIAS: |
101 | off = clamp_t(int, val, S16_MIN, S16_MAX); |
102 | if (off == data->calibbias) |
103 | return 0; |
104 | data->calibbias = off; |
105 | return i2c_smbus_write_word_swapped(client: data->client, |
106 | TMP117_REG_TEMP_OFFSET, value: off); |
107 | |
108 | default: |
109 | return -EINVAL; |
110 | } |
111 | } |
112 | |
113 | static const struct iio_chan_spec tmp117_channels[] = { |
114 | { |
115 | .type = IIO_TEMP, |
116 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
117 | BIT(IIO_CHAN_INFO_CALIBBIAS) | |
118 | BIT(IIO_CHAN_INFO_SCALE), |
119 | }, |
120 | }; |
121 | |
122 | static const struct iio_chan_spec tmp116_channels[] = { |
123 | { |
124 | .type = IIO_TEMP, |
125 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
126 | BIT(IIO_CHAN_INFO_SCALE), |
127 | }, |
128 | }; |
129 | |
130 | static const struct tmp11x_info tmp116_channels_info = { |
131 | .name = "tmp116" , |
132 | .channels = tmp116_channels, |
133 | .num_channels = ARRAY_SIZE(tmp116_channels) |
134 | }; |
135 | |
136 | static const struct tmp11x_info tmp117_channels_info = { |
137 | .name = "tmp117" , |
138 | .channels = tmp117_channels, |
139 | .num_channels = ARRAY_SIZE(tmp117_channels) |
140 | }; |
141 | |
142 | static const struct iio_info tmp117_info = { |
143 | .read_raw = tmp117_read_raw, |
144 | .write_raw = tmp117_write_raw, |
145 | }; |
146 | |
147 | static int tmp117_probe(struct i2c_client *client) |
148 | { |
149 | const struct tmp11x_info *match_data; |
150 | struct tmp117_data *data; |
151 | struct iio_dev *indio_dev; |
152 | int dev_id; |
153 | int ret; |
154 | |
155 | if (!i2c_check_functionality(adap: client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) |
156 | return -EOPNOTSUPP; |
157 | |
158 | ret = devm_regulator_get_enable(dev: &client->dev, id: "vcc" ); |
159 | if (ret) |
160 | return ret; |
161 | |
162 | fsleep(usecs: 1500); |
163 | |
164 | dev_id = i2c_smbus_read_word_swapped(client, TMP117_REG_DEVICE_ID); |
165 | if (dev_id < 0) |
166 | return dev_id; |
167 | |
168 | switch (dev_id) { |
169 | case TMP116_DEVICE_ID: |
170 | match_data = &tmp116_channels_info; |
171 | break; |
172 | case TMP117_DEVICE_ID: |
173 | match_data = &tmp117_channels_info; |
174 | break; |
175 | default: |
176 | dev_info(&client->dev, |
177 | "Unknown device id (0x%x), use fallback compatible\n" , |
178 | dev_id); |
179 | match_data = i2c_get_match_data(client); |
180 | } |
181 | |
182 | if (!match_data) |
183 | return dev_err_probe(dev: &client->dev, err: -ENODEV, |
184 | fmt: "Failed to identify unsupported device\n" ); |
185 | |
186 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
187 | if (!indio_dev) |
188 | return -ENOMEM; |
189 | |
190 | data = iio_priv(indio_dev); |
191 | data->client = client; |
192 | data->calibbias = 0; |
193 | |
194 | indio_dev->modes = INDIO_DIRECT_MODE; |
195 | indio_dev->info = &tmp117_info; |
196 | indio_dev->channels = match_data->channels; |
197 | indio_dev->num_channels = match_data->num_channels; |
198 | indio_dev->name = match_data->name; |
199 | |
200 | |
201 | return devm_iio_device_register(&client->dev, indio_dev); |
202 | } |
203 | |
204 | static const struct of_device_id tmp117_of_match[] = { |
205 | { .compatible = "ti,tmp116" , .data = &tmp116_channels_info }, |
206 | { .compatible = "ti,tmp117" , .data = &tmp117_channels_info }, |
207 | { } |
208 | }; |
209 | MODULE_DEVICE_TABLE(of, tmp117_of_match); |
210 | |
211 | static const struct i2c_device_id tmp117_id[] = { |
212 | { "tmp116" , (kernel_ulong_t)&tmp116_channels_info }, |
213 | { "tmp117" , (kernel_ulong_t)&tmp117_channels_info }, |
214 | { } |
215 | }; |
216 | MODULE_DEVICE_TABLE(i2c, tmp117_id); |
217 | |
218 | static struct i2c_driver tmp117_driver = { |
219 | .driver = { |
220 | .name = "tmp117" , |
221 | .of_match_table = tmp117_of_match, |
222 | }, |
223 | .probe = tmp117_probe, |
224 | .id_table = tmp117_id, |
225 | }; |
226 | module_i2c_driver(tmp117_driver); |
227 | |
228 | MODULE_AUTHOR("Puranjay Mohan <puranjay12@gmail.com>" ); |
229 | MODULE_DESCRIPTION("TI TMP117 Temperature sensor driver" ); |
230 | MODULE_LICENSE("GPL" ); |
231 | |