summaryrefslogtreecommitdiff
path: root/zephyr/emul/emul_bma4xx.c
blob: 7d432bca0c0af003da9c7915ec48cb24ac3ab611 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/* Copyright 2023 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "accelgyro.h"
#include "driver/accel_bma4xx.h"
#include "emul/emul_bma4xx.h"
#include "emul/emul_common_i2c.h"
#include "emul/emul_stub_device.h"
#include "i2c.h"
#include "motion_sense.h"

#include <errno.h>

#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/math_extras.h>
#include <zephyr/ztest.h>

#define DT_DRV_COMPAT cros_bma4xx_emul

LOG_MODULE_REGISTER(bma4xx_emul, LOG_LEVEL_INF);

struct bma4xx_emul_data {
	struct i2c_common_emul_data i2c;
	/** True if the sensor is currently enabled. */
	bool accel_enabled;
	/** Current sensor range, ±2/4/8/16g; value is positive gs. */
	uint8_t accel_range;
	/** Raw register value of ACC_CONF:acc_odr. */
	uint8_t odr_raw;
	/** Current sensor reading on XYZ axes, in milli-g. */
	intv3_t acceleration;
	/** Axis offset register values, XYZ. */
	uint8_t offset[3];
	/** NV_CONF register value. */
	uint8_t nv_config;
	/**
	 * True if the sensor FIFO is currently enabled.
	 *
	 * Only headerless mode for the accelerometer alone is supported.
	 */
	bool fifo_enabled;
	/** Pointer to data that will be read from the FIFO. */
	const uint8_t *fifo_data;
	/** Number of bytes remaining in fifo_data. */
	uint16_t fifo_available;

	/** True if in latched interrupt mode, otherwise non-latched. */
	bool interrupt_mode_latched;
	/** Raw value of INT1_IO_CTRL register. */
	uint8_t int1_io_ctrl;
	/** Raw value of INT_MAP_DATA register. */
	uint8_t int_map_data;
};

struct bma4xx_emul_cfg {
	struct i2c_common_emul_cfg i2c;
	int sensor_id;
};

void bma4xx_emul_reset(const struct emul *emul)
{
	struct bma4xx_emul_data *data = emul->data;

	i2c_common_emul_set_read_fail_reg(&data->i2c,
					  I2C_COMMON_EMUL_NO_FAIL_REG);
	i2c_common_emul_set_read_func(&data->i2c, NULL, NULL);
	i2c_common_emul_set_write_fail_reg(&data->i2c,
					   I2C_COMMON_EMUL_NO_FAIL_REG);
	i2c_common_emul_set_write_func(&data->i2c, NULL, NULL);

	data->accel_enabled = false;
	data->accel_range = 2;
	data->odr_raw = 8;
	memset(data->acceleration, 0, sizeof(data->acceleration));
	memset(data->offset, 0, sizeof(data->offset));
	data->nv_config = 0;
	data->fifo_enabled = false;
	data->fifo_data = NULL;
	data->fifo_available = 0;
	data->interrupt_mode_latched = false;
}

struct i2c_common_emul_data *bma4xx_emul_get_i2c(const struct emul *emul)
{
	struct bma4xx_emul_data *data = emul->data;

	return &data->i2c;
}

struct motion_sensor_t *bma4xx_emul_get_sensor_data(const struct emul *emul)
{
	return &motion_sensors[bma4xx_emul_get_sensor_num(emul)];
}

int bma4xx_emul_get_sensor_num(const struct emul *emul)
{
	const struct bma4xx_emul_cfg *cfg = emul->cfg;

	return cfg->sensor_id;
}

bool bma4xx_emul_is_accel_enabled(const struct emul *emul)
{
	struct bma4xx_emul_data *data = emul->data;

	return data->accel_enabled;
}

void bma4xx_emul_set_accel_enabled(const struct emul *emul, bool enabled)
{
	struct bma4xx_emul_data *data = emul->data;

	data->accel_enabled = enabled;
}

