blob: 56e31f357f43a6a3d0111bb4258b865b334de9b8 [file] [log] [blame]
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdint.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <zephyr/arch/arch_interface.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#define EMIOS_CHANNEL_COUNT 2U
#define EMIOS_CW_CH_IDX 0
#define EMIOS_CCW_CH_IDX 1
/* LCU LUT control values for each of the 4 LC outputs */
/* These values decide the direction of motor rotation */
#define LCU_O0_LUT 0xAAAA
#define LCU_O1_LUT 0xCCCC
#define LCU_O2_LUT 0x4182
#define LCU_O3_LUT 0x2814
LOG_MODULE_REGISTER(nxp_qdec_s32, CONFIG_SENSOR_LOG_LEVEL);
#include <Emios_Icu_Ip.h>
#include <Trgmux_Ip.h>
#include <Lcu_Ip.h>
#define DT_DRV_COMPAT nxp_qdec_s32
typedef void (*emios_callback_t)(void);
/* Configuration variables from eMIOS Icu driver */
extern eMios_Icu_Ip_ChStateType eMios_Icu_Ip_ChState[EMIOS_ICU_IP_NUM_OF_CHANNELS_USED];
extern uint8 eMios_Icu_Ip_IndexInChState[EMIOS_ICU_IP_INSTANCE_COUNT][EMIOS_ICU_IP_NUM_OF_CHANNELS];
struct qdec_s32_config {
uint8_t emios_inst;
uint8_t emios_channels[EMIOS_CHANNEL_COUNT];
const struct pinctrl_dev_config *pincfg;
const Trgmux_Ip_InitType *trgmux_config;
const Lcu_Ip_InitType *lcu_config;
emios_callback_t emios_cw_overflow_cb;
emios_callback_t emios_ccw_overflow_cb;
};
struct qdec_s32_data {
uint32_t counter_CW;
uint32_t counter_CCW;
int32_t abs_counter;
double micro_ticks_per_rev;
uint32_t ticks_per_sec;
uint32_t emios_cw_overflow_count;
uint32_t emios_ccw_overflow_count;
};
static void qdec_emios_overflow_count_cw_callback(const struct device *dev)
{
struct qdec_s32_data *data = dev->data;
data->emios_cw_overflow_count++;
}
static void qdec_emios_overflow_count_ccw_callback(const struct device *dev)
{
struct qdec_s32_data *data = dev->data;
data->emios_ccw_overflow_count++;
}
static int qdec_s32_fetch(const struct device *dev, enum sensor_channel ch)
{
const struct qdec_s32_config *config = dev->config;
struct qdec_s32_data *data = dev->data;
if (ch != SENSOR_CHAN_ALL) {
return -ENOTSUP;
}
data->counter_CW = (uint32_t)(Emios_Icu_Ip_GetEdgeNumbers(
config->emios_inst, config->emios_channels[EMIOS_CW_CH_IDX])); /* CW counter */
data->counter_CCW = (uint32_t)(Emios_Icu_Ip_GetEdgeNumbers(
config->emios_inst, config->emios_channels[EMIOS_CCW_CH_IDX]));/* CCW counter*/
data->abs_counter = (int32_t)(
+(data->counter_CW + EMIOS_ICU_IP_COUNTER_MASK *
data->emios_cw_overflow_count)
-(data->counter_CCW + EMIOS_ICU_IP_COUNTER_MASK *
data->emios_ccw_overflow_count));
LOG_DBG("ABS_COUNT = %d CW = %u OverFlow_CW = %u CCW = %u Overflow_CCW = %u",
data->abs_counter, data->counter_CW,
data->emios_cw_overflow_count,
data->counter_CCW, data->emios_ccw_overflow_count);
return 0;
}
static int qdec_s32_ch_get(const struct device *dev, enum sensor_channel ch,
struct sensor_value *val)
{
struct qdec_s32_data *data = dev->data;
double rotation = (data->abs_counter * 2.0 * M_PI) / data->micro_ticks_per_rev;
switch (ch) {
case SENSOR_CHAN_ROTATION:
sensor_value_from_double(val, rotation);
break;
default:
return -ENOTSUP;
}
return 0;
}
static const struct sensor_driver_api qdec_s32_api = {
.sample_fetch = &qdec_s32_fetch,
.channel_get = &qdec_s32_ch_get,
};
static int qdec_s32_initialize(const struct device *dev)
{
const struct qdec_s32_config *config = dev->config;
uint8_t emios_inst, emios_hw_ch_cw, emios_hw_ch_ccw;
pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
if (Trgmux_Ip_Init(config->trgmux_config)) {
LOG_ERR("Could not initialize Trgmux");
return -EINVAL;
}
LOG_DBG("TRGMUX ACCESS Input[0] =%d Output[0]=%d",
config->trgmux_config->paxLogicTrigger[0]->Input,
config->trgmux_config->paxLogicTrigger[0]->Output);
if (Lcu_Ip_Init(config->lcu_config)) {
LOG_ERR("Could not initialize Lcu");
return -EINVAL;
}
/* Unmask relevant LCU OUT Channels */
Lcu_Ip_SyncOutputValueType EncLcuEnable[4U];
EncLcuEnable[0].LogicOutputId = LCU_LOGIC_OUTPUT_0;
EncLcuEnable[0].Value = 1U;
EncLcuEnable[1].LogicOutputId = LCU_LOGIC_OUTPUT_1;
EncLcuEnable[1].Value = 1U;
EncLcuEnable[2].LogicOutputId = LCU_LOGIC_OUTPUT_2;
EncLcuEnable[2].Value = 1U;
EncLcuEnable[3].LogicOutputId = LCU_LOGIC_OUTPUT_3;
EncLcuEnable[3].Value = 1U;
Lcu_Ip_SetSyncOutputEnable(EncLcuEnable, 4U);
emios_inst = config->emios_inst;
emios_hw_ch_cw = config->emios_channels[EMIOS_CW_CH_IDX];
emios_hw_ch_ccw = config->emios_channels[EMIOS_CCW_CH_IDX];
/* Initialize the positions of the eMios hw channels used for QDEC
* to be beyond the eMios pwm hw channels. Currently only pwm and qdec
* are using the eMios channels so qdec ones are the last two.
*/
eMios_Icu_Ip_IndexInChState[emios_inst][emios_hw_ch_cw]
= EMIOS_ICU_IP_NUM_OF_CHANNELS_USED - 2;
eMios_Icu_Ip_IndexInChState[emios_inst][emios_hw_ch_ccw]
= EMIOS_ICU_IP_NUM_OF_CHANNELS_USED - 1;
/* Set Overflow Notification for eMIOS channels meant
* for Clockwise and Counterclock rotation counters
*/
eMios_Icu_Ip_ChState[eMios_Icu_Ip_IndexInChState[emios_inst][emios_hw_ch_cw]]
.eMiosOverflowNotification = config->emios_cw_overflow_cb;
eMios_Icu_Ip_ChState[eMios_Icu_Ip_IndexInChState[emios_inst][emios_hw_ch_ccw]]
.eMiosOverflowNotification = config->emios_ccw_overflow_cb;
Emios_Icu_Ip_SetInitialCounterValue(
config->emios_inst, config->emios_channels[EMIOS_CW_CH_IDX], (uint32_t)0x1U);
Emios_Icu_Ip_SetInitialCounterValue(
config->emios_inst, config->emios_channels[EMIOS_CCW_CH_IDX], (uint32_t)0x1U);
Emios_Icu_Ip_SetMaxCounterValue(config->emios_inst,
config->emios_channels[EMIOS_CW_CH_IDX],
EMIOS_ICU_IP_COUNTER_MASK);
Emios_Icu_Ip_SetMaxCounterValue(config->emios_inst,
config->emios_channels[EMIOS_CCW_CH_IDX],
EMIOS_ICU_IP_COUNTER_MASK);
/* This API sets MCB/EMIOS_ICU_MODE_EDGE_COUNTER mode */
Emios_Icu_Ip_EnableEdgeCount(config->emios_inst, config->emios_channels[EMIOS_CW_CH_IDX]);
Emios_Icu_Ip_EnableEdgeCount(config->emios_inst, config->emios_channels[EMIOS_CCW_CH_IDX]);
LOG_DBG("Init complete");
return 0;
}
#define EMIOS_NXP_S32_MCB_OVERFLOW_CALLBACK(n) \
static void qdec##n##_emios_overflow_count_cw_callback(void) \
{ \
qdec_emios_overflow_count_cw_callback(DEVICE_DT_INST_GET(n)); \
} \
\
static void qdec##n##_emios_overflow_count_ccw_callback(void) \
{ \
qdec_emios_overflow_count_ccw_callback(DEVICE_DT_INST_GET(n)); \
}
#define EMIOS_NXP_S32_INSTANCE_CHECK(idx, node_id) \
((DT_REG_ADDR(node_id) == IP_EMIOS_##idx##_BASE) ? idx : 0)
#define EMIOS_NXP_S32_GET_INSTANCE(node_id) \
LISTIFY(__DEBRACKET eMIOS_INSTANCE_COUNT, EMIOS_NXP_S32_INSTANCE_CHECK, (|), node_id)
#define LCU_NXP_S32_INSTANCE_CHECK(idx, node_id) \
((DT_REG_ADDR(node_id) == IP_LCU_##idx##_BASE) ? idx : 0)
#define LCU_NXP_S32_GET_INSTANCE(node_id) \
LISTIFY(__DEBRACKET LCU_INSTANCE_COUNT, LCU_NXP_S32_INSTANCE_CHECK, (|), node_id)
#define TRGMUX_NXP_S32_INSTANCE_CHECK(node_id) \
((DT_REG_ADDR(node_id) == IP_TRGMUX_BASE) ? 0 : -1)
#define TRGMUX_NXP_S32_GET_INSTANCE(node_id) TRGMUX_NXP_S32_INSTANCE_CHECK(node_id)
/* LCU Logic Input Configuration */
#define LogicInputCfg_Common(n, mux_sel_idx) \
{ \
.MuxSel = DT_INST_PROP_BY_IDX(n, lcu_mux_sel, mux_sel_idx), \
.SwSynMode = LCU_IP_SW_SYNC_IMMEDIATE, \
.SwValue = LCU_IP_SW_OVERRIDE_LOGIC_LOW, \
};
#define LogicInput_Config_Common(n, hw_lc_input_id, logic_input_n_cfg) \
{ \
.xLogicInputId = { \
.HwInstId = LCU_NXP_S32_GET_INSTANCE(DT_INST_PHANDLE(n, lcu)), \
.HwLcInputId = DT_INST_PROP_BY_IDX(n, lcu_input_idx, hw_lc_input_id), \
}, \
.pxLcInputConfig = &logic_input_n_cfg, \
};
/* LCU Logic Output Configuration */
#define LogicOutputCfg_Common(En_Debug_Mode, Lut_Control, Lut_Rise_Filt, Lut_Fall_Filt) \
{ \
.EnDebugMode = (boolean)En_Debug_Mode, \
.LutControl = Lut_Control, \
.LutRiseFilt = Lut_Rise_Filt, \
.LutFallFilt = Lut_Fall_Filt, \
.EnLutDma = (boolean)FALSE, \
.EnForceDma = (boolean)FALSE, \
.EnLutInt = (boolean)FALSE, \
.EnForceInt = (boolean)FALSE, \
.InvertOutput = (boolean)FALSE, \
.ForceSignalSel = 0U, \
.ClearForceMode = LCU_IP_CLEAR_FORCE_SIGNAL_IMMEDIATE, \
.ForceSyncSel = LCU_IP_SYNC_SEL_INPUT0, \
};
#define LogicOutput_Config_Common(n, logic_output_cfg, hw_lc_output_id) \
{ \
.xLogicOutputId = { \
.HwInstId = LCU_NXP_S32_GET_INSTANCE(DT_INST_PHANDLE(n, lcu)), \
.HwLcOutputId = hw_lc_output_id, \
.IntCallback = NULL_PTR, \
}, \
.pxLcOutputConfig = &logic_output_cfg, \
};
#define LCU_IP_INIT_CONFIG(n) \
const Lcu_Ip_LogicInputConfigType LogicInput##n##_0_Cfg = \
LogicInputCfg_Common(n, 0) \
const Lcu_Ip_LogicInputConfigType LogicInput##n##_1_Cfg = \
LogicInputCfg_Common(n, 1) \
const Lcu_Ip_LogicInputConfigType LogicInput##n##_2_Cfg = \
LogicInputCfg_Common(n, 2) \
const Lcu_Ip_LogicInputConfigType LogicInput##n##_3_Cfg = \
LogicInputCfg_Common(n, 3) \
\
const Lcu_Ip_LogicInputType LogicInput##n##_0_Config = \
LogicInput_Config_Common(n, 0, LogicInput##n##_0_Cfg) \
const Lcu_Ip_LogicInputType LogicInput##n##_1_Config = \
LogicInput_Config_Common(n, 1, LogicInput##n##_1_Cfg) \
const Lcu_Ip_LogicInputType LogicInput##n##_2_Config = \
LogicInput_Config_Common(n, 2, LogicInput##n##_2_Cfg) \
const Lcu_Ip_LogicInputType LogicInput##n##_3_Config = \
LogicInput_Config_Common(n, 3, LogicInput##n##_3_Cfg) \
\
const Lcu_Ip_LogicInputType \
*const Lcu_Ip_ppxLogicInputArray##n##_Config[LCU_IP_NOF_CFG_LOGIC_INPUTS] = { \
&LogicInput##n##_0_Config, \
&LogicInput##n##_1_Config, \
&LogicInput##n##_2_Config, \
&LogicInput##n##_3_Config, \
}; \
\
const Lcu_Ip_LogicOutputConfigType LogicOutput##n##_0_Cfg = LogicOutputCfg_Common( \
LCU_IP_DEBUG_DISABLE, LCU_O0_LUT, \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 1), \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 2)) \
const Lcu_Ip_LogicOutputConfigType LogicOutput##n##_1_Cfg = LogicOutputCfg_Common( \
LCU_IP_DEBUG_DISABLE, LCU_O1_LUT, \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 4), \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 5)) \
const Lcu_Ip_LogicOutputConfigType LogicOutput##n##_2_Cfg = LogicOutputCfg_Common( \
LCU_IP_DEBUG_ENABLE, LCU_O2_LUT, \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 7), \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 8)) \
const Lcu_Ip_LogicOutputConfigType LogicOutput##n##_3_Cfg = LogicOutputCfg_Common( \
LCU_IP_DEBUG_ENABLE, LCU_O3_LUT, \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 10), \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 11)) \
\
const Lcu_Ip_LogicOutputType LogicOutput##n##_0_Config = \
LogicOutput_Config_Common(n, LogicOutput##n##_0_Cfg, \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 0)) \
const Lcu_Ip_LogicOutputType LogicOutput##n##_1_Config = \
LogicOutput_Config_Common(n, LogicOutput##n##_1_Cfg, \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 3)) \
const Lcu_Ip_LogicOutputType LogicOutput##n##_2_Config = \
LogicOutput_Config_Common(n, LogicOutput##n##_2_Cfg, \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 6)) \
const Lcu_Ip_LogicOutputType LogicOutput##n##_3_Config = \
LogicOutput_Config_Common(n, LogicOutput##n##_3_Cfg, \
DT_INST_PROP_BY_IDX(n, lcu_output_filter_config, 9)) \
\
const Lcu_Ip_LogicOutputType \
*const Lcu_Ip_ppxLogicOutputArray##n##_Config[LCU_IP_NOF_CFG_LOGIC_OUTPUTS] = { \
&LogicOutput##n##_0_Config, \
&LogicOutput##n##_1_Config, \
&LogicOutput##n##_2_Config, \
&LogicOutput##n##_3_Config, \
}; \
\
const Lcu_Ip_LogicInputConfigType Lcu_Ip_LogicInputResetConfig##n = { \
.MuxSel = LCU_IP_MUX_SEL_LOGIC_0, \
.SwSynMode = LCU_IP_SW_SYNC_IMMEDIATE, \
.SwValue = LCU_IP_SW_OVERRIDE_LOGIC_LOW, \
}; \
\
const Lcu_Ip_LogicOutputConfigType Lcu_Ip_LogicOutputResetConfig##n = \
LogicOutputCfg_Common(LCU_IP_DEBUG_DISABLE, 0U, 0U, 0U) \
\
const Lcu_Ip_LogicInstanceType LcuLogicInstance##n##_0_Config = { \
.HwInstId = LCU_NXP_S32_GET_INSTANCE(DT_INST_PHANDLE(n, lcu)), \
.NumLogicCellConfig = 0U, \
.ppxLogicCellConfigArray = NULL_PTR, \
.OperationMode = LCU_IP_INTERRUPT_MODE, \
}; \
const Lcu_Ip_LogicInstanceType \
*const Lcu_Ip_ppxLogicInstanceArray##n##_Config[LCU_IP_NOF_CFG_LOGIC_INSTANCES] = { \
&LcuLogicInstance##n##_0_Config, \
}; \
\
Lcu_Ip_HwOutputStateType HwOutput##n##_0_State_Config; \
Lcu_Ip_HwOutputStateType HwOutput##n##_1_State_Config; \
Lcu_Ip_HwOutputStateType HwOutput##n##_2_State_Config; \
Lcu_Ip_HwOutputStateType HwOutput##n##_3_State_Config; \
Lcu_Ip_HwOutputStateType \
*Lcu_Ip_ppxHwOutputStateArray##n##_Config[LCU_IP_NOF_CFG_LOGIC_OUTPUTS] = { \
&HwOutput##n##_0_State_Config, \
&HwOutput##n##_1_State_Config, \
&HwOutput##n##_2_State_Config, \
&HwOutput##n##_3_State_Config, \
}; \
\
const Lcu_Ip_InitType Lcu_Ip_Init_Config##n = { \
.ppxHwOutputStateArray = &Lcu_Ip_ppxHwOutputStateArray##n##_Config[0], \
.ppxLogicInstanceConfigArray = &Lcu_Ip_ppxLogicInstanceArray##n##_Config[0], \
.pxLogicOutputResetConfigArray = &Lcu_Ip_LogicOutputResetConfig##n, \
.pxLogicInputResetConfigArray = &Lcu_Ip_LogicInputResetConfig##n, \
.ppxLogicOutputConfigArray = &Lcu_Ip_ppxLogicOutputArray##n##_Config[0], \
.ppxLogicInputConfigArray = &Lcu_Ip_ppxLogicInputArray##n##_Config[0], \
};
#define Trgmux_Ip_LogicTrigger_Config(n, logic_channel, output, input) \
{ \
.LogicChannel = logic_channel, \
.Output = output, \
.Input = input, \
.HwInstId = TRGMUX_NXP_S32_GET_INSTANCE(DT_INST_PHANDLE(n, trgmux)), \
.Lock = (boolean)FALSE, \
};
#define TRGMUX_IP_INIT_CONFIG(n) \
const Trgmux_Ip_LogicTriggerType \
Trgmux_Ip_LogicTrigger##n##_0_Config = Trgmux_Ip_LogicTrigger_Config(n, \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 0), \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 1), \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 2)) \
const Trgmux_Ip_LogicTriggerType \
Trgmux_Ip_LogicTrigger##n##_1_Config = Trgmux_Ip_LogicTrigger_Config(n, \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 3), \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 4), \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 5)) \
const Trgmux_Ip_LogicTriggerType \
Trgmux_Ip_LogicTrigger##n##_2_Config = Trgmux_Ip_LogicTrigger_Config(n, \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 6), \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 7), \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 8)) \
const Trgmux_Ip_LogicTriggerType \
Trgmux_Ip_LogicTrigger##n##_3_Config = Trgmux_Ip_LogicTrigger_Config(n, \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 9), \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 10), \
DT_INST_PROP_BY_IDX(n, trgmux_io_config, 11)) \
const Trgmux_Ip_InitType Trgmux_Ip_Init_##n##_Config = { \
.paxLogicTrigger = { \
&Trgmux_Ip_LogicTrigger##n##_0_Config, \
&Trgmux_Ip_LogicTrigger##n##_1_Config, \
&Trgmux_Ip_LogicTrigger##n##_2_Config, \
&Trgmux_Ip_LogicTrigger##n##_3_Config, \
}, \
};
#define QDEC_NXP_S32_INIT(n) \
\
static struct qdec_s32_data qdec_s32_##n##_data = { \
.micro_ticks_per_rev = (double)(DT_INST_PROP(n, micro_ticks_per_rev) / 1000000),\
.counter_CW = 1, \
.counter_CCW = 1, \
}; \
\
PINCTRL_DT_INST_DEFINE(n); \
TRGMUX_IP_INIT_CONFIG(n) \
LCU_IP_INIT_CONFIG(n) \
EMIOS_NXP_S32_MCB_OVERFLOW_CALLBACK(n) \
\
static const struct qdec_s32_config qdec_s32_##n##_config = { \
.emios_inst = EMIOS_NXP_S32_GET_INSTANCE(DT_INST_PHANDLE(n, emios)), \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
.trgmux_config = &Trgmux_Ip_Init_##n##_Config, \
.lcu_config = &Lcu_Ip_Init_Config##n, \
.emios_channels = {DT_INST_PROP_BY_IDX(n, emios_channels, EMIOS_CW_CH_IDX), \
DT_INST_PROP_BY_IDX(n, emios_channels, EMIOS_CCW_CH_IDX)}, \
.emios_cw_overflow_cb = &qdec##n##_emios_overflow_count_cw_callback, \
.emios_ccw_overflow_cb = &qdec##n##_emios_overflow_count_ccw_callback, \
}; \
\
SENSOR_DEVICE_DT_INST_DEFINE(n, qdec_s32_initialize, NULL, &qdec_s32_##n##_data, \
&qdec_s32_##n##_config, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &qdec_s32_api);
DT_INST_FOREACH_STATUS_OKAY(QDEC_NXP_S32_INIT)