1 /*
2 * Copyright (c) 2023 Google LLC
3 * Copyright (c) 2024 Florian Weber <Florian.Weber@live.de>
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT asahi_kasei_akm09918c
8
9 #include <zephyr/device.h>
10 #include <zephyr/drivers/i2c.h>
11 #include <zephyr/kernel.h>
12 #include <zephyr/drivers/sensor.h>
13 #include <zephyr/drivers/sensor_data_types.h>
14 #include <zephyr/sys/__assert.h>
15 #include <zephyr/sys/byteorder.h>
16 #include <zephyr/sys/util.h>
17 #include <zephyr/logging/log.h>
18
19 #include "akm09918c.h"
20 #include "akm09918c_reg.h"
21
22 LOG_MODULE_REGISTER(AKM09918C, CONFIG_SENSOR_LOG_LEVEL);
23
24 /**
25 * @brief Perform the bus transaction to start measurement.
26 *
27 * @param dev Sensor device to operate on
28 * @param chan Channel ID for starting the measurement
29 * @return int 0 if successful or error code
30 */
akm09918c_start_measurement(const struct device * dev,enum sensor_channel chan)31 int akm09918c_start_measurement(const struct device *dev, enum sensor_channel chan)
32 {
33 struct akm09918c_data *data = dev->data;
34 const struct akm09918c_config *cfg = dev->config;
35
36 if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_MAGN_X && chan != SENSOR_CHAN_MAGN_Y &&
37 chan != SENSOR_CHAN_MAGN_Z && chan != SENSOR_CHAN_MAGN_XYZ) {
38 LOG_DBG("Invalid channel %d", chan);
39 return -EINVAL;
40 }
41
42 if (data->mode == AKM09918C_CNTL2_PWR_DOWN) {
43 if (i2c_reg_write_byte_dt(&cfg->i2c, AKM09918C_REG_CNTL2,
44 AKM09918C_CNTL2_SINGLE_MEASURE) != 0) {
45 LOG_ERR("Failed to start measurement.");
46 return -EIO;
47 }
48 }
49 return 0;
50 }
51
52 /**
53 * @brief Perform the bus transaction to fetch samples.
54 *
55 * @param dev Sensor device to operate on
56 * @param chan Channel ID to fetch
57 * @param x Location to write X channel sample.
58 * @param y Location to write Y channel sample.
59 * @param z Location to write Z channel sample.
60 * @return int 0 if successful or error code
61 */
akm09918c_fetch_measurement(const struct device * dev,int16_t * x,int16_t * y,int16_t * z)62 int akm09918c_fetch_measurement(const struct device *dev, int16_t *x, int16_t *y, int16_t *z)
63 {
64 const struct akm09918c_config *cfg = dev->config;
65 uint8_t buf[9] = {0};
66
67 /* We have to read through the TMPS register or the data_ready bit won't clear */
68 if (i2c_burst_read_dt(&cfg->i2c, AKM09918C_REG_ST1, buf, ARRAY_SIZE(buf)) != 0) {
69 LOG_ERR("Failed to read sample data.");
70 return -EIO;
71 }
72
73 if (FIELD_GET(AKM09918C_ST1_DRDY, buf[0]) == 0) {
74 LOG_ERR("Data not ready, st1=0x%02x", buf[0]);
75 return -EBUSY;
76 }
77
78 *x = sys_le16_to_cpu(buf[1] | (buf[2] << 8));
79 *y = sys_le16_to_cpu(buf[3] | (buf[4] << 8));
80 *z = sys_le16_to_cpu(buf[5] | (buf[6] << 8));
81
82 return 0;
83 }
84
akm09918c_sample_fetch(const struct device * dev,enum sensor_channel chan)85 static int akm09918c_sample_fetch(const struct device *dev, enum sensor_channel chan)
86 {
87 struct akm09918c_data *data = dev->data;
88
89 int ret = akm09918c_start_measurement(dev, chan);
90
91 if (ret) {
92 return ret;
93 }
94 /* Wait for sample */
95 LOG_DBG("Waiting for sample...");
96 k_usleep(AKM09918C_MEASURE_TIME_US);
97
98 return akm09918c_fetch_measurement(dev, &data->x_sample, &data->y_sample, &data->z_sample);
99 }
100
akm09918c_convert(struct sensor_value * val,int16_t sample)101 static void akm09918c_convert(struct sensor_value *val, int16_t sample)
102 {
103 int64_t conv_val = sample * AKM09918C_MICRO_GAUSS_PER_BIT;
104
105 val->val1 = conv_val / 1000000;
106 val->val2 = conv_val - (val->val1 * 1000000);
107 }
108
akm09918c_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)109 static int akm09918c_channel_get(const struct device *dev, enum sensor_channel chan,
110 struct sensor_value *val)
111 {
112 struct akm09918c_data *data = dev->data;
113
114 if (chan == SENSOR_CHAN_MAGN_XYZ) {
115 akm09918c_convert(val, data->x_sample);
116 akm09918c_convert(val + 1, data->y_sample);
117 akm09918c_convert(val + 2, data->z_sample);
118 } else if (chan == SENSOR_CHAN_MAGN_X) {
119 akm09918c_convert(val, data->x_sample);
120 } else if (chan == SENSOR_CHAN_MAGN_Y) {
121 akm09918c_convert(val, data->y_sample);
122 } else if (chan == SENSOR_CHAN_MAGN_Z) {
123 akm09918c_convert(val, data->z_sample);
124 } else {
125 LOG_DBG("Invalid channel %d", chan);
126 return -ENOTSUP;
127 }
128
129 return 0;
130 }
131
akm09918c_attr_get(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,struct sensor_value * val)132 static int akm09918c_attr_get(const struct device *dev, enum sensor_channel chan,
133 enum sensor_attribute attr, struct sensor_value *val)
134 {
135 struct akm09918c_data *data = dev->data;
136
137 switch (chan) {
138 case SENSOR_CHAN_MAGN_X:
139 case SENSOR_CHAN_MAGN_Y:
140 case SENSOR_CHAN_MAGN_Z:
141 case SENSOR_CHAN_MAGN_XYZ:
142 if (attr != SENSOR_ATTR_SAMPLING_FREQUENCY) {
143 LOG_WRN("Invalid attribute %d", attr);
144 return -EINVAL;
145 }
146 akm09918c_reg_to_hz(data->mode, val);
147 break;
148 default:
149 LOG_WRN("Invalid channel %d", chan);
150 return -EINVAL;
151 }
152 return 0;
153 }
154
akm09918c_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)155 static int akm09918c_attr_set(const struct device *dev, enum sensor_channel chan,
156 enum sensor_attribute attr, const struct sensor_value *val)
157 {
158 const struct akm09918c_config *cfg = dev->config;
159 struct akm09918c_data *data = dev->data;
160 int res;
161
162 switch (chan) {
163 case SENSOR_CHAN_MAGN_X:
164 case SENSOR_CHAN_MAGN_Y:
165 case SENSOR_CHAN_MAGN_Z:
166 case SENSOR_CHAN_MAGN_XYZ:
167 if (attr != SENSOR_ATTR_SAMPLING_FREQUENCY) {
168 LOG_WRN("Invalid attribute %d", attr);
169 return -EINVAL;
170 }
171
172 uint8_t mode = akm09918c_hz_to_reg(val);
173
174 res = i2c_reg_write_byte_dt(&cfg->i2c, AKM09918C_REG_CNTL2, mode);
175 if (res != 0) {
176 LOG_ERR("Failed to set sample frequency");
177 return -EIO;
178 }
179 data->mode = mode;
180 break;
181 default:
182 LOG_WRN("Invalid channel %d", chan);
183 return -EINVAL;
184 }
185
186 return 0;
187 }
188
akm09918c_check_who_am_i(const struct i2c_dt_spec * i2c)189 static inline int akm09918c_check_who_am_i(const struct i2c_dt_spec *i2c)
190 {
191 uint8_t buffer[2];
192 int rc;
193
194 rc = i2c_burst_read_dt(i2c, AKM09918C_REG_WIA1, buffer, ARRAY_SIZE(buffer));
195 if (rc != 0) {
196 LOG_ERR("Failed to read who-am-i register (rc=%d)", rc);
197 return -EIO;
198 }
199
200 if (buffer[0] != AKM09918C_WIA1 || buffer[1] != AKM09918C_WIA2) {
201 LOG_ERR("Wrong who-am-i value");
202 return -EINVAL;
203 }
204
205 return 0;
206 }
207
akm09918c_init(const struct device * dev)208 static int akm09918c_init(const struct device *dev)
209 {
210 const struct akm09918c_config *cfg = dev->config;
211 struct akm09918c_data *data = dev->data;
212 int rc;
213
214 if (!i2c_is_ready_dt(&cfg->i2c)) {
215 LOG_ERR("I2C bus device not ready");
216 return -ENODEV;
217 }
218
219 /* Soft reset the chip */
220 rc = i2c_reg_write_byte_dt(&cfg->i2c, AKM09918C_REG_CNTL3,
221 FIELD_PREP(AKM09918C_CNTL3_SRST, 1));
222 if (rc != 0) {
223 LOG_ERR("Failed to soft reset");
224 return -EIO;
225 }
226
227 /* check chip ID */
228 rc = akm09918c_check_who_am_i(&cfg->i2c);
229 if (rc != 0) {
230 return rc;
231 }
232 data->mode = AKM09918C_CNTL2_PWR_DOWN;
233 #ifdef CONFIG_SENSOR_ASYNC_API
234 /* init work for fetching after measurement has completed */
235 k_work_init_delayable(&data->work_ctx.async_fetch_work, akm09918_async_fetch);
236 #endif
237 return 0;
238 }
239
240 static DEVICE_API(sensor, akm09918c_driver_api) = {
241 .sample_fetch = akm09918c_sample_fetch,
242 .channel_get = akm09918c_channel_get,
243 .attr_get = akm09918c_attr_get,
244 .attr_set = akm09918c_attr_set,
245 #ifdef CONFIG_SENSOR_ASYNC_API
246 .submit = akm09918c_submit,
247 .get_decoder = akm09918c_get_decoder,
248 #endif
249 };
250
251 #define AKM09918C_DEFINE(inst) \
252 static struct akm09918c_data akm09918c_data_##inst; \
253 \
254 static const struct akm09918c_config akm09918c_config_##inst = { \
255 .i2c = I2C_DT_SPEC_INST_GET(inst), \
256 }; \
257 \
258 SENSOR_DEVICE_DT_INST_DEFINE(inst, akm09918c_init, NULL, &akm09918c_data_##inst, \
259 &akm09918c_config_##inst, POST_KERNEL, \
260 CONFIG_SENSOR_INIT_PRIORITY, &akm09918c_driver_api);
261
262 DT_INST_FOREACH_STATUS_OKAY(AKM09918C_DEFINE)
263