uint8_t bma4xx_emul_get_accel_range(const struct emul *emul)
{
	struct bma4xx_emul_data *data = emul->data;

	return data->accel_range;
}

uint32_t bma4xx_emul_get_odr(const struct emul *emul)
{
	struct bma4xx_emul_data *data = emul->data;

	/*
	 * This function deliberately differs from bma4_reg_to_odr() to provide
	 * an obviously-correct reference for tests.
	 */
	switch (data->odr_raw) {
	case 1:
		return 781; /* 25/32 Hz */
	case 2:
		return 1562; /* 25/16 Hz */
	case 3:
		return 3125;
	case 4:
		return 6250;
	case 5:
		return 12500;
	case 6:
		return 25000;
	case 7:
		return 50000;
	case 8:
		return 100000;
	case 9:
		return 200000;
	case 10:
		return 400000;
	case 11:
		return 800000;
	case 12:
		return 1600000;
	default:
		LOG_ERR("ODR register value %#x is reserved", data->odr_raw);
		return 0;
	}
}

void bma4xx_emul_set_accel_data(const struct emul *emul, int x, int y, int z)
{
	struct bma4xx_emul_data *data = emul->data;

	data->acceleration[0] = x;
	data->acceleration[1] = y;
	data->acceleration[2] = z;
}

void bma4xx_emul_get_offset(const struct emul *emul, int8_t (*offset)[3])
{
	struct bma4xx_emul_data *data = emul->data;

	memcpy(offset, data->offset, sizeof(*offset));
}

uint8_t bma4xx_emul_get_nv_conf(const struct emul *emul)
{
	struct bma4xx_emul_data *data = emul->data;

	return data->nv_config;
}

bool bma4xx_emul_is_fifo_enabled(const struct emul *emul)
{
	struct bma4xx_emul_data *data = emul->data;

	return data->fifo_enabled;
}

void bma4xx_emul_set_fifo_data(const struct emul *emul,
			       const uint8_t *fifo_data, uint16_t data_sz)
{
	struct bma4xx_emul_data *data = emul->data;

	__ASSERT(data_sz % 6 == 0,
		 "FIFO data should be an integer number of frames");
	data->fifo_data = fifo_data;
	data->fifo_available = data_sz;
}

uint8_t bma4xx_emul_get_interrupt_config(const struct emul *emul,
					 uint8_t *int1_io_ctrl,
					 bool *latched_mode)
{
	struct bma4xx_emul_data *data = emul->data;

	*int1_io_ctrl = data->int1_io_ctrl;
	*latched_mode = data->interrupt_mode_latched;
	return data->int_map_data;
}

