1 /*
2  * Copyright (c) 2023 Google LLC
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 
6 #define DT_DRV_COMPAT asahi_kasei_akm09918c
7 
8 #include <zephyr/device.h>
9 #include <zephyr/drivers/emul.h>
10 #include <zephyr/drivers/emul_sensor.h>
11 #include <zephyr/drivers/i2c.h>
12 #include <zephyr/drivers/i2c_emul.h>
13 #include <zephyr/logging/log.h>
14 #include <zephyr/sys/util.h>
15 
16 #include "akm09918c.h"
17 #include "akm09918c_emul.h"
18 #include "akm09918c_reg.h"
19 
20 LOG_MODULE_DECLARE(AKM09918C, CONFIG_SENSOR_LOG_LEVEL);
21 
22 #define NUM_REGS AKM09918C_REG_TS2
23 
24 struct akm09918c_emul_data {
25 	uint8_t reg[NUM_REGS];
26 };
27 
28 struct akm09918c_emul_cfg {
29 };
30 
akm09918c_emul_set_reg(const struct emul * target,uint8_t reg_addr,const uint8_t * val,size_t count)31 void akm09918c_emul_set_reg(const struct emul *target, uint8_t reg_addr, const uint8_t *val,
32 			    size_t count)
33 {
34 	struct akm09918c_emul_data *data = target->data;
35 
36 	__ASSERT_NO_MSG(reg_addr + count < NUM_REGS);
37 	memcpy(data->reg + reg_addr, val, count);
38 }
39 
akm09918c_emul_get_reg(const struct emul * target,uint8_t reg_addr,uint8_t * val,size_t count)40 void akm09918c_emul_get_reg(const struct emul *target, uint8_t reg_addr, uint8_t *val, size_t count)
41 {
42 	struct akm09918c_emul_data *data = target->data;
43 
44 	__ASSERT_NO_MSG(reg_addr + count < NUM_REGS);
45 	memcpy(val, data->reg + reg_addr, count);
46 }
47 
akm09918c_emul_reset(const struct emul * target)48 void akm09918c_emul_reset(const struct emul *target)
49 {
50 	struct akm09918c_emul_data *data = target->data;
51 
52 	memset(data->reg, 0, NUM_REGS);
53 	data->reg[AKM09918C_REG_WIA1] = AKM09918C_WIA1;
54 	data->reg[AKM09918C_REG_WIA2] = AKM09918C_WIA2;
55 }
56 
akm09918c_emul_handle_write(const struct emul * target,uint8_t regn,uint8_t value)57 static int akm09918c_emul_handle_write(const struct emul *target, uint8_t regn, uint8_t value)
58 {
59 	struct akm09918c_emul_data *data = target->data;
60 
61 	switch (regn) {
62 	case AKM09918C_REG_CNTL2:
63 		data->reg[AKM09918C_REG_CNTL2] = value;
64 		break;
65 	case AKM09918C_REG_CNTL3:
66 		if (FIELD_GET(AKM09918C_CNTL3_SRST, value) == 1) {
67 			akm09918c_emul_reset(target);
68 		}
69 		break;
70 	}
71 	return 0;
72 }
73 
akm09918c_emul_transfer_i2c(const struct emul * target,struct i2c_msg * msgs,int num_msgs,int addr)74 static int akm09918c_emul_transfer_i2c(const struct emul *target, struct i2c_msg *msgs,
75 				       int num_msgs, int addr)
76 {
77 	struct akm09918c_emul_data *data = target->data;
78 
79 	i2c_dump_msgs_rw(target->dev, msgs, num_msgs, addr, false);
80 
81 	if (num_msgs < 1) {
82 		LOG_ERR("Invalid number of messages: %d", num_msgs);
83 		return -EIO;
84 	}
85 	if (FIELD_GET(I2C_MSG_READ, msgs->flags)) {
86 		LOG_ERR("Unexpected read");
87 		return -EIO;
88 	}
89 	if (msgs->len < 1) {
90 		LOG_ERR("Unexpected msg0 length %d", msgs->len);
91 		return -EIO;
92 	}
93 
94 	uint8_t regn = msgs->buf[0];
95 	bool is_read = FIELD_GET(I2C_MSG_READ, msgs->flags) == 1;
96 	bool is_stop = FIELD_GET(I2C_MSG_STOP, msgs->flags) == 1;
97 
98 	if (!is_stop && !is_read) {
99 		/* First message was a write with the register number, check next message */
100 		msgs++;
101 		is_read = FIELD_GET(I2C_MSG_READ, msgs->flags) == 1;
102 		is_stop = FIELD_GET(I2C_MSG_STOP, msgs->flags) == 1;
103 	}
104 	if (is_read) {
105 		/* Read data */
106 		uint8_t mode = data->reg[AKM09918C_REG_CNTL2];
107 
108 		for (int i = 0; i < msgs->len; ++i) {
109 			msgs->buf[i] = data->reg[regn + i];
110 			if (regn + i == AKM09918C_REG_TMPS &&
111 			    mode == AKM09918C_CNTL2_SINGLE_MEASURE) {
112 				/* Reading the TMPS register clears the DRDY bit */
113 				data->reg[AKM09918C_REG_ST1] = 0;
114 			}
115 		}
116 	} else {
117 		/* Write data */
118 		int rc = akm09918c_emul_handle_write(target, regn, msgs->buf[1]);
119 
120 		if (rc != 0) {
121 			return rc;
122 		}
123 	}
124 
125 	return 0;
126 }
127 
akm09918c_emul_init(const struct emul * target,const struct device * parent)128 static int akm09918c_emul_init(const struct emul *target, const struct device *parent)
129 {
130 	ARG_UNUSED(parent);
131 	akm09918c_emul_reset(target);
132 
133 	return 0;
134 }
135 
akm09918c_emul_backend_set_channel(const struct emul * target,struct sensor_chan_spec ch,const q31_t * value,int8_t shift)136 static int akm09918c_emul_backend_set_channel(const struct emul *target, struct sensor_chan_spec ch,
137 					      const q31_t *value, int8_t shift)
138 {
139 	if (!target || !target->data) {
140 		return -EINVAL;
141 	}
142 
143 	struct akm09918c_emul_data *data = target->data;
144 	uint8_t reg;
145 
146 	switch (ch.chan_type) {
147 	case SENSOR_CHAN_MAGN_X:
148 		reg = AKM09918C_REG_HXL;
149 		break;
150 	case SENSOR_CHAN_MAGN_Y:
151 		reg = AKM09918C_REG_HYL;
152 		break;
153 	case SENSOR_CHAN_MAGN_Z:
154 		reg = AKM09918C_REG_HZL;
155 		break;
156 	/* This function only supports setting single channels, so skip MAGN_XYZ */
157 	default:
158 		return -ENOTSUP;
159 	}
160 
161 	/* Set the ST1 register to show we have data */
162 	data->reg[AKM09918C_REG_ST1] |= AKM09918C_ST1_DRDY;
163 
164 	/* Convert fixed-point Gauss values into microgauss and then into its bit representation */
165 	int32_t microgauss =
166 		(shift < 0 ? ((int64_t)*value >> -shift) : ((int64_t)*value << shift)) * 1000000 /
167 		((int64_t)INT32_MAX + 1);
168 
169 	int16_t reg_val =
170 		CLAMP(microgauss, AKM09918C_MAGN_MIN_MICRO_GAUSS, AKM09918C_MAGN_MAX_MICRO_GAUSS) /
171 		AKM09918C_MICRO_GAUSS_PER_BIT;
172 
173 	/* Insert reading into registers */
174 	data->reg[reg] = reg_val & 0xFF;
175 	data->reg[reg + 1] = (reg_val >> 8) & 0xFF;
176 
177 	return 0;
178 }
179 
akm09918c_emul_backend_get_sample_range(const struct emul * target,struct sensor_chan_spec ch,q31_t * lower,q31_t * upper,q31_t * epsilon,int8_t * shift)180 static int akm09918c_emul_backend_get_sample_range(const struct emul *target,
181 						   struct sensor_chan_spec ch, q31_t *lower,
182 						   q31_t *upper, q31_t *epsilon, int8_t *shift)
183 {
184 	ARG_UNUSED(target);
185 
186 	if (!lower || !upper || !epsilon || !shift) {
187 		return -EINVAL;
188 	}
189 
190 	switch (ch.chan_type) {
191 	case SENSOR_CHAN_MAGN_X:
192 	case SENSOR_CHAN_MAGN_Y:
193 	case SENSOR_CHAN_MAGN_Z:
194 		/* +/- 49.12 Gs is the measurement range. 0.0015 Gs is the granularity */
195 		*shift = 6;
196 		*upper = (int64_t)(49.12 * ((int64_t)INT32_MAX + 1)) >> *shift;
197 		*lower = -*upper;
198 		*epsilon = (int64_t)(0.0015 * ((int64_t)INT32_MAX + 1)) >> *shift;
199 		break;
200 	default:
201 		return -ENOTSUP;
202 	}
203 
204 	return 0;
205 }
206 
207 static const struct i2c_emul_api akm09918c_emul_api_i2c = {
208 	.transfer = akm09918c_emul_transfer_i2c,
209 };
210 
211 static const struct emul_sensor_driver_api akm09918c_emul_sensor_driver_api = {
212 	.set_channel = akm09918c_emul_backend_set_channel,
213 	.get_sample_range = akm09918c_emul_backend_get_sample_range,
214 };
215 
216 #define AKM09918C_EMUL(n)                                                                          \
217 	const struct akm09918c_emul_cfg akm09918c_emul_cfg_##n;                                    \
218 	struct akm09918c_emul_data akm09918c_emul_data_##n;                                        \
219 	EMUL_DT_INST_DEFINE(n, akm09918c_emul_init, &akm09918c_emul_data_##n,                      \
220 			    &akm09918c_emul_cfg_##n, &akm09918c_emul_api_i2c,                      \
221 			    &akm09918c_emul_sensor_driver_api)
222 
223 DT_INST_FOREACH_STATUS_OKAY(AKM09918C_EMUL)
224