├── CMakeLists.txt ├── Kconfig ├── README.md ├── ain-map.png ├── dts └── bindings │ └── zmk,analog-input.yaml ├── include └── zmk │ └── drivers │ └── analog_input.h ├── src └── analog_input.c └── zephyr └── module.yml /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | zephyr_library() 2 | 3 | zephyr_library_sources_ifdef(CONFIG_ANALOG_INPUT src/analog_input.c) 4 | zephyr_include_directories(include) 5 | 6 | zephyr_include_directories(${APPLICATION_SOURCE_DIR}/include) 7 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | # Sensor data simulator 2 | # 3 | # Copyright (c) 2019 Nordic Semiconductor 4 | # 5 | # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause 6 | # 7 | 8 | menuconfig ANALOG_INPUT 9 | bool "ANALOG_INPUT adc sensor" 10 | select ADC 11 | help 12 | Enable ANALOG_INPUT adc sensor. 13 | 14 | if ANALOG_INPUT 15 | 16 | config ANALOG_INPUT_WORKQUEUE_PRIORITY 17 | int "Workqueue priority" 18 | default 10 19 | help 20 | Priority of thread used by the workqueue 21 | 22 | config ANALOG_INPUT_WORKQUEUE_STACK_SIZE 23 | int "Workqueue stack size" 24 | default 1024 25 | help 26 | Stack size of thread used by the workqueue 27 | 28 | config ANALOG_INPUT_REPORT_INTERVAL_MIN 29 | int "ANALOG_INPUT's default minimum report rate" 30 | default 0 31 | help 32 | Default minimum report interval in milliseconds. 33 | Slow down input reporting for hid queue over the air. 34 | 35 | config ANALOG_INPUT_LOG_DBG_RAW 36 | bool "ANALOG_INPUT debug log for RAW mV" 37 | 38 | config ANALOG_INPUT_LOG_DBG_REPORT 39 | bool "ANALOG_INPUT debug log for REPORT value" 40 | 41 | module = ANALOG_INPUT 42 | module-str = ANALOG_INPUT 43 | source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" 44 | 45 | endif #ANALOG_INPUT 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZMK Analog Input Driver 2 | 3 | This driver groups ADC io channels into single input event for input subsystem. It provides config for thumbstick input with mid-point alignment, min-mav limitation, deadzone, sampling rate, reporting rate, multiplier, divisor, invertor, etc. 4 | 5 | > [!CAUTION] 6 | > This poll mode driver has relativley high power consumption, its not recommended for wireless builds. 7 | 8 | ## Installation 9 | 10 | Only GitHub actions builds are covered here. Local builds are different for each user, therefore it's not possible to cover all cases. 11 | 12 | Include this project on your ZMK's west manifest in `config/west.yml`: 13 | 14 | ```yml 15 | manifest: 16 | remotes: 17 | ... 18 | # START ##### 19 | - name: badjeff 20 | url-base: https://github.com/badjeff 21 | # END ####### 22 | projects: 23 | ... 24 | # START ##### 25 | - name: zmk-analog-input-driver 26 | remote: badjeff 27 | revision: main 28 | # END ####### 29 | ... 30 | ``` 31 | 32 | Now, update your `board.overlay` adding the necessary bits (update the pins for your board accordingly): 33 | 34 | ```dts 35 | #include 36 | /* Reference: https://docs.zephyrproject.org/apidoc/latest/group__input__events.html */ 37 | 38 | &adc { 39 | status = "okay"; 40 | }; 41 | 42 | / { 43 | anin0: analog_input_0 { 44 | compatible = "zmk,analog-input"; 45 | sampling-hz = <100>; 46 | x-ch { 47 | io-channels = <&adc 2>; // <--- see ain-map.png for nRF52840 48 | mv-mid = <1630>; 49 | mv-min-max = <1600>; 50 | mv-deadzone = <10>; 51 | scale-multiplier = <1>; 52 | scale-divisor = <70>; 53 | invert; 54 | evt-type = ; 55 | input-code = ; 56 | }; 57 | y-ch { 58 | io-channels = <&adc 3>; // <--- see ain-map.png for nRF52840 59 | mv-mid = <1630>; 60 | mv-min-max = <1600>; 61 | mv-deadzone = <10>; 62 | scale-multiplier = <3>; 63 | scale-divisor = <4>; 64 | invert; 65 | evt-type = ; 66 | input-code = ; 67 | 68 | /* enable report mdoe for gamepad axix or knob */ 69 | /* to only call input_report on quantquantized value is updated */ 70 | /* NOTE: mouse input does NOT need this */ 71 | report-on-change-only; 72 | 73 | }; 74 | }; 75 | }; 76 | ``` 77 | 78 | Now enable the driver config in your `.config` file (read the Kconfig file to find out all possible options): 79 | 80 | ```conf 81 | # Enable Analog Input 82 | CONFIG_ADC=y 83 | # Use async mode (Optional) 84 | CONFIG_ADC_ASYNC=y 85 | 86 | # Enable Analog Input Module 87 | CONFIG_ANALOG_INPUT=y 88 | # CONFIG_ANALOG_INPUT_LOG_LEVEL_DBG=y 89 | # CONFIG_ANALOG_INPUT_REPORT_INTERVAL_MIN=22 90 | 91 | # Enable logging for pre/post processed value 92 | CONFIG_ANALOG_INPUT_LOG_DBG_RAW=y 93 | CONFIG_ANALOG_INPUT_LOG_DBG_REPORT=y 94 | 95 | # Just in case, you don't RTFM 96 | CONFIG_INPUT=y 97 | ``` 98 | 99 | ## Troubleshooting 100 | 101 | *What if it just work 1 minute?* 102 | 103 | TL;DR: Set oversampling to zero at [here](https://github.com/zmkfirmware/zmk/blob/461f5c832fb8854d87dca54d113d306323697219/app/module/drivers/sensor/battery/battery_nrf_vddh.c#L90) in your zmk fork to use this module. 104 | 105 | If you are running on nrf52840 board and analog reading get stuck after some moment, you need to ground all `uint8_t adc_sequence::oversampling` to zero in your ZMK branch in respect to `oversampling` setting is unsupported by given ADC hardware in a specific mode. [Reference](https://docs.zephyrproject.org/apidoc/latest/structadc__sequence.html#a233e8b20b57bb2fdbebf2c85f076c802). 106 | -------------------------------------------------------------------------------- /ain-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badjeff/zmk-analog-input-driver/d07aedb66801598c0c278e60565bd8f6ab42f9b2/ain-map.png -------------------------------------------------------------------------------- /dts/bindings/zmk,analog-input.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 The ZMK Contributors 2 | # SPDX-License-Identifier: MIT 3 | description: | 4 | Input driver for ADC io channel 5 | 6 | compatible: "zmk,analog-input" 7 | 8 | properties: 9 | sampling-hz: 10 | type: int 11 | default: 100 12 | 13 | child-binding: 14 | description: Per-channel configuration settings 15 | properties: 16 | io-channels: 17 | type: phandle-array 18 | required: true 19 | mv-mid: 20 | type: int 21 | required: true 22 | mv-min-max: 23 | type: int 24 | required: true 25 | mv-deadzone: 26 | type: int 27 | default: 10 28 | invert: 29 | type: boolean 30 | report-on-change-only: 31 | type: boolean 32 | scale-multiplier: 33 | type: int 34 | default: 1 35 | scale-divisor: 36 | type: int 37 | default: 70 38 | evt-type: 39 | type: int 40 | required: true 41 | input-code: 42 | type: int 43 | required: true 44 | -------------------------------------------------------------------------------- /include/zmk/drivers/analog_input.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Nordic Semiconductor ASA 3 | * 4 | * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause 5 | */ 6 | 7 | #ifndef ZEPHYR_INCLUDE_ANALOG_INPUT_H_ 8 | #define ZEPHYR_INCLUDE_ANALOG_INPUT_H_ 9 | 10 | /** 11 | * @file analog_input.h 12 | * 13 | * @brief Header file for the analog_input driver. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | struct analog_input_data { 27 | const struct device *dev; 28 | struct adc_sequence as; 29 | #if CONFIG_ADC_ASYNC 30 | struct k_poll_signal async_sig; 31 | struct k_poll_event async_evt; 32 | #endif 33 | uint16_t *as_buff; 34 | int32_t *delta; 35 | int32_t *prev; 36 | struct k_work_delayable init_work; 37 | int async_init_step; 38 | bool ready; 39 | 40 | uint32_t sampling_hz; 41 | bool enabled; 42 | bool actived; 43 | 44 | struct k_work sampling_work; 45 | struct k_timer sampling_timer; 46 | int err; 47 | }; 48 | 49 | struct analog_input_io_channel { 50 | struct adc_dt_spec adc_channel; 51 | uint16_t mv_mid; 52 | uint16_t mv_min_max; 53 | uint8_t mv_deadzone; 54 | bool invert; 55 | bool report_on_change_only; 56 | uint16_t scale_multiplier; 57 | uint16_t scale_divisor; 58 | uint8_t evt_type; 59 | uint8_t input_code; 60 | }; 61 | 62 | struct analog_input_config { 63 | uint32_t sampling_hz; 64 | uint8_t io_channels_len; 65 | struct analog_input_io_channel io_channels[]; 66 | }; 67 | 68 | /* Helper macros used to convert sensor values. */ 69 | #define ANALOG_INPUT_SVALUE_TO_SAMPLING_HZ(svalue) ((uint32_t)(svalue).val1) 70 | #define ANALOG_INPUT_SVALUE_TO_ENABLE(svalue) ((uint32_t)(svalue).val1) 71 | #define ANALOG_INPUT_SVALUE_TO_ACTIVE(svalue) ((uint32_t)(svalue).val1) 72 | 73 | /** @brief Sensor specific attributes of ANALOG_INPUT. */ 74 | enum analog_input_attribute { 75 | 76 | // setup polling timer 77 | ANALOG_INPUT_ATTR_SAMPLING_HZ, 78 | 79 | // ENABLE sampling timer 80 | ANALOG_INPUT_ATTR_ENABLE, 81 | 82 | // ACTIVE input reporting 83 | // or else, manually call sample_fetch & channel_get via sensor api. 84 | ANALOG_INPUT_ATTR_ACTIVE, 85 | 86 | }; 87 | 88 | #ifdef __cplusplus 89 | } 90 | #endif 91 | 92 | /** 93 | * @} 94 | */ 95 | 96 | #endif /* ZEPHYR_INCLUDE_ANALOG_INPUT_H_ */ 97 | -------------------------------------------------------------------------------- /src/analog_input.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 The ZMK Contributors 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #define DT_DRV_COMPAT zmk_analog_input 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include //for abs() 14 | #include // for CLAMP 15 | 16 | #include 17 | LOG_MODULE_REGISTER(ANALOG_INPUT, CONFIG_ANALOG_INPUT_LOG_LEVEL); 18 | 19 | #include 20 | 21 | static int analog_input_report_data(const struct device *dev) { 22 | struct analog_input_data *data = dev->data; 23 | const struct analog_input_config *config = dev->config; 24 | 25 | if (unlikely(!data->ready)) { 26 | LOG_WRN("Device is not initialized yet"); 27 | return -EBUSY; 28 | } 29 | 30 | #if CONFIG_ANALOG_INPUT_REPORT_INTERVAL_MIN > 0 31 | static int64_t last_smp_time = 0; 32 | static int64_t last_rpt_time = 0; 33 | int64_t now = k_uptime_get(); 34 | #endif 35 | 36 | struct adc_sequence* as = &data->as; 37 | 38 | for (uint8_t i = 0; i < config->io_channels_len; i++) { 39 | struct analog_input_io_channel ch_cfg = (struct analog_input_io_channel)config->io_channels[i]; 40 | const struct device* adc = ch_cfg.adc_channel.dev; 41 | 42 | if (i == 0) { 43 | #ifdef CONFIG_ADC_ASYNC 44 | int err = adc_read_async(adc, as, &data->async_sig); 45 | if (err < 0) { 46 | LOG_ERR("AIN%u read_async returned %d", i, err); 47 | return err; 48 | } 49 | err = k_poll(&data->async_evt, 1, K_FOREVER); 50 | if (err < 0) { 51 | LOG_ERR("AIN%u k_poll returned %d", i, err); 52 | return err; 53 | } 54 | if (!data->async_evt.signal->signaled) { 55 | return 0; 56 | } 57 | data->async_evt.signal->signaled = 0; 58 | data->async_evt.state = K_POLL_STATE_NOT_READY; 59 | #else 60 | int err = adc_read(adc, as); 61 | if (err < 0) { 62 | LOG_ERR("AIN%u read returned %d", i, err); 63 | return err; 64 | } 65 | #endif 66 | } 67 | 68 | int32_t raw = data->as_buff[i]; 69 | int32_t mv = raw; 70 | adc_raw_to_millivolts(adc_ref_internal(adc), ADC_GAIN_1_6, as->resolution, &mv); 71 | #if IS_ENABLED(CONFIG_ANALOG_INPUT_LOG_DBG_RAW) 72 | LOG_DBG("AIN%u raw: %d mv: %d", ch_cfg.adc_channel.channel_id, raw, mv); 73 | #endif 74 | 75 | int16_t v = mv - ch_cfg.mv_mid; 76 | int16_t dz = ch_cfg.mv_deadzone; 77 | if (dz) { 78 | if (v > 0) { 79 | if (v < dz) v = 0; else v -= dz; 80 | } 81 | if (v < 0) { 82 | if (v > -dz) v = 0; else v += dz; 83 | } 84 | } 85 | uint16_t mm = ch_cfg.mv_min_max; 86 | if (mm) { 87 | if (v > 0 && v > mm) v = mm; 88 | if (v < 0 && v < -mm) v = -mm; 89 | } 90 | 91 | if (ch_cfg.invert) v *= -1; 92 | v = (int16_t)((v * ch_cfg.scale_multiplier) / ch_cfg.scale_divisor); 93 | 94 | if (ch_cfg.report_on_change_only) { 95 | // track raw value to compare until next report interval 96 | data->delta[i] = v; 97 | } 98 | else { 99 | // accumulate delta until report in next iteration 100 | int32_t delta = data->delta[i]; 101 | int32_t dv = delta + v; 102 | data->delta[i] = dv; 103 | } 104 | } 105 | 106 | // First read is setup as calibration 107 | as->calibrate = false; 108 | 109 | #if CONFIG_ANALOG_INPUT_REPORT_INTERVAL_MIN > 0 110 | // purge accumulated delta, if last sampled had not been reported on last report tick 111 | if (now - last_smp_time >= CONFIG_ANALOG_INPUT_REPORT_INTERVAL_MIN) { 112 | for (uint8_t i = 0; i < config->io_channels_len; i++) { 113 | data->delta[i] = 0; 114 | data->prev[i] = 0; 115 | } 116 | } 117 | last_smp_time = now; 118 | #endif 119 | 120 | #if CONFIG_ANALOG_INPUT_REPORT_INTERVAL_MIN > 0 121 | // strict to report inerval 122 | if (now - last_rpt_time < CONFIG_ANALOG_INPUT_REPORT_INTERVAL_MIN) { 123 | return 0; 124 | } 125 | #endif 126 | 127 | if (!data->actived) { 128 | return 0; 129 | } 130 | 131 | int8_t idx_to_sync = -1; 132 | for (uint8_t i = config->io_channels_len - 1; i >= 0; i--) { 133 | int32_t dv = data->delta[i]; 134 | int32_t pv = data->prev[i]; 135 | if (dv != pv) { 136 | idx_to_sync = i; 137 | break; 138 | } 139 | } 140 | 141 | for (uint8_t i = 0; i < config->io_channels_len; i++) { 142 | struct analog_input_io_channel ch_cfg = (struct analog_input_io_channel)config->io_channels[i]; 143 | // LOG_DBG("AIN%u get delta AGAIN", i); 144 | int32_t dv = data->delta[i]; 145 | int32_t pv = data->prev[i]; 146 | if (dv != pv) { 147 | #if CONFIG_ANALOG_INPUT_REPORT_INTERVAL_MIN > 0 148 | last_rpt_time = now; 149 | #endif 150 | data->delta[i] = 0; 151 | if (ch_cfg.report_on_change_only) { 152 | data->prev[i] = dv; 153 | } 154 | 155 | #if IS_ENABLED(CONFIG_ANALOG_INPUT_LOG_DBG_REPORT) 156 | LOG_DBG("input_report %u rv: %d e:%d c:%d", i, dv, ch_cfg.evt_type, ch_cfg.input_code); 157 | #endif 158 | input_report(dev, ch_cfg.evt_type, ch_cfg.input_code, dv, i == idx_to_sync, K_NO_WAIT); 159 | } 160 | } 161 | return 0; 162 | } 163 | 164 | K_THREAD_STACK_DEFINE(analog_input_q_stack, CONFIG_ANALOG_INPUT_WORKQUEUE_STACK_SIZE); 165 | 166 | static struct k_work_q analog_input_work_q; 167 | 168 | static void sampling_work_handler(struct k_work *work) { 169 | struct analog_input_data *data = CONTAINER_OF(work, struct analog_input_data, sampling_work); 170 | // LOG_DBG("sampling work triggered"); 171 | analog_input_report_data(data->dev); 172 | } 173 | 174 | static void sampling_timer_handler(struct k_timer *timer) { 175 | struct analog_input_data *data = CONTAINER_OF(timer, struct analog_input_data, sampling_timer); 176 | // LOG_DBG("sampling timer triggered"); 177 | k_work_submit_to_queue(&analog_input_work_q, &data->sampling_work); 178 | k_work_submit(&data->sampling_work); 179 | } 180 | 181 | static int active_set_value(const struct device *dev, bool active) { 182 | struct analog_input_data *data = dev->data; 183 | if (data->actived == active) return 0; 184 | LOG_DBG("%d", active ? 1 : 0); 185 | data->actived = active; 186 | return 0; 187 | } 188 | 189 | static int sample_hz_set_value(const struct device *dev, uint32_t hz) { 190 | struct analog_input_data *data = dev->data; 191 | 192 | if (unlikely(!data->ready)) { 193 | LOG_DBG("Device is not initialized yet"); 194 | return -EBUSY; 195 | } 196 | 197 | if (data->enabled) { 198 | LOG_DBG("Device is busy, would not update sampleing rate in enable state."); 199 | return -EBUSY; 200 | } 201 | 202 | LOG_DBG("%d", hz); 203 | data->sampling_hz = hz; 204 | return 0; 205 | } 206 | 207 | static int enable_set_value(const struct device *dev, bool enable) { 208 | struct analog_input_data *data = dev->data; 209 | // const struct tb6612fng_config *config = dev->config; 210 | 211 | if (unlikely(!data->ready)) { 212 | LOG_DBG("Device is not initialized yet"); 213 | return -EBUSY; 214 | } 215 | 216 | if (data->enabled == enable) { 217 | return 0; 218 | } 219 | 220 | LOG_DBG("%d", enable ? 1 : 0); 221 | if (enable) { 222 | if (data->sampling_hz != 0) { 223 | uint32_t usec = 1000000UL / data->sampling_hz; 224 | k_timer_start(&data->sampling_timer, K_USEC(usec), K_USEC(usec)); 225 | } else { 226 | k_timer_start(&data->sampling_timer, K_NO_WAIT, K_NO_WAIT); 227 | } 228 | data->enabled = true; 229 | } 230 | else { 231 | k_timer_stop(&data->sampling_timer); 232 | data->enabled = false; 233 | } 234 | 235 | return 0; 236 | } 237 | 238 | static void analog_input_async_init(struct k_work *work) { 239 | struct k_work_delayable *work_delayable = (struct k_work_delayable *)work; 240 | struct analog_input_data *data = CONTAINER_OF(work_delayable, 241 | struct analog_input_data, init_work); 242 | const struct device *dev = data->dev; 243 | const struct analog_input_config *config = dev->config; 244 | 245 | // LOG_DBG("ANALOG_INPUT async init"); 246 | uint32_t ch_mask = 0; 247 | 248 | for (uint8_t i = 0; i < config->io_channels_len; i++) { 249 | struct analog_input_io_channel ch_cfg = (struct analog_input_io_channel)config->io_channels[i]; 250 | const struct device* adc = ch_cfg.adc_channel.dev; 251 | uint8_t channel_id = ch_cfg.adc_channel.channel_id; 252 | 253 | struct adc_channel_cfg channel_cfg = { 254 | .gain = ADC_GAIN_1_6, 255 | .reference = ADC_REF_INTERNAL, 256 | .acquisition_time = ADC_ACQ_TIME_DEFAULT, 257 | .channel_id = channel_id, 258 | #ifdef CONFIG_ADC_CONFIGURABLE_INPUTS 259 | #ifdef CONFIG_ADC_NRFX_SAADC 260 | .input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 + channel_id, 261 | #else /* CONFIG_ADC_NRFX_SAADC */ 262 | .input_positive = channel_id, 263 | #endif /* CONFIG_ADC_NRFX_SAADC */ 264 | #endif /* CONFIG_ADC_CONFIGURABLE_INPUTS */ 265 | }; 266 | 267 | ch_mask |= BIT(channel_id); 268 | 269 | if (!device_is_ready(adc)) { 270 | LOG_ERR("AIN%u device is not ready %s", i, adc->name); 271 | continue; 272 | } 273 | 274 | int err = adc_channel_setup(adc, &channel_cfg); 275 | if (err < 0) { 276 | LOG_ERR("AIN%u setup returned %d", i, err); 277 | } 278 | } 279 | 280 | uint16_t delta_size = config->io_channels_len * sizeof(int32_t); 281 | data->delta = malloc(delta_size); 282 | memset(data->delta, 0, delta_size); 283 | 284 | uint16_t prev_size = config->io_channels_len * sizeof(int32_t); 285 | data->prev = malloc(prev_size); 286 | memset(data->prev, 0, prev_size); 287 | 288 | uint16_t buff_size = config->io_channels_len * sizeof(uint16_t); 289 | data->as_buff = malloc(buff_size); 290 | memset(data->as_buff, 0, buff_size); 291 | 292 | data->as = (struct adc_sequence){ 293 | .channels = ch_mask, 294 | .buffer = data->as_buff, 295 | .buffer_size = buff_size, 296 | .oversampling = 0, 297 | .resolution = 12, 298 | .calibrate = true, 299 | }; 300 | 301 | #ifdef CONFIG_ADC_ASYNC 302 | k_poll_signal_init(&data->async_sig); 303 | struct k_poll_event async_evt = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, 304 | K_POLL_MODE_NOTIFY_ONLY, 305 | &data->async_sig); 306 | data->async_evt = async_evt; 307 | #endif 308 | 309 | data->ready = true; 310 | 311 | k_work_init(&data->sampling_work, sampling_work_handler); 312 | k_work_queue_start(&analog_input_work_q, 313 | analog_input_q_stack, K_THREAD_STACK_SIZEOF(analog_input_q_stack), 314 | CONFIG_ANALOG_INPUT_WORKQUEUE_PRIORITY, NULL); 315 | 316 | k_timer_init(&data->sampling_timer, sampling_timer_handler, NULL); 317 | 318 | sample_hz_set_value(dev, config->sampling_hz); 319 | active_set_value(dev, true); 320 | if (data->sampling_hz) { 321 | enable_set_value(dev, true); 322 | } 323 | 324 | } 325 | 326 | static int analog_input_init(const struct device *dev) { 327 | struct analog_input_data *data = dev->data; 328 | // const struct analog_input_config *config = dev->config; 329 | int err = 0; 330 | 331 | data->dev = dev; 332 | k_work_init_delayable(&data->init_work, analog_input_async_init); 333 | k_work_schedule(&data->init_work, K_MSEC(1)); 334 | 335 | return err; 336 | } 337 | 338 | static int analog_input_attr_set(const struct device *dev, enum sensor_channel chan, 339 | enum sensor_attribute attr, const struct sensor_value *val) { 340 | struct analog_input_data *data = dev->data; 341 | // const struct analog_input_config *config = dev->config; 342 | int err; 343 | 344 | if (chan != SENSOR_CHAN_ALL) { 345 | LOG_DBG("Selected channel is not supported: %d.", chan); 346 | return -ENOTSUP; 347 | } 348 | if (unlikely(!data->ready)) { 349 | LOG_DBG("Device is not initialized yet"); 350 | return -EBUSY; 351 | } 352 | 353 | switch ((uint32_t)attr) { 354 | case ANALOG_INPUT_ATTR_SAMPLING_HZ: 355 | err = sample_hz_set_value(dev, ANALOG_INPUT_SVALUE_TO_SAMPLING_HZ(*val)); 356 | break; 357 | 358 | case ANALOG_INPUT_ATTR_ENABLE: 359 | err = enable_set_value(dev, ANALOG_INPUT_SVALUE_TO_ENABLE(*val)); 360 | break; 361 | 362 | case ANALOG_INPUT_ATTR_ACTIVE: 363 | err = active_set_value(dev, ANALOG_INPUT_SVALUE_TO_ACTIVE(*val)); 364 | break; 365 | 366 | default: 367 | LOG_ERR("Unknown attribute"); 368 | err = -ENOTSUP; 369 | } 370 | 371 | return err; 372 | } 373 | 374 | static int analog_input_sample_fetch(const struct device *dev, enum sensor_channel chan) { 375 | struct analog_input_data *data = dev->data; 376 | // const struct analog_input_config *config = dev->config; 377 | 378 | if (chan != SENSOR_CHAN_ALL) { 379 | LOG_DBG("Selected channel is not supported: %d.", chan); 380 | return -ENOTSUP; 381 | } 382 | if (unlikely(!data->ready)) { 383 | LOG_DBG("Device is not initialized yet"); 384 | return -EBUSY; 385 | } 386 | 387 | int err = analog_input_report_data(data->dev); 388 | if (err < 0) { 389 | LOG_ERR("analog_input_report_data returned %d", err); 390 | return err; 391 | } 392 | 393 | return 0; 394 | } 395 | 396 | static int analog_input_channel_get(const struct device *dev, enum sensor_channel chan, 397 | struct sensor_value *val) { 398 | struct analog_input_data *data = dev->data; 399 | const struct analog_input_config *config = dev->config; 400 | 401 | if (unlikely(chan != SENSOR_CHAN_ALL)) { 402 | LOG_DBG("Selected channel is not supported: %d.", chan); 403 | return -ENOTSUP; 404 | } 405 | if (unlikely(!data->ready)) { 406 | LOG_DBG("Device is not initialized yet"); 407 | return -EBUSY; 408 | } 409 | 410 | for (uint8_t i = 0; i < config->io_channels_len; i++) { 411 | struct analog_input_io_channel ch_cfg = (struct analog_input_io_channel)config->io_channels[i]; 412 | if (!ch_cfg.report_on_change_only) { 413 | continue; 414 | } 415 | if (i == 0) val->val1 = data->delta[i]; 416 | else if (i == 1) val->val2 = data->delta[i]; 417 | } 418 | 419 | return 0; 420 | } 421 | 422 | static const struct sensor_driver_api analog_input_driver_api = { 423 | .attr_set = analog_input_attr_set, 424 | .sample_fetch = analog_input_sample_fetch, 425 | .channel_get = analog_input_channel_get, 426 | }; 427 | 428 | #define TRANSFORMED_IO_CHANNEL_ENTRY(node_id) \ 429 | { \ 430 | .adc_channel = ADC_DT_SPEC_GET_BY_IDX(node_id, 0), \ 431 | .mv_mid = DT_PROP(node_id, mv_mid), \ 432 | .mv_min_max = DT_PROP(node_id, mv_min_max), \ 433 | .mv_deadzone = DT_PROP(node_id, mv_deadzone), \ 434 | .invert = DT_PROP(node_id, invert), \ 435 | .report_on_change_only = DT_PROP(node_id, report_on_change_only), \ 436 | .scale_multiplier = DT_PROP(node_id, scale_multiplier), \ 437 | .scale_divisor = DT_PROP(node_id, scale_divisor), \ 438 | .evt_type = DT_PROP(node_id, evt_type), \ 439 | .input_code = DT_PROP(node_id, input_code), \ 440 | } 441 | 442 | #define ANIN_IOC_CHILD_LEN_PLUS_ONE(node) 1 + 443 | 444 | #define ANALOG_INPUT_DEFINE(n) \ 445 | static struct analog_input_data data##n = { \ 446 | }; \ 447 | static const struct analog_input_config config##n = { \ 448 | .sampling_hz = DT_PROP(DT_DRV_INST(n), sampling_hz), \ 449 | .io_channels_len = (DT_FOREACH_CHILD(DT_DRV_INST(n), ANIN_IOC_CHILD_LEN_PLUS_ONE) 0), \ 450 | .io_channels = { DT_INST_FOREACH_CHILD_SEP(n, TRANSFORMED_IO_CHANNEL_ENTRY, (, )) }, \ 451 | }; \ 452 | \ 453 | DEVICE_DT_INST_DEFINE(n, analog_input_init, NULL, &data##n, &config##n, POST_KERNEL, \ 454 | CONFIG_SENSOR_INIT_PRIORITY, &analog_input_driver_api); 455 | 456 | DT_INST_FOREACH_STATUS_OKAY(ANALOG_INPUT_DEFINE) 457 | -------------------------------------------------------------------------------- /zephyr/module.yml: -------------------------------------------------------------------------------- 1 | build: 2 | cmake: . 3 | kconfig: Kconfig 4 | settings: 5 | dts_root: . 6 | --------------------------------------------------------------------------------