1 /* Driver for the SHT21 Relative Humidity and Temperature Sensor */
2
3 #include <minix/ds.h>
4 #include <minix/drivers.h>
5 #include <minix/i2c.h>
6 #include <minix/i2cdriver.h>
7 #include <minix/chardriver.h>
8 #include <minix/log.h>
9
10 #include <time.h>
11
12 /*
13 * Device Commands
14 */
15
16 /*
17 * The trigger commands start a measurement. 'Hold' ties up the bus while the
18 * measurement is being performed while 'no hold' requires the driver to poll
19 * the chip until the data is ready. Hold is faster and requires less message
20 * passing while no hold frees up the bus while the measurement is in progress.
21 * The worst case conversion times are 85 ms for temperature and 29 ms for
22 * humidity. Typical conversion times are about 75% of the worst case times.
23 *
24 * The driver uses the 'hold' versions of the trigger commands.
25 */
26 #define CMD_TRIG_T_HOLD 0xe3
27 #define CMD_TRIG_RH_HOLD 0xe5
28 #define CMD_TRIG_T_NOHOLD 0xf3
29 #define CMD_TRIG_RH_NOHOLD 0xf5
30
31 /* Read and write the user register contents */
32 #define CMD_WR_USR_REG 0xe6
33 #define CMD_RD_USR_REG 0xe7
34
35 /* Resets the chip */
36 #define CMD_SOFT_RESET 0xfe
37
38 /* Status bits included in the measurement need to be masked in calculation */
39 #define STATUS_BITS_MASK 0x0003
40
41 /*
42 * The user register has some reserved bits that the device changes over
43 * time. The driver must preserve the value of those bits when writing to
44 * the user register.
45 */
46 #define USR_REG_RESERVED_MASK ((1<<3)|(1<<4)|(1<<5))
47
48 /* End of Battery flag is set when the voltage drops below 2.25V. */
49 #define USR_REG_EOB_MASK (1<<6)
50
51 /* When powered up and communicating, the register should have only the
52 * 'Disable OTP Reload' bit set
53 */
54 #define EXPECTED_PWR_UP_TEST_VAL (1<<1)
55
56 /* Define some constants for the different sensor types on the chip. */
57 enum sht21_sensors
58 { SHT21_T, SHT21_RH };
59
60 /* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
61 static struct log log = {
62 .name = "sht21",
63 .log_level = LEVEL_INFO,
64 .log_func = default_log
65 };
66
67 /* device slave address is fixed at 0x40 */
68 static i2c_addr_t valid_addrs[2] = {
69 0x40, 0x00
70 };
71
72 /* Buffer to store output string returned when reading from device file. */
73 #define BUFFER_LEN 64
74 char buffer[BUFFER_LEN + 1];
75
76 /* the bus that this device is on (counting starting at 1) */
77 static uint32_t bus;
78
79 /* slave address of the device */
80 static i2c_addr_t address;
81
82 /* endpoint for the driver for the bus itself. */
83 static endpoint_t bus_endpoint;
84
85 /* Sampling causes self-heating. To limit the self-heating to < 0.1C, the
86 * data sheet suggests limiting sampling to 2 samples per second. Since
87 * the driver samples temperature and relative humidity at the same time,
88 * it's measure function does at most 1 pair of samples per second. It uses
89 * this timestamp to see if a measurement was taken less than 1 second ago.
90 */
91 static time_t last_sample_time = 0;
92
93 /*
94 * Cache temperature and relative humidity readings. These values are returned
95 * when the last_sample_time == current_time to keep the chip activity below
96 * 10% to help prevent self-heating.
97 */
98 static int32_t cached_t = 0.0;
99 static int32_t cached_rh = 0.0;
100
101 /*
102 * An 8-bit CRC is used to validate the readings.
103 */
104 #define CRC8_POLYNOMIAL 0x131
105 #define CRC8_INITIAL_CRC 0x00
106
107 /* main driver functions */
108 static int sht21_init(void);
109 static int sensor_read(enum sht21_sensors sensor, int32_t * measurement);
110 static int measure(void);
111
112 /* CRC functions */
113 static uint8_t crc8(uint8_t crc, uint8_t byte);
114 static int checksum(uint8_t * bytes, int nbytes, uint8_t expected_crc);
115
116 /* libchardriver callbacks */
117 static ssize_t sht21_read(devminor_t minor, u64_t position, endpoint_t endpt,
118 cp_grant_id_t grant, size_t size, int flags, cdev_id_t id);
119 static void sht21_other(message * m, int ipc_status);
120
121 /* Entry points to this driver from libchardriver. */
122 static struct chardriver sht21_tab = {
123 .cdr_read = sht21_read,
124 .cdr_other = sht21_other
125 };
126
127 /*
128 * Performs a soft reset and reads the contents of the user register to ensure
129 * that the chip is in a good state and working properly.
130 */
131 static int
sht21_init(void)132 sht21_init(void)
133 {
134 int r;
135 uint8_t usr_reg_val;
136
137 /* Perform a soft-reset */
138 r = i2creg_raw_write8(bus_endpoint, address, CMD_SOFT_RESET);
139 if (r != OK) {
140 return -1;
141 }
142
143 /* soft reset takes up to 15 ms to complete. */
144 micro_delay(15000);
145
146 log_debug(&log, "Soft Reset Complete\n");
147
148 r = i2creg_read8(bus_endpoint, address, CMD_RD_USR_REG, &usr_reg_val);
149 if (r != OK) {
150 return -1;
151 }
152
153 /* Check for End of Battery flag. */
154 if ((usr_reg_val & USR_REG_EOB_MASK) == USR_REG_EOB_MASK) {
155 log_warn(&log, "End of Battery Alarm\n");
156 return -1;
157 }
158
159 /* Check that the non-reserved bits are in the default state. */
160 if ((usr_reg_val & ~USR_REG_RESERVED_MASK) != EXPECTED_PWR_UP_TEST_VAL) {
161 log_warn(&log, "USR_REG has non-default values after reset\n");
162 log_warn(&log, "Expected 0x%x | Actual 0x%x",
163 EXPECTED_PWR_UP_TEST_VAL,
164 (usr_reg_val & ~USR_REG_RESERVED_MASK));
165 return -1;
166 }
167
168 return OK;
169 }
170
171 /*
172 * Read from the sensor, check the CRC, convert the ADC value into the final
173 * representation, and store the result in measurement.
174 */
175 static int
sensor_read(enum sht21_sensors sensor,int32_t * measurement)176 sensor_read(enum sht21_sensors sensor, int32_t * measurement)
177 {
178 int r;
179 uint8_t cmd;
180 uint16_t val;
181 uint8_t bytes[2];
182 uint32_t val32;
183 uint8_t expected_crc;
184
185 switch (sensor) {
186 case SHT21_T:
187 cmd = CMD_TRIG_T_HOLD;
188 break;
189 case SHT21_RH:
190 cmd = CMD_TRIG_RH_HOLD;
191 break;
192 default:
193 log_warn(&log, "sensor_read() called with bad sensor type.\n");
194 return -1;
195 }
196
197 if (measurement == NULL) {
198 log_warn(&log, "sensor_read() called with NULL pointer\n");
199 return -1;
200 }
201
202 r = i2creg_read24(bus_endpoint, address, cmd, &val32);
203 if (r != OK) {
204 log_warn(&log, "sensor_read() failed (r=%d)\n", r);
205 return -1;
206 }
207
208 expected_crc = val32 & 0xff;
209 val = (val32 >> 8) & 0xffff;
210
211 bytes[0] = (val >> 8) & 0xff;
212 bytes[1] = val & 0xff;
213
214 r = checksum(bytes, 2, expected_crc);
215 if (r != OK) {
216 return -1;
217 }
218
219 val &= ~STATUS_BITS_MASK; /* clear status bits */
220
221 log_debug(&log, "Read VAL:0x%x CRC:0x%x\n", val, expected_crc);
222
223 /* Convert the ADC value to the actual value. */
224 if (cmd == CMD_TRIG_T_HOLD) {
225 *measurement = (int32_t)
226 ((-46.85 + ((175.72 / 65536) * ((float) val))) * 1000.0);
227 log_debug(&log, "Measured Temperature %d mC\n", *measurement);
228 } else if (cmd == CMD_TRIG_RH_HOLD) {
229 *measurement =
230 (int32_t) ((-6.0 +
231 ((125.0 / 65536) * ((float) val))) * 1000.0);
232 log_debug(&log, "Measured Humidity %d m%%\n", *measurement);
233 }
234
235 return OK;
236 }
237
238 static int
measure(void)239 measure(void)
240 {
241 int r;
242 time_t sample_time;
243 int32_t t, rh;
244
245 log_debug(&log, "Taking a measurement...");
246
247 sample_time = time(NULL);
248 if (sample_time == last_sample_time) {
249 log_debug(&log, "measure() called too soon, using cache.\n");
250 return OK;
251 }
252
253 r = sensor_read(SHT21_T, &t);
254 if (r != OK) {
255 return -1;
256 }
257
258 r = sensor_read(SHT21_RH, &rh);
259 if (r != OK) {
260 return -1;
261 }
262
263 /* save measured values */
264 cached_t = t;
265 cached_rh = rh;
266 last_sample_time = time(NULL);
267
268 log_debug(&log, "Measurement completed\n");
269
270 return OK;
271 }
272
273 /*
274 * Return an updated checksum for the given crc and byte.
275 */
276 static uint8_t
crc8(uint8_t crc,uint8_t byte)277 crc8(uint8_t crc, uint8_t byte)
278 {
279 int i;
280
281 crc ^= byte;
282
283 for (i = 0; i < 8; i++) {
284
285 if ((crc & 0x80) == 0x80) {
286 crc = (crc << 1) ^ CRC8_POLYNOMIAL;
287 } else {
288 crc <<= 1;
289 }
290 }
291
292 return crc;
293 }
294
295 /*
296 * Compute the CRC of an array of bytes and compare it to expected_crc.
297 * If the computed CRC matches expected_crc, then return OK, otherwise EINVAL.
298 */
299 static int
checksum(uint8_t * bytes,int nbytes,uint8_t expected_crc)300 checksum(uint8_t * bytes, int nbytes, uint8_t expected_crc)
301 {
302 int i;
303 uint8_t crc;
304
305 crc = CRC8_INITIAL_CRC;
306
307 log_debug(&log, "Checking CRC\n");
308
309 for (i = 0; i < nbytes; i++) {
310 crc = crc8(crc, bytes[i]);
311 }
312
313 if (crc == expected_crc) {
314 log_debug(&log, "CRC OK\n");
315 return OK;
316 } else {
317 log_warn(&log,
318 "Bad CRC -- Computed CRC: 0x%x | Expected CRC: 0x%x\n",
319 crc, expected_crc);
320 return EINVAL;
321 }
322 }
323
324 static ssize_t
sht21_read(devminor_t UNUSED (minor),u64_t position,endpoint_t endpt,cp_grant_id_t grant,size_t size,int UNUSED (flags),cdev_id_t UNUSED (id))325 sht21_read(devminor_t UNUSED(minor), u64_t position, endpoint_t endpt,
326 cp_grant_id_t grant, size_t size, int UNUSED(flags), cdev_id_t UNUSED(id))
327 {
328 u64_t dev_size;
329 int bytes, r;
330
331 r = measure();
332 if (r != OK) {
333 return EIO;
334 }
335
336 memset(buffer, '\0', BUFFER_LEN + 1);
337 snprintf(buffer, BUFFER_LEN, "%-16s: %d.%03d\n%-16s: %d.%03d\n",
338 "TEMPERATURE", cached_t / 1000, cached_t % 1000, "HUMIDITY",
339 cached_rh / 1000, cached_rh % 1000);
340
341 log_trace(&log, "%s", buffer);
342
343 dev_size = (u64_t)strlen(buffer);
344 if (position >= dev_size) return 0;
345 if (position + size > dev_size)
346 size = (size_t)(dev_size - position);
347
348 r = sys_safecopyto(endpt, grant, 0,
349 (vir_bytes)(buffer + (size_t)position), size);
350
351 return (r != OK) ? r : size;
352 }
353
354 static void
sht21_other(message * m,int ipc_status)355 sht21_other(message * m, int ipc_status)
356 {
357 int r;
358
359 if (is_ipc_notify(ipc_status)) {
360 if (m->m_source == DS_PROC_NR) {
361 log_debug(&log,
362 "bus driver changed state, update endpoint\n");
363 i2cdriver_handle_bus_update(&bus_endpoint, bus,
364 address);
365 }
366 return;
367 }
368
369 log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
370 }
371
372 static int
sef_cb_lu_state_save(int UNUSED (result),int UNUSED (flags))373 sef_cb_lu_state_save(int UNUSED(result), int UNUSED(flags))
374 {
375 ds_publish_u32("bus", bus, DSF_OVERWRITE);
376 ds_publish_u32("address", address, DSF_OVERWRITE);
377 return OK;
378 }
379
380 static int
lu_state_restore(void)381 lu_state_restore(void)
382 {
383 /* Restore the state. */
384 u32_t value;
385
386 ds_retrieve_u32("bus", &value);
387 ds_delete_u32("bus");
388 bus = (int) value;
389
390 ds_retrieve_u32("address", &value);
391 ds_delete_u32("address");
392 address = (int) value;
393
394 return OK;
395 }
396
397 static int
sef_cb_init(int type,sef_init_info_t * UNUSED (info))398 sef_cb_init(int type, sef_init_info_t * UNUSED(info))
399 {
400 int r;
401
402 if (type == SEF_INIT_LU) {
403 /* Restore the state. */
404 lu_state_restore();
405 }
406
407 /* look-up the endpoint for the bus driver */
408 bus_endpoint = i2cdriver_bus_endpoint(bus);
409 if (bus_endpoint == 0) {
410 log_warn(&log, "Couldn't find bus driver.\n");
411 return EXIT_FAILURE;
412 }
413
414 /* claim the device */
415 r = i2cdriver_reserve_device(bus_endpoint, address);
416 if (r != OK) {
417 log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
418 address, r);
419 return EXIT_FAILURE;
420 }
421
422 r = sht21_init();
423 if (r != OK) {
424 log_warn(&log, "Device Init Failed\n");
425 return EXIT_FAILURE;
426 }
427
428 if (type != SEF_INIT_LU) {
429
430 /* sign up for updates about the i2c bus going down/up */
431 r = i2cdriver_subscribe_bus_updates(bus);
432 if (r != OK) {
433 log_warn(&log, "Couldn't subscribe to bus updates\n");
434 return EXIT_FAILURE;
435 }
436
437 i2cdriver_announce(bus);
438 log_debug(&log, "announced\n");
439 }
440
441 return OK;
442 }
443
444 static void
sef_local_startup(void)445 sef_local_startup(void)
446 {
447 /*
448 * Register init callbacks. Use the same function for all event types
449 */
450 sef_setcb_init_fresh(sef_cb_init);
451 sef_setcb_init_lu(sef_cb_init);
452 sef_setcb_init_restart(sef_cb_init);
453
454 /*
455 * Register live update callbacks.
456 */
457 sef_setcb_lu_state_save(sef_cb_lu_state_save);
458
459 /* Let SEF perform startup. */
460 sef_startup();
461 }
462
463 int
main(int argc,char * argv[])464 main(int argc, char *argv[])
465 {
466 int r;
467
468 env_setargs(argc, argv);
469
470 r = i2cdriver_env_parse(&bus, &address, valid_addrs);
471 if (r < 0) {
472 log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n");
473 log_warn(&log, "Example -args 'bus=1 address=0x40'\n");
474 return EXIT_FAILURE;
475 } else if (r > 0) {
476 log_warn(&log,
477 "Invalid slave address for device, expecting 0x40\n");
478 return EXIT_FAILURE;
479 }
480
481 sef_local_startup();
482
483 chardriver_task(&sht21_tab);
484
485 return 0;
486 }
487