]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
d8469e93 MV |
2 | /* |
3 | * Copyright (c) 2016 Marek Vasut <marex@denx.de> | |
4 | * | |
5 | * Driver for Hope RF HP03 digital temperature and pressure sensor. | |
d8469e93 MV |
6 | */ |
7 | ||
8 | #define pr_fmt(fmt) "hp03: " fmt | |
9 | ||
10 | #include <linux/module.h> | |
11 | #include <linux/delay.h> | |
12 | #include <linux/gpio/consumer.h> | |
13 | #include <linux/i2c.h> | |
14 | #include <linux/regmap.h> | |
15 | #include <linux/iio/iio.h> | |
16 | #include <linux/iio/sysfs.h> | |
17 | ||
18 | /* | |
19 | * The HP03 sensor occupies two fixed I2C addresses: | |
20 | * 0x50 ... read-only EEPROM with calibration data | |
21 | * 0x77 ... read-write ADC for pressure and temperature | |
22 | */ | |
23 | #define HP03_EEPROM_ADDR 0x50 | |
24 | #define HP03_ADC_ADDR 0x77 | |
25 | ||
26 | #define HP03_EEPROM_CX_OFFSET 0x10 | |
27 | #define HP03_EEPROM_AB_OFFSET 0x1e | |
28 | #define HP03_EEPROM_CD_OFFSET 0x20 | |
29 | ||
30 | #define HP03_ADC_WRITE_REG 0xff | |
31 | #define HP03_ADC_READ_REG 0xfd | |
32 | #define HP03_ADC_READ_PRESSURE 0xf0 /* D1 in datasheet */ | |
33 | #define HP03_ADC_READ_TEMP 0xe8 /* D2 in datasheet */ | |
34 | ||
35 | struct hp03_priv { | |
36 | struct i2c_client *client; | |
37 | struct mutex lock; | |
38 | struct gpio_desc *xclr_gpio; | |
39 | ||
40 | struct i2c_client *eeprom_client; | |
41 | struct regmap *eeprom_regmap; | |
42 | ||
43 | s32 pressure; /* kPa */ | |
44 | s32 temp; /* Deg. C */ | |
45 | }; | |
46 | ||
47 | static const struct iio_chan_spec hp03_channels[] = { | |
48 | { | |
49 | .type = IIO_PRESSURE, | |
50 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), | |
51 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), | |
52 | }, | |
53 | { | |
54 | .type = IIO_TEMP, | |
55 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), | |
56 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), | |
57 | }, | |
58 | }; | |
59 | ||
60 | static bool hp03_is_writeable_reg(struct device *dev, unsigned int reg) | |
61 | { | |
62 | return false; | |
63 | } | |
64 | ||
65 | static bool hp03_is_volatile_reg(struct device *dev, unsigned int reg) | |
66 | { | |
67 | return false; | |
68 | } | |
69 | ||
70 | static const struct regmap_config hp03_regmap_config = { | |
71 | .reg_bits = 8, | |
72 | .val_bits = 8, | |
73 | ||
74 | .max_register = HP03_EEPROM_CD_OFFSET + 1, | |
75 | .cache_type = REGCACHE_RBTREE, | |
76 | ||
77 | .writeable_reg = hp03_is_writeable_reg, | |
78 | .volatile_reg = hp03_is_volatile_reg, | |
79 | }; | |
80 | ||
81 | static int hp03_get_temp_pressure(struct hp03_priv *priv, const u8 reg) | |
82 | { | |
83 | int ret; | |
84 | ||
85 | ret = i2c_smbus_write_byte_data(priv->client, HP03_ADC_WRITE_REG, reg); | |
86 | if (ret < 0) | |
87 | return ret; | |
88 | ||
89 | msleep(50); /* Wait for conversion to finish */ | |
90 | ||
91 | return i2c_smbus_read_word_data(priv->client, HP03_ADC_READ_REG); | |
92 | } | |
93 | ||
94 | static int hp03_update_temp_pressure(struct hp03_priv *priv) | |
95 | { | |
96 | struct device *dev = &priv->client->dev; | |
97 | u8 coefs[18]; | |
98 | u16 cx_val[7]; | |
99 | int ab_val, d1_val, d2_val, diff_val, dut, off, sens, x; | |
100 | int i, ret; | |
101 | ||
102 | /* Sample coefficients from EEPROM */ | |
103 | ret = regmap_bulk_read(priv->eeprom_regmap, HP03_EEPROM_CX_OFFSET, | |
104 | coefs, sizeof(coefs)); | |
105 | if (ret < 0) { | |
106 | dev_err(dev, "Failed to read EEPROM (reg=%02x)\n", | |
107 | HP03_EEPROM_CX_OFFSET); | |
108 | return ret; | |
109 | } | |
110 | ||
111 | /* Sample Temperature and Pressure */ | |
112 | gpiod_set_value_cansleep(priv->xclr_gpio, 1); | |
113 | ||
114 | ret = hp03_get_temp_pressure(priv, HP03_ADC_READ_PRESSURE); | |
115 | if (ret < 0) { | |
116 | dev_err(dev, "Failed to read pressure\n"); | |
117 | goto err_adc; | |
118 | } | |
119 | d1_val = ret; | |
120 | ||
121 | ret = hp03_get_temp_pressure(priv, HP03_ADC_READ_TEMP); | |
122 | if (ret < 0) { | |
123 | dev_err(dev, "Failed to read temperature\n"); | |
124 | goto err_adc; | |
125 | } | |
126 | d2_val = ret; | |
127 | ||
128 | gpiod_set_value_cansleep(priv->xclr_gpio, 0); | |
129 | ||
130 | /* The Cx coefficients and Temp/Pressure values are MSB first. */ | |
131 | for (i = 0; i < 7; i++) | |
132 | cx_val[i] = (coefs[2 * i] << 8) | (coefs[(2 * i) + 1] << 0); | |
133 | d1_val = ((d1_val >> 8) & 0xff) | ((d1_val & 0xff) << 8); | |
134 | d2_val = ((d2_val >> 8) & 0xff) | ((d2_val & 0xff) << 8); | |
135 | ||
136 | /* Coefficient voodoo from the HP03 datasheet. */ | |
137 | if (d2_val >= cx_val[4]) | |
138 | ab_val = coefs[14]; /* A-value */ | |
139 | else | |
140 | ab_val = coefs[15]; /* B-value */ | |
141 | ||
142 | diff_val = d2_val - cx_val[4]; | |
143 | dut = (ab_val * (diff_val >> 7) * (diff_val >> 7)) >> coefs[16]; | |
144 | dut = diff_val - dut; | |
145 | ||
146 | off = (cx_val[1] + (((cx_val[3] - 1024) * dut) >> 14)) * 4; | |
147 | sens = cx_val[0] + ((cx_val[2] * dut) >> 10); | |
148 | x = ((sens * (d1_val - 7168)) >> 14) - off; | |
149 | ||
150 | priv->pressure = ((x * 100) >> 5) + (cx_val[6] * 10); | |
151 | priv->temp = 250 + ((dut * cx_val[5]) >> 16) - (dut >> coefs[17]); | |
152 | ||
153 | return 0; | |
154 | ||
155 | err_adc: | |
156 | gpiod_set_value_cansleep(priv->xclr_gpio, 0); | |
157 | return ret; | |
158 | } | |
159 | ||
160 | static int hp03_read_raw(struct iio_dev *indio_dev, | |
161 | struct iio_chan_spec const *chan, | |
162 | int *val, int *val2, long mask) | |
163 | { | |
164 | struct hp03_priv *priv = iio_priv(indio_dev); | |
165 | int ret; | |
166 | ||
167 | mutex_lock(&priv->lock); | |
168 | ret = hp03_update_temp_pressure(priv); | |
169 | mutex_unlock(&priv->lock); | |
170 | ||
171 | if (ret) | |
172 | return ret; | |
173 | ||
174 | switch (mask) { | |
175 | case IIO_CHAN_INFO_RAW: | |
176 | switch (chan->type) { | |
177 | case IIO_PRESSURE: | |
178 | *val = priv->pressure; | |
179 | return IIO_VAL_INT; | |
180 | case IIO_TEMP: | |
181 | *val = priv->temp; | |
182 | return IIO_VAL_INT; | |
183 | default: | |
184 | return -EINVAL; | |
185 | } | |
186 | break; | |
187 | case IIO_CHAN_INFO_SCALE: | |
188 | switch (chan->type) { | |
189 | case IIO_PRESSURE: | |
190 | *val = 0; | |
191 | *val2 = 1000; | |
192 | return IIO_VAL_INT_PLUS_MICRO; | |
193 | case IIO_TEMP: | |
194 | *val = 10; | |
195 | return IIO_VAL_INT; | |
196 | default: | |
197 | return -EINVAL; | |
198 | } | |
199 | break; | |
200 | default: | |
201 | return -EINVAL; | |
202 | } | |
203 | ||
204 | return -EINVAL; | |
205 | } | |
206 | ||
207 | static const struct iio_info hp03_info = { | |
d8469e93 MV |
208 | .read_raw = &hp03_read_raw, |
209 | }; | |
210 | ||
211 | static int hp03_probe(struct i2c_client *client, | |
212 | const struct i2c_device_id *id) | |
213 | { | |
214 | struct device *dev = &client->dev; | |
215 | struct iio_dev *indio_dev; | |
216 | struct hp03_priv *priv; | |
217 | int ret; | |
218 | ||
219 | indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); | |
220 | if (!indio_dev) | |
221 | return -ENOMEM; | |
222 | ||
223 | priv = iio_priv(indio_dev); | |
224 | priv->client = client; | |
225 | mutex_init(&priv->lock); | |
226 | ||
227 | indio_dev->dev.parent = dev; | |
228 | indio_dev->name = id->name; | |
229 | indio_dev->channels = hp03_channels; | |
230 | indio_dev->num_channels = ARRAY_SIZE(hp03_channels); | |
231 | indio_dev->info = &hp03_info; | |
232 | indio_dev->modes = INDIO_DIRECT_MODE; | |
233 | ||
234 | priv->xclr_gpio = devm_gpiod_get_index(dev, "xclr", 0, GPIOD_OUT_HIGH); | |
235 | if (IS_ERR(priv->xclr_gpio)) { | |
236 | dev_err(dev, "Failed to claim XCLR GPIO\n"); | |
237 | ret = PTR_ERR(priv->xclr_gpio); | |
238 | return ret; | |
239 | } | |
240 | ||
241 | /* | |
242 | * Allocate another device for the on-sensor EEPROM, | |
243 | * which has it's dedicated I2C address and contains | |
244 | * the calibration constants for the sensor. | |
245 | */ | |
246 | priv->eeprom_client = i2c_new_dummy(client->adapter, HP03_EEPROM_ADDR); | |
247 | if (!priv->eeprom_client) { | |
248 | dev_err(dev, "New EEPROM I2C device failed\n"); | |
249 | return -ENODEV; | |
250 | } | |
251 | ||
252 | priv->eeprom_regmap = regmap_init_i2c(priv->eeprom_client, | |
253 | &hp03_regmap_config); | |
254 | if (IS_ERR(priv->eeprom_regmap)) { | |
255 | dev_err(dev, "Failed to allocate EEPROM regmap\n"); | |
256 | ret = PTR_ERR(priv->eeprom_regmap); | |
257 | goto err_cleanup_eeprom_client; | |
258 | } | |
259 | ||
260 | ret = iio_device_register(indio_dev); | |
261 | if (ret) { | |
262 | dev_err(dev, "Failed to register IIO device\n"); | |
263 | goto err_cleanup_eeprom_regmap; | |
264 | } | |
265 | ||
266 | i2c_set_clientdata(client, indio_dev); | |
267 | ||
268 | return 0; | |
269 | ||
270 | err_cleanup_eeprom_regmap: | |
271 | regmap_exit(priv->eeprom_regmap); | |
272 | ||
273 | err_cleanup_eeprom_client: | |
274 | i2c_unregister_device(priv->eeprom_client); | |
275 | return ret; | |
276 | } | |
277 | ||
278 | static int hp03_remove(struct i2c_client *client) | |
279 | { | |
280 | struct iio_dev *indio_dev = i2c_get_clientdata(client); | |
281 | struct hp03_priv *priv = iio_priv(indio_dev); | |
282 | ||
283 | iio_device_unregister(indio_dev); | |
284 | regmap_exit(priv->eeprom_regmap); | |
285 | i2c_unregister_device(priv->eeprom_client); | |
286 | ||
287 | return 0; | |
288 | } | |
289 | ||
290 | static const struct i2c_device_id hp03_id[] = { | |
291 | { "hp03", 0 }, | |
292 | { }, | |
293 | }; | |
294 | MODULE_DEVICE_TABLE(i2c, hp03_id); | |
295 | ||
72fc0270 JMC |
296 | static const struct of_device_id hp03_of_match[] = { |
297 | { .compatible = "hoperf,hp03" }, | |
298 | { }, | |
299 | }; | |
300 | MODULE_DEVICE_TABLE(of, hp03_of_match); | |
301 | ||
d8469e93 MV |
302 | static struct i2c_driver hp03_driver = { |
303 | .driver = { | |
304 | .name = "hp03", | |
72fc0270 | 305 | .of_match_table = hp03_of_match, |
d8469e93 MV |
306 | }, |
307 | .probe = hp03_probe, | |
308 | .remove = hp03_remove, | |
309 | .id_table = hp03_id, | |
310 | }; | |
311 | module_i2c_driver(hp03_driver); | |
312 | ||
313 | MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); | |
314 | MODULE_DESCRIPTION("Driver for Hope RF HP03 pressure and temperature sensor"); | |
315 | MODULE_LICENSE("GPL v2"); |