1 /*
2  * Copyright 2023 Cirrus Logic, Inc.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Emulator for SBS 1.1 compliant smart battery charger.
7  */
8 
9 #define DT_DRV_COMPAT sbs_sbs_charger
10 
11 #include <zephyr/device.h>
12 #include <zephyr/drivers/emul.h>
13 #include <zephyr/drivers/i2c.h>
14 #include <zephyr/drivers/i2c_emul.h>
15 #include <zephyr/logging/log.h>
16 #include <zephyr/sys/byteorder.h>
17 
18 #include "sbs_charger.h"
19 
20 LOG_MODULE_REGISTER(sbs_sbs_charger);
21 
22 /** Static configuration for the emulator */
23 struct sbs_charger_emul_cfg {
24 	/** I2C address of emulator */
25 	uint16_t addr;
26 };
27 
28 /** Run-time data used by the emulator */
29 struct sbs_charger_emul_data {
30 	uint16_t reg_charger_mode;
31 };
32 
emul_sbs_charger_reg_write(const struct emul * target,int reg,int val)33 static int emul_sbs_charger_reg_write(const struct emul *target, int reg, int val)
34 {
35 	struct sbs_charger_emul_data *data = target->data;
36 
37 	LOG_INF("write %x = %x", reg, val);
38 	switch (reg) {
39 	case SBS_CHARGER_REG_CHARGER_MODE:
40 		data->reg_charger_mode = val;
41 		break;
42 	default:
43 		LOG_ERR("Unknown write %x", reg);
44 		return -EIO;
45 	}
46 
47 	return 0;
48 }
49 
emul_sbs_charger_reg_read(const struct emul * target,int reg,int * val)50 static int emul_sbs_charger_reg_read(const struct emul *target, int reg, int *val)
51 {
52 	switch (reg) {
53 	case SBS_CHARGER_REG_SPEC_INFO:
54 	case SBS_CHARGER_REG_CHARGER_MODE:
55 	case SBS_CHARGER_REG_STATUS:
56 	case SBS_CHARGER_REG_ALARM_WARNING:
57 		/* Arbitrary stub value. */
58 		*val = 1;
59 		break;
60 	default:
61 		LOG_ERR("Unknown register 0x%x read", reg);
62 		return -EIO;
63 	}
64 	LOG_INF("read 0x%x = 0x%x", reg, *val);
65 
66 	return 0;
67 }
68 
sbs_charger_emul_transfer_i2c(const struct emul * target,struct i2c_msg * msgs,int num_msgs,int addr)69 static int sbs_charger_emul_transfer_i2c(const struct emul *target, struct i2c_msg *msgs,
70 					 int num_msgs, int addr)
71 {
72 	/* Largely copied from emul_sbs_gauge.c */
73 	struct sbs_charger_emul_data *data;
74 	unsigned int val;
75 	int reg;
76 	int rc;
77 
78 	data = target->data;
79 
80 	i2c_dump_msgs_rw(target->dev, msgs, num_msgs, addr, false);
81 	switch (num_msgs) {
82 	case 2:
83 		if (msgs->flags & I2C_MSG_READ) {
84 			LOG_ERR("Unexpected read");
85 			return -EIO;
86 		}
87 		if (msgs->len != 1) {
88 			LOG_ERR("Unexpected msg0 length %d", msgs->len);
89 			return -EIO;
90 		}
91 		reg = msgs->buf[0];
92 
93 		/* Now process the 'read' part of the message */
94 		msgs++;
95 		if (msgs->flags & I2C_MSG_READ) {
96 			switch (msgs->len - 1) {
97 			case 1:
98 				rc = emul_sbs_charger_reg_read(target, reg, &val);
99 				if (rc) {
100 					/* Return before writing bad value to message buffer */
101 					return rc;
102 				}
103 
104 				/* SBS uses SMBus, which sends data in little-endian format. */
105 				sys_put_le16(val, msgs->buf);
106 				break;
107 			default:
108 				LOG_ERR("Unexpected msg1 length %d", msgs->len);
109 				return -EIO;
110 			}
111 		} else {
112 			/* We write a word (2 bytes by the SBS spec) */
113 			if (msgs->len != 2) {
114 				LOG_ERR("Unexpected msg1 length %d", msgs->len);
115 			}
116 			uint16_t value = sys_get_le16(msgs->buf);
117 
118 			rc = emul_sbs_charger_reg_write(target, reg, value);
119 		}
120 		break;
121 	default:
122 		LOG_ERR("Invalid number of messages: %d", num_msgs);
123 		return -EIO;
124 	}
125 
126 	return rc;
127 }
128 
129 static const struct i2c_emul_api sbs_charger_emul_api_i2c = {
130 	.transfer = sbs_charger_emul_transfer_i2c,
131 };
132 
emul_sbs_sbs_charger_init(const struct emul * target,const struct device * parent)133 static int emul_sbs_sbs_charger_init(const struct emul *target, const struct device *parent)
134 {
135 	ARG_UNUSED(target);
136 	ARG_UNUSED(parent);
137 
138 	return 0;
139 }
140 
141 /*
142  * Main instantiation macro. SBS Charger Emulator only implemented for I2C
143  */
144 #define SBS_CHARGER_EMUL(n)                                                                        \
145 	static struct sbs_charger_emul_data sbs_charger_emul_data_##n;                             \
146                                                                                                    \
147 	static const struct sbs_charger_emul_cfg sbs_charger_emul_cfg_##n = {                      \
148 		.addr = DT_INST_REG_ADDR(n),                                                       \
149 	};                                                                                         \
150 	EMUL_DT_INST_DEFINE(n, emul_sbs_sbs_charger_init, &sbs_charger_emul_data_##n,              \
151 			    &sbs_charger_emul_cfg_##n, &sbs_charger_emul_api_i2c, NULL)
152 
153 DT_INST_FOREACH_STATUS_OKAY(SBS_CHARGER_EMUL)
154