/* * Copyright (c) 2018, Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include LOG_MODULE_REGISTER(qdec_nrfx, CONFIG_SENSOR_LOG_LEVEL); #define DT_DRV_COMPAT nordic_nrf_qdec #define FULL_ANGLE 360 /* limit range to avoid overflow when converting steps to degrees */ #define ACC_MAX (INT_MAX / FULL_ANGLE) #define ACC_MIN (INT_MIN / FULL_ANGLE) struct qdec_nrfx_data { s32_t acc; sensor_trigger_handler_t data_ready_handler; #ifdef CONFIG_DEVICE_POWER_MANAGEMENT u32_t pm_state; #endif }; static struct qdec_nrfx_data qdec_nrfx_data; DEVICE_DECLARE(qdec_nrfx); static void accumulate(struct qdec_nrfx_data *data, int16_t acc) { unsigned int key = irq_lock(); bool overflow = ((acc > 0) && (ACC_MAX - acc < data->acc)) || ((acc < 0) && (ACC_MIN - acc > data->acc)); if (!overflow) { data->acc += acc; } irq_unlock(key); } static int qdec_nrfx_sample_fetch(struct device *dev, enum sensor_channel chan) { struct qdec_nrfx_data *data = &qdec_nrfx_data; int16_t acc; int16_t accdbl; ARG_UNUSED(dev); LOG_DBG(""); if ((chan != SENSOR_CHAN_ALL) && (chan != SENSOR_CHAN_ROTATION)) { return -ENOTSUP; } nrfx_qdec_accumulators_read(&acc, &accdbl); accumulate(data, acc); return 0; } static int qdec_nrfx_channel_get(struct device *dev, enum sensor_channel chan, struct sensor_value *val) { struct qdec_nrfx_data *data = &qdec_nrfx_data; unsigned int key; s32_t acc; const s32_t steps = DT_INST_PROP(0, steps); ARG_UNUSED(dev); LOG_DBG(""); if (chan != SENSOR_CHAN_ROTATION) { return -ENOTSUP; } key = irq_lock(); acc = data->acc; data->acc = 0; irq_unlock(key); BUILD_ASSERT(steps > 0, "only positive number valid"); BUILD_ASSERT(steps <= 2148, "overflow possible"); val->val1 = (acc * FULL_ANGLE) / steps; val->val2 = (acc * FULL_ANGLE) - (val->val1 * steps); if (val->val2 != 0) { val->val2 *= 1000000; val->val2 /= steps; } return 0; } static int qdec_nrfx_trigger_set(struct device *dev, const struct sensor_trigger *trig, sensor_trigger_handler_t handler) { struct qdec_nrfx_data *data = &qdec_nrfx_data; unsigned int key; ARG_UNUSED(dev); LOG_DBG(""); if (trig->type != SENSOR_TRIG_DATA_READY) { return -ENOTSUP; } if ((trig->chan != SENSOR_CHAN_ALL) && (trig->chan != SENSOR_CHAN_ROTATION)) { return -ENOTSUP; } key = irq_lock(); data->data_ready_handler = handler; irq_unlock(key); return 0; } static void qdec_nrfx_event_handler(nrfx_qdec_event_t event) { sensor_trigger_handler_t handler; unsigned int key; switch (event.type) { case NRF_QDEC_EVENT_REPORTRDY: accumulate(&qdec_nrfx_data, event.data.report.acc); key = irq_lock(); handler = qdec_nrfx_data.data_ready_handler; irq_unlock(key); if (handler) { struct sensor_trigger trig = { .type = SENSOR_TRIG_DATA_READY, .chan = SENSOR_CHAN_ROTATION, }; handler(DEVICE_GET(qdec_nrfx), &trig); } break; default: LOG_ERR("unhandled event (0x%x)", event.type); break; } } static void qdec_nrfx_gpio_ctrl(bool enable) { #if DT_INST_NODE_HAS_PROP(0, enable_pin) uint32_t val = (enable)?(0):(1); nrf_gpio_pin_write(DT_INST_PROP(0, enable_pin), val); nrf_gpio_cfg_output(DT_INST_PROP(0, enable_pin)); #endif } static int qdec_nrfx_init(struct device *dev) { static const nrfx_qdec_config_t config = { .reportper = NRF_QDEC_REPORTPER_40, .sampleper = NRF_QDEC_SAMPLEPER_2048us, .psela = DT_INST_PROP(0, a_pin), .pselb = DT_INST_PROP(0, b_pin), #if DT_INST_NODE_HAS_PROP(0, led_pin) .pselled = DT_INST_PROP(0, led_pin), #else .pselled = 0xFFFFFFFF, /* disabled */ #endif .ledpre = DT_INST_PROP(0, led_pre), .ledpol = NRF_QDEC_LEPOL_ACTIVE_HIGH, .interrupt_priority = NRFX_QDEC_DEFAULT_CONFIG_IRQ_PRIORITY, .dbfen = 0, /* disabled */ .sample_inten = 0, /* disabled */ }; nrfx_err_t nerr; LOG_DBG(""); IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), nrfx_isr, nrfx_qdec_irq_handler, 0); nerr = nrfx_qdec_init(&config, qdec_nrfx_event_handler); if (nerr == NRFX_ERROR_INVALID_STATE) { LOG_ERR("qdec already in use"); return -EBUSY; } else if (nerr != NRFX_SUCCESS) { LOG_ERR("failed to initialize qdec"); return -EFAULT; } qdec_nrfx_gpio_ctrl(true); nrfx_qdec_enable(); #ifdef CONFIG_DEVICE_POWER_MANAGEMENT struct qdec_nrfx_data *data = &qdec_nrfx_data; data->pm_state = DEVICE_PM_ACTIVE_STATE; #endif return 0; } #ifdef CONFIG_DEVICE_POWER_MANAGEMENT static int qdec_nrfx_pm_get_state(struct qdec_nrfx_data *data, u32_t *state) { unsigned int key = irq_lock(); *state = data->pm_state; irq_unlock(key); return 0; } static int qdec_nrfx_pm_set_state(struct qdec_nrfx_data *data, u32_t new_state) { u32_t old_state; unsigned int key; key = irq_lock(); old_state = data->pm_state; irq_unlock(key); if (old_state == new_state) { /* leave unchanged */ return 0; } if (old_state == DEVICE_PM_ACTIVE_STATE) { /* device must be suspended */ nrfx_qdec_disable(); qdec_nrfx_gpio_ctrl(false); } if (new_state == DEVICE_PM_OFF_STATE) { /* device must be uninitialized */ nrfx_qdec_uninit(); } if (new_state == DEVICE_PM_ACTIVE_STATE) { qdec_nrfx_gpio_ctrl(true); nrfx_qdec_enable(); } /* record the new state */ key = irq_lock(); data->pm_state = new_state; irq_unlock(key); return 0; } static int qdec_nrfx_pm_control(struct device *dev, u32_t ctrl_command, void *context, device_pm_cb cb, void *arg) { struct qdec_nrfx_data *data = &qdec_nrfx_data; int err; LOG_DBG(""); switch (ctrl_command) { case DEVICE_PM_GET_POWER_STATE: err = qdec_nrfx_pm_get_state(data, context); break; case DEVICE_PM_SET_POWER_STATE: err = qdec_nrfx_pm_set_state(data, *((u32_t *)context)); break; default: err = -ENOTSUP; break; } if (cb) { cb(dev, err, context, arg); } return err; } #endif /* CONFIG_DEVICE_POWER_MANAGEMENT */ static const struct sensor_driver_api qdec_nrfx_driver_api = { .sample_fetch = qdec_nrfx_sample_fetch, .channel_get = qdec_nrfx_channel_get, .trigger_set = qdec_nrfx_trigger_set, }; DEVICE_DEFINE(qdec_nrfx, DT_INST_LABEL(0), qdec_nrfx_init, qdec_nrfx_pm_control, NULL, NULL, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &qdec_nrfx_driver_api);