blob: b771d47c70f50da7fd9573ccef7dc24734c907ab [file] [log] [blame]
/*
* Copyright (c) 2018, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/drivers/pinctrl.h>
#include <soc.h>
#include <nrfx_qdec.h>
#include <hal/nrf_gpio.h>
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
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 {
int32_t acc;
sensor_trigger_handler_t data_ready_handler;
const struct sensor_trigger *data_ready_trigger;
};
struct qdec_nrfx_config {
nrfx_qdec_t qdec;
nrfx_qdec_config_t config;
void (*irq_connect)(void);
const struct pinctrl_dev_config *pcfg;
uint32_t enable_pin;
int32_t steps;
};
static void accumulate(struct qdec_nrfx_data *data, int32_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(const struct device *dev,
enum sensor_channel chan)
{
const struct qdec_nrfx_config *config = dev->config;
struct qdec_nrfx_data *data = dev->data;
int32_t acc;
uint32_t accdbl;
if ((chan != SENSOR_CHAN_ALL) && (chan != SENSOR_CHAN_ROTATION)) {
return -ENOTSUP;
}
nrfx_qdec_accumulators_read(&config->qdec, &acc, &accdbl);
accumulate(data, acc);
return 0;
}
static int qdec_nrfx_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
struct qdec_nrfx_data *data = dev->data;
const struct qdec_nrfx_config *config = dev->config;
unsigned int key;
int32_t acc;
if (chan != SENSOR_CHAN_ROTATION) {
return -ENOTSUP;
}
key = irq_lock();
acc = data->acc;
data->acc = 0;
irq_unlock(key);
val->val1 = (acc * FULL_ANGLE) / config->steps;
val->val2 = (acc * FULL_ANGLE) - (val->val1 * config->steps);
if (val->val2 != 0) {
val->val2 *= 1000000;
val->val2 /= config->steps;
}
return 0;
}
static int qdec_nrfx_trigger_set(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
struct qdec_nrfx_data *data = dev->data;
unsigned int key;
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;
data->data_ready_trigger = trig;
irq_unlock(key);
return 0;
}
static void qdec_nrfx_event_handler(nrfx_qdec_event_t event, void *p_context)
{
const struct device *dev = p_context;
struct qdec_nrfx_data *dev_data = dev->data;
sensor_trigger_handler_t handler;
const struct sensor_trigger *trig;
unsigned int key;
switch (event.type) {
case NRF_QDEC_EVENT_REPORTRDY:
accumulate(dev_data, event.data.report.acc);
key = irq_lock();
handler = dev_data->data_ready_handler;
trig = dev_data->data_ready_trigger;
irq_unlock(key);
if (handler) {
handler(dev, trig);
}
break;
default:
LOG_ERR("unhandled event (0x%x)", event.type);
break;
}
}
static void qdec_nrfx_gpio_ctrl(const struct device *dev, bool enable)
{
const struct qdec_nrfx_config *config = dev->config;
if (config->enable_pin != NRF_QDEC_PIN_NOT_CONNECTED) {
uint32_t val = (enable)?(0):(1);
nrf_gpio_pin_write(config->enable_pin, val);
nrf_gpio_cfg_output(config->enable_pin);
}
}
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,
};
#ifdef CONFIG_PM_DEVICE
static int qdec_nrfx_pm_action(const struct device *dev,
enum pm_device_action action)
{
const struct qdec_nrfx_config *config = dev->config;
int ret = 0;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
ret = pinctrl_apply_state(config->pcfg,
PINCTRL_STATE_DEFAULT);
if (ret < 0) {
return ret;
}
qdec_nrfx_gpio_ctrl(dev, true);
nrfx_qdec_enable(&config->qdec);
break;
case PM_DEVICE_ACTION_TURN_OFF:
/* device must be uninitialized */
nrfx_qdec_uninit(&config->qdec);
ret = pinctrl_apply_state(config->pcfg,
PINCTRL_STATE_SLEEP);
if (ret < 0) {
return ret;
}
break;
case PM_DEVICE_ACTION_SUSPEND:
/* device must be suspended */
nrfx_qdec_disable(&config->qdec);
qdec_nrfx_gpio_ctrl(dev, false);
ret = pinctrl_apply_state(config->pcfg,
PINCTRL_STATE_SLEEP);
if (ret < 0) {
return ret;
}
break;
default:
return -ENOTSUP;
}
return ret;
}
#endif /* CONFIG_PM_DEVICE */
static int qdec_nrfx_init(const struct device *dev)
{
const struct qdec_nrfx_config *dev_config = dev->config;
dev_config->irq_connect();
int err = pinctrl_apply_state(dev_config->pcfg, PINCTRL_STATE_DEFAULT);
if (err < 0) {
return err;
}
nrfx_err_t nerr = nrfx_qdec_init(&dev_config->qdec,
&dev_config->config,
qdec_nrfx_event_handler,
(void *)dev);
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(dev, true);
nrfx_qdec_enable(&dev_config->qdec);
return 0;
}
#define QDEC(idx) DT_NODELABEL(qdec##idx)
#define QDEC_PROP(idx, prop) DT_PROP(QDEC(idx), prop)
#define SENSOR_NRFX_QDEC_DEVICE(idx) \
NRF_DT_CHECK_NODE_HAS_PINCTRL_SLEEP(QDEC(idx)); \
BUILD_ASSERT(QDEC_PROP(idx, steps) > 0, \
"Wrong QDEC"#idx" steps setting in dts. Only positive number valid"); \
BUILD_ASSERT(QDEC_PROP(idx, steps) <= 2048, \
"Wrong QDEC"#idx" steps setting in dts. Overflow possible"); \
static void irq_connect##idx(void) \
{ \
IRQ_CONNECT(DT_IRQN(QDEC(idx)), DT_IRQ(QDEC(idx), priority), \
nrfx_isr, nrfx_qdec_##idx##_irq_handler, 0); \
} \
static struct qdec_nrfx_data qdec_##idx##_data; \
PINCTRL_DT_DEFINE(QDEC(idx)); \
static struct qdec_nrfx_config qdec_##idx##_config = { \
.qdec = NRFX_QDEC_INSTANCE(idx), \
.config = { \
.reportper = NRF_QDEC_REPORTPER_40, \
.sampleper = NRF_QDEC_SAMPLEPER_2048US, \
.skip_gpio_cfg = true, \
.skip_psel_cfg = true, \
.ledpre = QDEC_PROP(idx, led_pre), \
.ledpol = NRF_QDEC_LEPOL_ACTIVE_HIGH, \
}, \
.irq_connect = irq_connect##idx, \
.pcfg = PINCTRL_DT_DEV_CONFIG_GET(QDEC(idx)), \
.enable_pin = DT_PROP_OR(QDEC(idx), enable_pin, NRF_QDEC_PIN_NOT_CONNECTED), \
.steps = QDEC_PROP(idx, steps), \
}; \
PM_DEVICE_DT_DEFINE(QDEC(idx), qdec_nrfx_pm_action); \
SENSOR_DEVICE_DT_DEFINE(QDEC(idx), \
qdec_nrfx_init, \
PM_DEVICE_DT_GET(QDEC(idx)), \
&qdec_##idx##_data, \
&qdec_##idx##_config, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&qdec_nrfx_driver_api)
#if DT_NODE_HAS_STATUS(DT_NODELABEL(qdec0), okay)
SENSOR_NRFX_QDEC_DEVICE(0);
#endif
#if DT_NODE_HAS_STATUS(DT_NODELABEL(qdec1), okay)
SENSOR_NRFX_QDEC_DEVICE(1);
#endif