static int bma4xx_emul_read_byte(const struct emul *target, int reg,
				 uint8_t *val, int bytes)
{
	struct bma4xx_emul_data *data = target->data;

	if (reg != BMA4_FIFO_DATA_ADDR) {
		/*
		 * Burst reads autoincrement register addresses, except for
		 * FIFO data which reads from the FIFO instead.
		 */
		reg += bytes;
	}

	switch (reg) {
	case BMA4_CHIP_ID_ADDR:
		*val = 0x12; /* BMA422_CHIP_ID */
		return 0;
	case BMA4_DATA_8_ADDR: /* ACC_X(LSB) */
		*val = (data->acceleration[0] & GENMASK(3, 0)) << 4;
		return 0;
	case BMA4_DATA_8_ADDR + 1: /* ACC_X(MSB) */
		*val = (data->acceleration[0] >> 4) & GENMASK(7, 0);
		return 0;
	case BMA4_DATA_8_ADDR + 2: /* ACC_Y(LSB) */
		*val = (data->acceleration[1] & GENMASK(3, 0)) << 4;
		return 0;
	case BMA4_DATA_8_ADDR + 3: /* ACC_Y(MSB) */
		*val = (data->acceleration[1] >> 4) & GENMASK(7, 0);
		return 0;
	case BMA4_DATA_8_ADDR + 4: /* ACC_Z(LSB) */
		*val = (data->acceleration[2] & GENMASK(3, 0)) << 4;
		return 0;
	case BMA4_DATA_8_ADDR + 5: /* ACC_Z(MSB) */
		*val = (data->acceleration[2] >> 4) & GENMASK(7, 0);
		return 0;
	case BMA4_INT_STAT_1_ADDR:
		*val = data->fifo_available > 0 ? 0x80 : 0; /* acc_drdy_int */
		return 0;
	case BMA4_FIFO_LENGTH_0_ADDR: /* LSB */
		*val = data->fifo_available & 0xFF;
		return 0;
	case BMA4_FIFO_LENGTH_0_ADDR + 1: /* MSB */
		*val = data->fifo_available >> 8;
		return 0;
	case BMA4_FIFO_DATA_ADDR:
		/*
		 * Read FIFO data; only supporting headerless mode with accel
		 * data only.
		 *
		 * Partial reads (of less than an entire frame) do not consume
		 * FIFO data, so we track the amount read in this burst. Reading
		 * past the end of the FIFO returns 0x8000.
		 */
		if (bytes % 6 >= data->fifo_available) {
			/* Out of data. */
			if (bytes % 2 == 0) {
				*val = 0;
			} else {
				*val = 0x80;
			}
			return 0;
		}
		*val = data->fifo_data[bytes % 6];
		if (bytes % 6 == 5) {
			/* Consume frame after reading the entire thing. */
			data->fifo_data += 6;
			data->fifo_available -= 6;
		}
		return 0;
	case BMA4_ACCEL_CONFIG_ADDR:
		*val = data->odr_raw | 0xA0;
		return 0;
	case BMA4_ACCEL_RANGE_ADDR:
		*val = u32_count_trailing_zeros(data->accel_range) - 1;
		__ASSERT_NO_MSG(*val >= 0 && *val <= 3);
		return 0;
	case BMA4_NV_CONFIG_ADDR:
		*val = data->nv_config;
		return 0;
	case BMA4_FIFO_CONFIG_1_ADDR:
		*val = data->fifo_enabled ? BMA4_FIFO_ACC_EN : 0;
		return 0;
	case BMA4_INT_LATCH_ADDR:
		*val = data->interrupt_mode_latched ? 1 : 0;
		return 0;
	case BMA4_OFFSET_0_ADDR:
	case BMA4_OFFSET_1_ADDR:
	case BMA4_OFFSET_2_ADDR:
		*val = data->offset[reg - BMA4_OFFSET_0_ADDR];
		return 0;
	case BMA4_POWER_CTRL_ADDR:
		*val = data->accel_enabled ? BMA4_ACCEL_ENABLE_MSK : 0;
		return 0;
	}

	LOG_WRN("unhandled I2C read from register %#x", reg);
	return -ENOTSUP;
}

