|
| 1 | +/* |
| 2 | + * Copyright (c) 2023 Google LLC. |
| 3 | + * Copyright (c) 2024 Croxel Inc. |
| 4 | + * |
| 5 | + * SPDX-License-Identifier: Apache-2.0 |
| 6 | + */ |
| 7 | + |
| 8 | +#include <zephyr/drivers/adc.h> |
| 9 | +#include <zephyr/logging/log.h> |
| 10 | +#include <zephyr/rtio/work.h> |
| 11 | + |
| 12 | +LOG_MODULE_REGISTER(adc_compat, CONFIG_ADC_LOG_LEVEL); |
| 13 | + |
| 14 | +static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe); |
| 15 | + |
| 16 | +static void adc_iodev_submit(struct rtio_iodev_sqe *iodev_sqe) |
| 17 | +{ |
| 18 | + const struct adc_read_config *cfg = iodev_sqe->sqe.iodev->data; |
| 19 | + const struct device *dev = cfg->adc; |
| 20 | + const struct adc_driver_api *api = dev->api; |
| 21 | + |
| 22 | + if (api->submit != NULL) { |
| 23 | + api->submit(dev, iodev_sqe); |
| 24 | + } else if (!cfg->is_streaming) { |
| 25 | + adc_submit_fallback(dev, iodev_sqe); |
| 26 | + } else { |
| 27 | + rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP); |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +const struct rtio_iodev_api __adc_iodev_api = { |
| 32 | + .submit = adc_iodev_submit, |
| 33 | +}; |
| 34 | + |
| 35 | +/** |
| 36 | + * @brief Compute the required header size |
| 37 | + * |
| 38 | + * This function takes into account alignment of the q31 values that will follow the header. |
| 39 | + * |
| 40 | + * @param[in] num_output_samples The number of samples to represent |
| 41 | + * @return The number of bytes needed for this sample frame's header |
| 42 | + */ |
| 43 | +static inline uint32_t compute_read_buf_size(const struct adc_dt_spec *adc_spec, int num_channels) |
| 44 | +{ |
| 45 | + uint32_t size = 0; |
| 46 | + for (int i = 0; i < num_channels; ++i) { |
| 47 | + size += adc_spec[i].resolution / 8; |
| 48 | + if (adc_spec[i].resolution % 8) { |
| 49 | + size++; |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + /* Align to 4 bytes */ |
| 54 | + if (size % 4) { |
| 55 | + size += 4 - (size % 4); |
| 56 | + } |
| 57 | + |
| 58 | + return size; |
| 59 | +} |
| 60 | + |
| 61 | +/** |
| 62 | + * @brief Compute the required header size |
| 63 | + * |
| 64 | + * This function takes into account alignment of the q31 values that will follow the header. |
| 65 | + * |
| 66 | + * @param[in] num_output_samples The number of samples to represent |
| 67 | + * @return The number of bytes needed for this sample frame's header |
| 68 | + */ |
| 69 | +static inline uint32_t compute_header_size(int num_output_samples) |
| 70 | +{ |
| 71 | + uint32_t size = sizeof(struct adc_data_generic_header) + |
| 72 | + (num_output_samples * sizeof(struct adc_chan_spec)); |
| 73 | + return (size + 3) & ~0x3; |
| 74 | +} |
| 75 | + |
| 76 | +/** |
| 77 | + * @brief Compute the minimum number of bytes needed |
| 78 | + * |
| 79 | + * @param[in] num_output_samples The number of samples to represent |
| 80 | + * @return The number of bytes needed for this sample frame |
| 81 | + */ |
| 82 | +static inline uint32_t compute_min_buf_len(int num_output_samples) |
| 83 | +{ |
| 84 | + return compute_header_size(num_output_samples) + (num_output_samples * sizeof(q31_t)); |
| 85 | +} |
| 86 | + |
| 87 | +/** |
| 88 | + * @brief Convert sample to q31_t format |
| 89 | + * |
| 90 | + * @param[in] out Pointer to the output q31_t value |
| 91 | + * @param[in] data_in The input data to convert |
| 92 | + * @param[in] channel The ADC channel specification |
| 93 | + * @param[in] adc_shift The shift value for the ADC |
| 94 | + */ |
| 95 | +static inline void adc_convert_q31(q31_t *out, uint64_t data_in, const struct adc_dt_spec *adc_spec, uint8_t adc_shift) |
| 96 | +{ |
| 97 | + uint32_t scale = BIT(adc_spec->resolution); |
| 98 | + uint8_t data_size = adc_spec->resolution / 8; |
| 99 | + if (adc_spec->resolution % 8) { |
| 100 | + data_size++; |
| 101 | + } |
| 102 | + |
| 103 | + /* In Differential mode, 1 bit is used for sign */ |
| 104 | + if (adc_spec->channel_cfg.differential) { |
| 105 | + scale = BIT(adc_spec->resolution - 1); |
| 106 | + } |
| 107 | + |
| 108 | + uint32_t sensitivity = (adc_spec->vref_mv * (scale - 1)) / scale |
| 109 | + * 1000 / scale; /* uV / LSB */ |
| 110 | + |
| 111 | + *out = BIT(31 - adc_shift)/* scaling to q_31*/ * sensitivity / 1000000/*uV to V*/ * data_in; |
| 112 | +} |
| 113 | + |
| 114 | +/** |
| 115 | + * @brief Compute the number of bits needed to represent the vref_mv |
| 116 | + * |
| 117 | + * @param[in] vref_mv The reference voltage in mV |
| 118 | + * @return The number of bits needed to represent the vref_mv |
| 119 | + */ |
| 120 | +uint8_t adc_convert_vref_to_shift(uint16_t vref_mv) { |
| 121 | + uint8_t count = 1; |
| 122 | + while (1) { |
| 123 | + vref_mv /= 2; |
| 124 | + if (vref_mv) { |
| 125 | + count++; |
| 126 | + } else { |
| 127 | + break; |
| 128 | + } |
| 129 | + } |
| 130 | + return count; |
| 131 | +} |
| 132 | + |
| 133 | +/** |
| 134 | + * @brief Fallback function for retrofiting old drivers to rtio (sync) |
| 135 | + * |
| 136 | + * @param[in] iodev_sqe The read submission queue event |
| 137 | + */ |
| 138 | +static void adc_submit_fallback_sync(struct rtio_iodev_sqe *iodev_sqe) |
| 139 | +{ |
| 140 | + const struct adc_read_config *cfg = iodev_sqe->sqe.iodev->data; |
| 141 | + const struct device *dev = cfg->adc; |
| 142 | + const struct adc_dt_spec *adc_spec = cfg->adc_spec; |
| 143 | + const int num_output_samples = cfg->adc_spec_cnt; |
| 144 | + uint32_t min_buf_len = compute_min_buf_len(num_output_samples); |
| 145 | + uint64_t timestamp_ns = k_ticks_to_ns_floor64(k_uptime_ticks()); |
| 146 | + uint8_t read_buf_size = compute_read_buf_size(adc_spec, num_output_samples); |
| 147 | + uint8_t sample_buffer[read_buf_size]; |
| 148 | + struct adc_sequence sequence = { |
| 149 | + .buffer = sample_buffer, |
| 150 | + .buffer_size = read_buf_size, |
| 151 | + }; |
| 152 | + int rc = adc_read(dev, &sequence); |
| 153 | + |
| 154 | + uint8_t *buf; |
| 155 | + uint32_t buf_len; |
| 156 | + |
| 157 | + /* Check that the fetch succeeded */ |
| 158 | + if (rc != 0) { |
| 159 | + LOG_WRN("Failed to fetch samples"); |
| 160 | + rtio_iodev_sqe_err(iodev_sqe, rc); |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + /* Get the buffer for the frame, it may be allocated dynamically by the rtio context */ |
| 165 | + rc = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len); |
| 166 | + if (rc != 0) { |
| 167 | + LOG_WRN("Failed to get a read buffer of size %u bytes", min_buf_len); |
| 168 | + rtio_iodev_sqe_err(iodev_sqe, rc); |
| 169 | + return; |
| 170 | + } |
| 171 | + |
| 172 | + /* Set the timestamp and num_channels */ |
| 173 | + struct adc_data_generic_header *header = (struct adc_data_generic_header *)buf; |
| 174 | + |
| 175 | + header->timestamp_ns = timestamp_ns; |
| 176 | + header->num_channels = num_output_samples; |
| 177 | + header->shift = 0; |
| 178 | + |
| 179 | + q31_t *q = (q31_t *)(buf + compute_header_size(num_output_samples)); |
| 180 | + uint8_t *sample_pointer = sample_buffer; |
| 181 | + |
| 182 | + /* Populate values, update shift, and set channels */ |
| 183 | + for (size_t i = 0; i < num_output_samples; ++i) { |
| 184 | + uint8_t sample_size = adc_spec[i].resolution / 8; |
| 185 | + if (adc_spec[i].resolution % 8) { |
| 186 | + sample_size++; |
| 187 | + } |
| 188 | + |
| 189 | + uint64_t sample = 0; |
| 190 | + memcpy(&sample, sample_pointer, sample_size); |
| 191 | + sample_pointer += sample_size; |
| 192 | + if (adc_spec[i].channel_cfg.differential) { |
| 193 | + if (sample & (BIT(adc_spec[i].resolution - 1))) { |
| 194 | + sample |= ~BIT_MASK(adc_spec[i].resolution); |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + header->channels[i].chan_idx = adc_spec[i].channel_id; |
| 199 | + header->channels[i].chan_resolution = adc_spec[i].resolution; |
| 200 | + |
| 201 | + int8_t new_shift = adc_convert_vref_to_shift(adc_spec[i].vref_mv); |
| 202 | + |
| 203 | + if (header->shift < new_shift) { |
| 204 | + /* |
| 205 | + * Shift was updated, need to convert all the existing q values. This could |
| 206 | + * be optimized by calling zdsp_scale_q31() but that would force a |
| 207 | + * dependency between sensors and the zDSP subsystem. |
| 208 | + */ |
| 209 | + for (int q_idx = 0; q_idx < i; ++q_idx) { |
| 210 | + q[q_idx] = q[q_idx] >> (new_shift - header->shift); |
| 211 | + } |
| 212 | + header->shift = new_shift; |
| 213 | + } |
| 214 | + |
| 215 | + adc_convert_q31(&q[i], sample, &adc_spec[i], header->shift); |
| 216 | + } |
| 217 | + LOG_DBG("Total channels in header: %" PRIu32, header->num_channels); |
| 218 | + rtio_iodev_sqe_ok(iodev_sqe, 0); |
| 219 | +} |
| 220 | + |
| 221 | +/** |
| 222 | + * @brief Fallback function for retrofiting old drivers to rtio |
| 223 | + * |
| 224 | + * @param[in] dev The ADC device to read |
| 225 | + * @param[in] iodev_sqe The read submission queue event |
| 226 | + */ |
| 227 | +static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe) |
| 228 | +{ |
| 229 | + struct rtio_work_req *req = rtio_work_req_alloc(); |
| 230 | + |
| 231 | + if (req == NULL) { |
| 232 | + LOG_ERR("RTIO work item allocation failed. Consider to increase " |
| 233 | + "CONFIG_RTIO_WORKQ_POOL_ITEMS."); |
| 234 | + rtio_iodev_sqe_err(iodev_sqe, -ENOMEM); |
| 235 | + return; |
| 236 | + } |
| 237 | + |
| 238 | + rtio_work_req_submit(req, iodev_sqe, adc_submit_fallback_sync); |
| 239 | + } |
| 240 | + |
| 241 | + /** |
| 242 | + * @brief Default decoder get frame count |
| 243 | + * |
| 244 | + * Default reader can only ever service a single frame at a time. |
| 245 | + * |
| 246 | + * @param[in] buffer The data buffer to parse |
| 247 | + * @param[in] channel The channel to get the count for |
| 248 | + * @param[out] frame_count The number of frames in the buffer (always 1) |
| 249 | + * @return 0 in all cases |
| 250 | + */ |
| 251 | + static int get_frame_count(const uint8_t *buffer, uint32_t channel, uint16_t *frame_count) |
| 252 | + { |
| 253 | + *frame_count = 1; |
| 254 | + return 0; |
| 255 | + } |
| 256 | + |
| 257 | + int adc_natively_supported_channel_size_info(struct adc_dt_spec adc_spec, uint32_t channel, size_t *base_size, |
| 258 | + size_t *frame_size) |
| 259 | + { |
| 260 | + __ASSERT_NO_MSG(base_size != NULL); |
| 261 | + __ASSERT_NO_MSG(frame_size != NULL); |
| 262 | + |
| 263 | + *base_size = sizeof(struct adc_data); |
| 264 | + *frame_size = sizeof(struct adc_sample_data); |
| 265 | + return 0; |
| 266 | + } |
| 267 | + |
| 268 | + static int get_q31_value(const struct adc_data_generic_header *header, const q31_t *values, |
| 269 | + uint32_t channel, q31_t *out) |
| 270 | + { |
| 271 | + for (size_t i = 0; i < header->num_channels; ++i) { |
| 272 | + if (channel == header->channels[i].chan_idx) { |
| 273 | + *out = values[i]; |
| 274 | + return 0; |
| 275 | + } |
| 276 | + } |
| 277 | + |
| 278 | + return -EINVAL; |
| 279 | + } |
| 280 | + |
| 281 | + /** |
| 282 | + * @brief Decode up to N samples from the buffer |
| 283 | + * |
| 284 | + * This function will never wrap frames. If 1 channel is available in the current frame and |
| 285 | + * @p max_count is 2, only 1 channel will be decoded and the frame iterator will be modified |
| 286 | + * so that the next call to decode will begin at the next frame. |
| 287 | + * |
| 288 | + * @param[in] buffer The buffer provided on the :c:struct:`rtio` context |
| 289 | + * @param[in] channel The channel to decode |
| 290 | + * @param[in,out] fit The current frame iterator |
| 291 | + * @param[in] max_count The maximum number of channels to decode. |
| 292 | + * @param[out] data_out The decoded data |
| 293 | + * @return 0 no more samples to decode |
| 294 | + * @return >0 the number of decoded frames |
| 295 | + * @return <0 on error |
| 296 | + */ |
| 297 | + static int decode(const uint8_t *buffer, uint32_t channel, uint32_t *fit, |
| 298 | + uint16_t max_count, void *data_out) |
| 299 | + { |
| 300 | + const struct adc_data_generic_header *header = |
| 301 | + (const struct adc_data_generic_header *)buffer; |
| 302 | + const q31_t *q = (const q31_t *)(buffer + compute_header_size(header->num_channels)); |
| 303 | + struct adc_data *data_out_q31 = (struct adc_data *)data_out; |
| 304 | + |
| 305 | + if (*fit != 0 || max_count < 1) { |
| 306 | + return -EINVAL; |
| 307 | + } |
| 308 | + |
| 309 | + data_out_q31->header.base_timestamp_ns = header->timestamp_ns; |
| 310 | + data_out_q31->header.reading_count = 1; |
| 311 | + data_out_q31->shift = header->shift; |
| 312 | + data_out_q31->readings[0].timestamp_delta = 0; |
| 313 | + |
| 314 | + *fit = 1; |
| 315 | + |
| 316 | + return get_q31_value(header, q, channel, &data_out_q31->readings[0].value); |
| 317 | + } |
| 318 | + |
| 319 | + const struct adc_decoder_api __adc_default_decoder = { |
| 320 | + .get_frame_count = get_frame_count, |
| 321 | + .get_size_info = adc_natively_supported_channel_size_info, |
| 322 | + .decode = decode, |
| 323 | +}; |
0 commit comments