1 /*
2 * Copyright (c) 2021 Vestas Wind Systems A/S
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT lm77
8
9 #include <zephyr/device.h>
10 #include <zephyr/drivers/gpio.h>
11 #include <zephyr/drivers/i2c.h>
12 #include <zephyr/drivers/sensor.h>
13 #include <zephyr/logging/log.h>
14 #include <zephyr/pm/device.h>
15 #include <zephyr/sys/byteorder.h>
16
17 LOG_MODULE_REGISTER(lm77, CONFIG_SENSOR_LOG_LEVEL);
18
19 /*
20 * Only compile in trigger support if enabled in Kconfig and at least one
21 * enabled lm77 devicetree node has the int-gpios property.
22 */
23 #define LM77_TRIGGER_SUPPORT \
24 (CONFIG_LM77_TRIGGER && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios))
25
26 /* LM77 registers */
27 #define LM77_REG_TEMP 0x00U
28 #define LM77_REG_CONFIG 0x01U
29 #define LM77_REG_THYST 0x02U
30 #define LM77_REG_TCRIT 0x03U
31 #define LM77_REG_TLOW 0x04U
32 #define LM77_REG_THIGH 0x05U
33
34 /* LM77 configuration register bits */
35 union lm77_reg_config {
36 uint8_t reg;
37 struct {
38 uint8_t shutdown : 1;
39 uint8_t int_mode : 1;
40 uint8_t tcrita_pol : 1;
41 uint8_t int_pol : 1;
42 uint8_t fault_queue : 1;
43 uint8_t reserved : 3;
44 } __packed;
45 };
46
47 struct lm77_config {
48 struct i2c_dt_spec i2c;
49 union lm77_reg_config config_dt;
50 #if LM77_TRIGGER_SUPPORT
51 struct gpio_dt_spec int_gpio;
52 #endif /* LM77_TRIGGER_SUPPORT */
53 };
54
55 struct lm77_data {
56 int16_t temp;
57 #if LM77_TRIGGER_SUPPORT
58 const struct device *dev;
59 struct k_work_q workq;
60 struct k_work work;
61 struct gpio_callback int_gpio_cb;
62 const struct sensor_trigger *trigger;
63 sensor_trigger_handler_t trigger_handler;
64
65 K_KERNEL_STACK_MEMBER(stack, CONFIG_LM77_TRIGGER_THREAD_STACK_SIZE);
66 #endif /* LM77_TRIGGER_SUPPORT */
67 };
68
lm77_write_config(const struct device * dev,uint8_t value)69 static int lm77_write_config(const struct device *dev, uint8_t value)
70 {
71 const struct lm77_config *config = dev->config;
72 uint8_t buf[2] = { LM77_REG_CONFIG, value };
73
74 return i2c_write_dt(&config->i2c, buf, sizeof(buf));
75 }
76
lm77_read_temp(const struct device * dev,uint8_t reg,int16_t * value)77 static int lm77_read_temp(const struct device *dev, uint8_t reg, int16_t *value)
78 {
79 const struct lm77_config *config = dev->config;
80 uint8_t buf[2];
81 int err;
82
83 err = i2c_write_read_dt(&config->i2c, ®, sizeof(reg), &buf, sizeof(buf));
84 if (err < 0) {
85 LOG_ERR("failed to read temperature register 0x%02x (err %d)", reg, err);
86 return err;
87 }
88
89 *value = sys_get_be16(buf);
90
91 return 0;
92 }
93
lm77_write_temp(const struct device * dev,uint8_t reg,int16_t value)94 static int lm77_write_temp(const struct device *dev, uint8_t reg, int16_t value)
95 {
96 const struct lm77_config *config = dev->config;
97 uint8_t buf[3] = { reg, value >> 8, value };
98
99 return i2c_write_dt(&config->i2c, buf, sizeof(buf));
100 }
101
lm77_sensor_value_to_temp(const struct sensor_value * val,int16_t * temp)102 static void lm77_sensor_value_to_temp(const struct sensor_value *val, int16_t *temp)
103 {
104 /* Integer part in degrees Celsius (LSB = 0.5 degrees Celsius) */
105 *temp = val->val1 << 1;
106
107 /* Fractional part in micro degrees Celsius */
108 *temp += (val->val2 * 2) / 1000000;
109
110 /* Shift up to include "status" bits */
111 *temp <<= 3;
112 }
113
lm77_temp_to_sensor_value(int16_t temp,struct sensor_value * val)114 static void lm77_temp_to_sensor_value(int16_t temp, struct sensor_value *val)
115 {
116 /* Shift down to remove "status" bits (LSB = 0.5 degrees Celsius) */
117 temp = (temp >> 3) * 5;
118
119 /* Integer part in degrees Celsius */
120 val->val1 = temp / 10;
121
122 /* Fractional part in micro degrees Celsius */
123 val->val2 = temp % 10;
124 val->val2 *= 100000;
125 }
126
lm77_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)127 static int lm77_attr_set(const struct device *dev, enum sensor_channel chan,
128 enum sensor_attribute attr, const struct sensor_value *val)
129 {
130 int16_t temp = 0;
131 uint8_t reg;
132 int err;
133
134 __ASSERT_NO_MSG(val != NULL);
135
136 if (chan != SENSOR_CHAN_AMBIENT_TEMP) {
137 return -ENOTSUP;
138 }
139
140 switch (attr) {
141 case SENSOR_ATTR_LOWER_THRESH:
142 reg = LM77_REG_TLOW;
143 break;
144 case SENSOR_ATTR_UPPER_THRESH:
145 reg = LM77_REG_THIGH;
146 break;
147 case SENSOR_ATTR_ALERT:
148 reg = LM77_REG_TCRIT;
149 break;
150 case SENSOR_ATTR_HYSTERESIS:
151 reg = LM77_REG_THYST;
152 break;
153 default:
154 return -ENOTSUP;
155 }
156
157 lm77_sensor_value_to_temp(val, &temp);
158
159 err = lm77_write_temp(dev, reg, temp);
160 if (err < 0) {
161 LOG_ERR("failed to write register 0x%02x (err %d)", reg, err);
162 return err;
163 }
164
165 return 0;
166 }
167
lm77_attr_get(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,struct sensor_value * val)168 static int lm77_attr_get(const struct device *dev, enum sensor_channel chan,
169 enum sensor_attribute attr, struct sensor_value *val)
170 {
171 int16_t temp;
172 uint8_t reg;
173 int err;
174
175 __ASSERT_NO_MSG(val != NULL);
176
177 if (chan != SENSOR_CHAN_AMBIENT_TEMP) {
178 return -ENOTSUP;
179 }
180
181 switch (attr) {
182 case SENSOR_ATTR_LOWER_THRESH:
183 reg = LM77_REG_TLOW;
184 break;
185 case SENSOR_ATTR_UPPER_THRESH:
186 reg = LM77_REG_THIGH;
187 break;
188 case SENSOR_ATTR_ALERT:
189 reg = LM77_REG_TCRIT;
190 break;
191 case SENSOR_ATTR_HYSTERESIS:
192 reg = LM77_REG_THYST;
193 break;
194 default:
195 return -ENOTSUP;
196 }
197
198 err = lm77_read_temp(dev, reg, &temp);
199 if (err < 0) {
200 LOG_ERR("failed to read register 0x%02x (err %d)", reg, err);
201 return err;
202 }
203
204 lm77_temp_to_sensor_value(temp, val);
205
206 return 0;
207 }
208
209 #if LM77_TRIGGER_SUPPORT
lm77_trigger_set(const struct device * dev,const struct sensor_trigger * trig,sensor_trigger_handler_t handler)210 static int lm77_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
211 sensor_trigger_handler_t handler)
212 {
213 const struct lm77_config *config = dev->config;
214 struct lm77_data *data = dev->data;
215 gpio_flags_t flags;
216 int err;
217
218 __ASSERT_NO_MSG(trig != NULL);
219
220 if (trig->type != SENSOR_TRIG_THRESHOLD || trig->chan != SENSOR_CHAN_AMBIENT_TEMP ||
221 config->int_gpio.port == NULL) {
222 return -ENOTSUP;
223 }
224
225 if (handler != NULL) {
226 flags = GPIO_INT_EDGE_TO_ACTIVE;
227 } else {
228 flags = GPIO_INT_DISABLE;
229 }
230
231 err = gpio_pin_interrupt_configure_dt(&config->int_gpio, flags);
232 if (err < 0) {
233 LOG_ERR("failed to configure INT GPIO IRQ (err %d)", err);
234 return err;
235 }
236
237 data->trigger = trig;
238 data->trigger_handler = handler;
239
240 return 0;
241 }
242
lm77_trigger_work_handler(struct k_work * item)243 static void lm77_trigger_work_handler(struct k_work *item)
244 {
245 struct lm77_data *data = CONTAINER_OF(item, struct lm77_data, work);
246 sensor_trigger_handler_t handler = data->trigger_handler;
247
248 if (handler != NULL) {
249 handler(data->dev, (struct sensor_trigger *)data->trigger);
250 }
251 }
252
lm77_int_gpio_callback_handler(const struct device * port,struct gpio_callback * cb,gpio_port_pins_t pins)253 static void lm77_int_gpio_callback_handler(const struct device *port,
254 struct gpio_callback *cb,
255 gpio_port_pins_t pins)
256 {
257 struct lm77_data *data = CONTAINER_OF(cb, struct lm77_data, int_gpio_cb);
258
259 ARG_UNUSED(port);
260 ARG_UNUSED(pins);
261
262 k_work_submit_to_queue(&data->workq, &data->work);
263 }
264 #endif /* LM77_TRIGGER_SUPPORT */
265
lm77_sample_fetch(const struct device * dev,enum sensor_channel chan)266 static int lm77_sample_fetch(const struct device *dev, enum sensor_channel chan)
267 {
268 struct lm77_data *data = dev->data;
269 int16_t temp;
270 int err;
271
272 if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP) {
273 return -ENOTSUP;
274 }
275
276 err = lm77_read_temp(dev, LM77_REG_TEMP, &temp);
277 if (err < 0) {
278 LOG_ERR("failed to read temperature (err %d)", err);
279 return err;
280 }
281
282 data->temp = temp;
283
284 return 0;
285 }
286
lm77_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)287 static int lm77_channel_get(const struct device *dev, enum sensor_channel chan,
288 struct sensor_value *val)
289 {
290 struct lm77_data *data = dev->data;
291
292 if (chan != SENSOR_CHAN_AMBIENT_TEMP) {
293 return -ENOTSUP;
294 }
295
296 lm77_temp_to_sensor_value(data->temp, val);
297
298 return 0;
299 }
300
301 static const struct sensor_driver_api lm77_driver_api = {
302 .attr_set = lm77_attr_set,
303 .attr_get = lm77_attr_get,
304 #if LM77_TRIGGER_SUPPORT
305 .trigger_set = lm77_trigger_set,
306 #endif /* LM77_TRIGGER_SUPPORT */
307 .sample_fetch = lm77_sample_fetch,
308 .channel_get = lm77_channel_get,
309 };
310
lm77_init(const struct device * dev)311 static int lm77_init(const struct device *dev)
312 {
313 const struct lm77_config *config = dev->config;
314 int err;
315 #if LM77_TRIGGER_SUPPORT
316 struct lm77_data *data = dev->data;
317 #endif /* LM77_TRIGGER_SUPPORT */
318
319 if (!device_is_ready(config->i2c.bus)) {
320 LOG_ERR("I2c bus not ready");
321 return -EINVAL;
322 }
323
324 err = lm77_write_config(dev, config->config_dt.reg);
325 if (err < 0) {
326 LOG_ERR("failed to write configuration (err %d)", err);
327 return err;
328 }
329
330 #if LM77_TRIGGER_SUPPORT
331 data->dev = dev;
332 k_work_queue_start(&data->workq, data->stack, K_THREAD_STACK_SIZEOF(data->stack),
333 CONFIG_LM77_TRIGGER_THREAD_PRIO, NULL);
334 k_thread_name_set(&data->workq.thread, "lm77_trigger");
335 k_work_init(&data->work, lm77_trigger_work_handler);
336
337 if (config->int_gpio.port != NULL) {
338 if (!gpio_is_ready_dt(&config->int_gpio)) {
339 LOG_ERR("INT GPIO not ready");
340 return -EINVAL;
341 }
342
343 err = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
344 if (err < 0) {
345 LOG_ERR("failed to configure INT GPIO (err %d)", err);
346 return err;
347 }
348
349 gpio_init_callback(&data->int_gpio_cb, lm77_int_gpio_callback_handler,
350 BIT(config->int_gpio.pin));
351
352 err = gpio_add_callback(config->int_gpio.port, &data->int_gpio_cb);
353 if (err < 0) {
354 LOG_ERR("failed to add INT GPIO callback (err %d)", err);
355 return err;
356 }
357 }
358 #endif /* LM77_TRIGGER_SUPPORT */
359
360 return 0;
361 }
362
363 #ifdef CONFIG_PM_DEVICE
lm77_pm_action(const struct device * dev,enum pm_device_action action)364 static int lm77_pm_action(const struct device *dev,
365 enum pm_device_action action)
366 {
367 const struct lm77_config *config = dev->config;
368 union lm77_reg_config creg = config->config_dt;
369 int err;
370
371 switch (action) {
372 case PM_DEVICE_ACTION_SUSPEND:
373 creg.shutdown = 1;
374 break;
375 case PM_DEVICE_ACTION_RESUME:
376 creg.shutdown = 0;
377 break;
378 default:
379 return -ENOTSUP;
380 }
381
382 err = lm77_write_config(dev, creg.reg);
383 if (err < 0) {
384 LOG_ERR("failed to write configuration (err %d)", err);
385 return err;
386 }
387
388 return 0;
389 }
390 #endif /* CONFIG_PM_DEVICE */
391
392 #if LM77_TRIGGER_SUPPORT
393 #define LM77_INT_GPIO_INIT(n) .int_gpio = GPIO_DT_SPEC_INST_GET_OR(n, int_gpios, { 0 })
394 #else /* LM77_TRIGGER_SUPPORT */
395 #define LM77_INT_GPIO_INIT(n)
396 #endif /* ! LM77_TRIGGER_SUPPORT */
397
398 #define LM77_INIT(n) \
399 static struct lm77_data lm77_data_##n; \
400 \
401 static const struct lm77_config lm77_config_##n = { \
402 .i2c = I2C_DT_SPEC_INST_GET(n), \
403 .config_dt = { \
404 .shutdown = 0, \
405 .int_mode = DT_INST_NODE_HAS_PROP(n, int_gpios), \
406 .tcrita_pol = DT_INST_PROP(n, tcrita_inverted), \
407 .int_pol = DT_INST_PROP(n, int_inverted), \
408 .fault_queue = DT_INST_PROP(n, enable_fault_queue), \
409 .reserved = 0, \
410 }, \
411 LM77_INT_GPIO_INIT(n) \
412 }; \
413 \
414 PM_DEVICE_DT_INST_DEFINE(n, lm77_pm_action); \
415 \
416 SENSOR_DEVICE_DT_INST_DEFINE(n, lm77_init, \
417 PM_DEVICE_DT_INST_GET(n), \
418 &lm77_data_##n, \
419 &lm77_config_##n, POST_KERNEL, \
420 CONFIG_SENSOR_INIT_PRIORITY, \
421 &lm77_driver_api);
422
423 DT_INST_FOREACH_STATUS_OKAY(LM77_INIT)
424