static int bma4xx_emul_write_byte(const struct emul *target, int reg,
				  uint8_t val, int bytes)
{
	struct bma4xx_emul_data *data = target->data;

	if (bytes != 1) {
		LOG_ERR("multi-byte writes are not supported");
		return -ENOTSUP;
	}

	switch (reg) {
	case BMA4_ACCEL_CONFIG_ADDR:
		if ((val & 0xF0) != 0xA0) {
			LOG_ERR("unsupported acc_bwp/acc_perf_mode: %#x", val);
			return -EINVAL;
		}
		data->odr_raw = val & BMA4_ACCEL_ODR_MSK;
		return 0;
	case BMA4_ACCEL_RANGE_ADDR:
		if ((val & GENMASK(1, 0)) != val) {
			LOG_ERR("reserved bits set in ACC_RANGE write: %#x",
				val);
			return -EINVAL;
		}
		/* 0 => 2, 1 => 4, ... 3 => 16 */
		data->accel_range = 2 << val;
		return 0;
	case BMA4_FIFO_CONFIG_1_ADDR:
		if (val & ~BMA4_FIFO_ACC_EN) {
			LOG_ERR("unsupported bits set in FIFO_CONFIG_1"
				" write: %#x",
				val);
			return -EINVAL;
		}
		data->fifo_enabled = (val & BMA4_FIFO_ACC_EN) != 0;
		return 0;
	case BMA4_INT1_IO_CTRL_ADDR:
		data->int1_io_ctrl = val;
		return 0;
	case BMA4_INT_LATCH_ADDR:
		if ((val & ~1) != 0) {
			LOG_ERR("reserved bits set in INT_LATCH: %#x", val);
			return -EINVAL;
		}
		data->interrupt_mode_latched = (val & 1) == 1;
		return 0;
	case BMA4_INT_MAP_DATA_ADDR:
		data->int_map_data = val;
		return 0;
	case BMA4_NV_CONFIG_ADDR:
		if (val & GENMASK(7, 4)) {
			LOG_ERR("reserved bits set in NV_CONF write: %#x", val);
			return -EINVAL;
		}
		data->nv_config = val;
		return 0;
	case BMA4_OFFSET_0_ADDR:
	case BMA4_OFFSET_1_ADDR:
	case BMA4_OFFSET_2_ADDR:
		data->offset[reg - BMA4_OFFSET_0_ADDR] = val;
		return 0;
	case BMA4_POWER_CTRL_ADDR:
		if ((val & ~BMA4_ACCEL_ENABLE_MSK) != 0) {
			LOG_ERR("unhandled bits in POWER_CTRL write: %#x", val);
			return -ENOTSUP;
		}
		data->accel_enabled = (val & BMA4_ACCEL_ENABLE_MSK) != 0;
		return 0;
	case BMA4_CMD_ADDR:
		if (val == 0xb0) { /* fifo_flush */
			data->fifo_data = NULL;
			data->fifo_available = 0;
			return 0;
		}
		break;
	}

	LOG_WRN("unhandled I2C write to register %#x", reg);
	return -ENOTSUP;
}

static int bma4xx_emul_init(const struct emul *emul,
			    const struct device *parent)
{
	struct bma4xx_emul_data *data = emul->data;

	data->i2c.i2c = parent;
	i2c_common_emul_init(&data->i2c);
	bma4xx_emul_reset(emul);

	return 0;
}

#define INIT_BMA4XX(n)                                                    \
	static struct bma4xx_emul_data bma4xx_emul_data_##n = {         \
		.i2c = {                                                \
			.write_byte = bma4xx_emul_write_byte,           \
			.read_byte = bma4xx_emul_read_byte,             \
			.i2c = DEVICE_DT_GET(DT_INST_PARENT(n)),        \
		},                                                      \
	}; \
	static const struct bma4xx_emul_cfg bma4xx_emul_cfg_##n = {	\
		.i2c = {                                                \
			.dev_label = DT_NODE_FULL_NAME(DT_DRV_INST(n)), \
			.addr = DT_INST_REG_ADDR(n),                    \
		},						        \
		.sensor_id = SENSOR_ID(DT_INST_PHANDLE(n,               \
						       motionsense_sensor)), \
	};     \
	EMUL_DT_INST_DEFINE(n, bma4xx_emul_init, &bma4xx_emul_data_##n,   \
			    &bma4xx_emul_cfg_##n, &i2c_common_emul_api, NULL)

DT_INST_FOREACH_STATUS_OKAY(INIT_BMA4XX)
DT_INST_FOREACH_STATUS_OKAY(EMUL_STUB_DEVICE);

static void bma4xx_emul_reset_rule_before(const struct ztest_unit_test *test,
					  void *data)
{
	ARG_UNUSED(test);
	ARG_UNUSED(data);

#define BMA4XX_EMUL_RESET_RULE_BEFORE(n) \
	bma4xx_emul_reset(EMUL_DT_GET(DT_DRV_INST(n)))

	DT_INST_FOREACH_STATUS_OKAY(BMA4XX_EMUL_RESET_RULE_BEFORE);
}
ZTEST_RULE(bma4xx_emul_reset, bma4xx_emul_reset_rule_before, NULL);