1 /*
2  * Copyright (c) 2023 Benjamin Cabé <benjamin@zephyrproject.org>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT seeed_hm330x
8 
9 #include <zephyr/drivers/i2c.h>
10 #include <zephyr/drivers/sensor.h>
11 #include <zephyr/init.h>
12 #include <zephyr/logging/log.h>
13 #include <zephyr/sys/__assert.h>
14 
15 LOG_MODULE_REGISTER(HM3301, CONFIG_SENSOR_LOG_LEVEL);
16 
17 #define HM330X_SELECT_COMM_CMD	0X88
18 
19 #define HM330X_PM_1_0_ATM	10
20 #define HM330X_PM_2_5_ATM	12
21 #define HM330X_PM_10_ATM	14
22 
23 #define HM330X_FRAME_LEN	29
24 
25 struct hm330x_data {
26 	uint16_t pm_1_0_sample;
27 	uint16_t pm_2_5_sample;
28 	uint16_t pm_10_sample;
29 };
30 
31 struct hm330x_config {
32 	struct i2c_dt_spec i2c;
33 };
34 
hm330x_sample_fetch(const struct device * dev,enum sensor_channel chan)35 static int hm330x_sample_fetch(const struct device *dev,
36 			       enum sensor_channel chan)
37 {
38 	struct hm330x_data *drv_data = dev->data;
39 	const struct hm330x_config *config = dev->config;
40 	uint8_t buf[HM330X_FRAME_LEN];
41 	uint8_t checksum = 0;
42 
43 	__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL);
44 
45 	if (i2c_burst_read_dt(&config->i2c,
46 			      0, buf, HM330X_FRAME_LEN) < 0) {
47 		return -EIO;
48 	}
49 
50 	drv_data->pm_1_0_sample = (buf[HM330X_PM_1_0_ATM] << 8) | buf[HM330X_PM_1_0_ATM + 1];
51 	drv_data->pm_2_5_sample = (buf[HM330X_PM_2_5_ATM] << 8) | buf[HM330X_PM_2_5_ATM + 1];
52 	drv_data->pm_10_sample = (buf[HM330X_PM_10_ATM] << 8) | buf[HM330X_PM_10_ATM + 1];
53 
54 	for (int i = 0; i < HM330X_FRAME_LEN - 1; i++) {
55 		checksum += buf[i];
56 	}
57 	if (checksum != buf[HM330X_FRAME_LEN - 1]) {
58 		LOG_ERR("Checksum error");
59 		return -EBADMSG;
60 	}
61 
62 	return 0;
63 }
64 
hm330x_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)65 static int hm330x_channel_get(const struct device *dev,
66 			      enum sensor_channel chan,
67 			      struct sensor_value *val)
68 {
69 	struct hm330x_data *drv_data = dev->data;
70 
71 	if (chan == SENSOR_CHAN_PM_1_0) {
72 		val->val1 = drv_data->pm_1_0_sample;
73 		val->val2 = 0;
74 	} else if (chan == SENSOR_CHAN_PM_2_5) {
75 		val->val1 = drv_data->pm_2_5_sample;
76 		val->val2 = 0;
77 	} else if (chan == SENSOR_CHAN_PM_10) {
78 		val->val1 = drv_data->pm_10_sample;
79 		val->val2 = 0;
80 	} else {
81 		return -ENOTSUP;
82 	}
83 
84 	return 0;
85 }
86 
87 static const struct sensor_driver_api hm330x_driver_api = {
88 	.sample_fetch = hm330x_sample_fetch,
89 	.channel_get = hm330x_channel_get
90 };
91 
hm330x_init(const struct device * dev)92 int hm330x_init(const struct device *dev)
93 {
94 	const struct hm330x_config *config = dev->config;
95 
96 	if (!i2c_is_ready_dt(&config->i2c)) {
97 		LOG_ERR("I2C bus device not ready");
98 		return -ENODEV;
99 	}
100 
101 	/** Enable I2C communications (module defaults to UART) */
102 	if (i2c_reg_write_byte_dt(&config->i2c, 0, HM330X_SELECT_COMM_CMD) < 0) {
103 		LOG_ERR("Failed to switch to I2C");
104 		return -EIO;
105 	}
106 
107 	return 0;
108 }
109 
110 #define HM330X_DEFINE(inst)									\
111 	static struct hm330x_data hm330x_data_##inst;						\
112 												\
113 	static const struct hm330x_config hm330x_config##inst = {				\
114 		.i2c = I2C_DT_SPEC_INST_GET(inst)						\
115 	};											\
116 												\
117 	SENSOR_DEVICE_DT_INST_DEFINE(inst, hm330x_init, NULL, &hm330x_data_##inst,		\
118 			      &hm330x_config##inst, POST_KERNEL,				\
119 			      CONFIG_SENSOR_INIT_PRIORITY, &hm330x_driver_api);			\
120 
121 DT_INST_FOREACH_STATUS_OKAY(HM330X_DEFINE)
122