1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2016 Marek Vasut <marex@denx.de> |
4 | * |
5 | * Driver for Hope RF HP03 digital temperature and pressure sensor. |
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(client: priv->client, HP03_ADC_WRITE_REG, value: reg); |
86 | if (ret < 0) |
87 | return ret; |
88 | |
89 | msleep(msecs: 50); /* Wait for conversion to finish */ |
90 | |
91 | return i2c_smbus_read_word_data(client: 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(map: priv->eeprom_regmap, HP03_EEPROM_CX_OFFSET, |
104 | val: coefs, val_count: 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(desc: priv->xclr_gpio, value: 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(desc: priv->xclr_gpio, value: 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(desc: priv->xclr_gpio, value: 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(lock: &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 = { |
208 | .read_raw = &hp03_read_raw, |
209 | }; |
210 | |
211 | static int hp03_probe(struct i2c_client *client) |
212 | { |
213 | const struct i2c_device_id *id = i2c_client_get_device_id(client); |
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(parent: dev, sizeof_priv: 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->name = id->name; |
228 | indio_dev->channels = hp03_channels; |
229 | indio_dev->num_channels = ARRAY_SIZE(hp03_channels); |
230 | indio_dev->info = &hp03_info; |
231 | indio_dev->modes = INDIO_DIRECT_MODE; |
232 | |
233 | priv->xclr_gpio = devm_gpiod_get_index(dev, con_id: "xclr" , idx: 0, flags: GPIOD_OUT_HIGH); |
234 | if (IS_ERR(ptr: priv->xclr_gpio)) { |
235 | dev_err(dev, "Failed to claim XCLR GPIO\n" ); |
236 | ret = PTR_ERR(ptr: priv->xclr_gpio); |
237 | return ret; |
238 | } |
239 | |
240 | /* |
241 | * Allocate another device for the on-sensor EEPROM, |
242 | * which has it's dedicated I2C address and contains |
243 | * the calibration constants for the sensor. |
244 | */ |
245 | priv->eeprom_client = devm_i2c_new_dummy_device(dev, adap: client->adapter, |
246 | HP03_EEPROM_ADDR); |
247 | if (IS_ERR(ptr: priv->eeprom_client)) { |
248 | dev_err(dev, "New EEPROM I2C device failed\n" ); |
249 | return PTR_ERR(ptr: priv->eeprom_client); |
250 | } |
251 | |
252 | priv->eeprom_regmap = devm_regmap_init_i2c(priv->eeprom_client, |
253 | &hp03_regmap_config); |
254 | if (IS_ERR(ptr: priv->eeprom_regmap)) { |
255 | dev_err(dev, "Failed to allocate EEPROM regmap\n" ); |
256 | return PTR_ERR(ptr: priv->eeprom_regmap); |
257 | } |
258 | |
259 | ret = devm_iio_device_register(dev, indio_dev); |
260 | if (ret) { |
261 | dev_err(dev, "Failed to register IIO device\n" ); |
262 | return ret; |
263 | } |
264 | |
265 | return 0; |
266 | } |
267 | |
268 | static const struct i2c_device_id hp03_id[] = { |
269 | { "hp03" , 0 }, |
270 | { }, |
271 | }; |
272 | MODULE_DEVICE_TABLE(i2c, hp03_id); |
273 | |
274 | static const struct of_device_id hp03_of_match[] = { |
275 | { .compatible = "hoperf,hp03" }, |
276 | { }, |
277 | }; |
278 | MODULE_DEVICE_TABLE(of, hp03_of_match); |
279 | |
280 | static struct i2c_driver hp03_driver = { |
281 | .driver = { |
282 | .name = "hp03" , |
283 | .of_match_table = hp03_of_match, |
284 | }, |
285 | .probe = hp03_probe, |
286 | .id_table = hp03_id, |
287 | }; |
288 | module_i2c_driver(hp03_driver); |
289 | |
290 | MODULE_AUTHOR("Marek Vasut <marex@denx.de>" ); |
291 | MODULE_DESCRIPTION("Driver for Hope RF HP03 pressure and temperature sensor" ); |
292 | MODULE_LICENSE("GPL v2" ); |
293 | |