blob: 37ba0a4287465a119c7b792dd2e90d2337fd6603 [file] [log] [blame]
/*
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <fsl_opamp.h>
#include <zephyr/drivers/opamp.h>
#include <zephyr/pm/device.h>
#if defined(CONFIG_SOC_FAMILY_LPC)
#include <zephyr/drivers/reset.h>
#endif
#include <zephyr/drivers/clock_control.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(nxp_opamp, CONFIG_OPAMP_LOG_LEVEL);
#define DT_DRV_COMPAT nxp_opamp
struct mcux_opamp_config {
OPAMP_Type *base;
uint8_t positive_reference;
uint8_t operation_mode;
uint8_t functional_mode;
bool enable_measure_reference;
bool enable_measure_output;
const struct device *clock_dev;
clock_control_subsys_t clock_subsys;
#if defined(CONFIG_SOC_FAMILY_LPC)
const struct reset_dt_spec reset;
#endif
};
static int mcux_opamp_pm_callback(const struct device *dev, enum pm_device_action action)
{
const struct mcux_opamp_config *config = dev->config;
if (action == PM_DEVICE_ACTION_RESUME) {
config->base->OPAMP_CTR |= OPAMP_OPAMP_CTR_EN_MASK;
return 0;
}
if (action == PM_DEVICE_ACTION_SUSPEND) {
config->base->OPAMP_CTR &= ~OPAMP_OPAMP_CTR_EN_MASK;
return 0;
}
return -ENOTSUP;
}
static int mcux_opamp_set_gain(const struct device *dev, enum opamp_gain gain)
{
const struct mcux_opamp_config *config = dev->config;
uint8_t gain_index;
switch (gain) {
case OPAMP_GAIN_1:
gain_index = 1;
break;
case OPAMP_GAIN_2:
gain_index = 2;
break;
case OPAMP_GAIN_4:
gain_index = 3;
break;
case OPAMP_GAIN_8:
gain_index = 4;
break;
case OPAMP_GAIN_16:
gain_index = 5;
break;
case OPAMP_GAIN_33:
gain_index = 6;
break;
case OPAMP_GAIN_64:
gain_index = 7;
break;
default:
LOG_ERR("Invalid gain value: %d", gain);
return -EINVAL;
}
switch (config->functional_mode) {
case OPAMP_FUNCTIONAL_MODE_DIFFERENTIAL:
case OPAMP_FUNCTIONAL_MODE_INVERTING:
case OPAMP_FUNCTIONAL_MODE_NON_INVERTING:
/* For differential, inverting and non-inverting mode,
* set Ngain to select different gains, and set Pgain
* to be the same as Ngain.
*/
OPAMP_DoNegGainConfig(config->base, gain_index);
OPAMP_DoPosGainConfig(config->base, gain_index);
break;
case OPAMP_FUNCTIONAL_MODE_FOLLOWER:
OPAMP_DoNegGainConfig(config->base, kOPAMP_NegGainBufferMode);
OPAMP_DoPosGainConfig(config->base, kOPAMP_PosGainNonInvert1X);
default:
break;
}
return 0;
}
int mcux_opamp_init(const struct device *dev)
{
const struct mcux_opamp_config *config = dev->config;
OPAMP_Type *base = config->base;
int ret;
/* Enable OPAMP clock. */
if (!device_is_ready(config->clock_dev)) {
LOG_ERR("Clock device is not ready");
return -ENODEV;
}
ret = clock_control_on(config->clock_dev, config->clock_subsys);
if (ret) {
LOG_ERR("Device clock turn on failed");
return ret;
}
/* Only LPC implemented RESET driver */
#if defined(CONFIG_SOC_FAMILY_LPC)
if (!device_is_ready(config->reset.dev)) {
LOG_ERR("Reset device is not ready");
return -ENODEV;
}
ret = reset_line_assert(config->reset.dev, config->reset.id);
if (ret) {
LOG_ERR("Device clock turn on failed");
return ret;
}
#endif
base->OPAMP_CTR = ((config->base->OPAMP_CTR & ~OPAMP_OPAMP_CTR_MODE_MASK) |
OPAMP_OPAMP_CTR_MODE(config->operation_mode));
if (config->enable_measure_reference) {
#if defined(FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_ADCSW2) && FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_ADCSW2
base->OPAMP_CTR |= OPAMP_OPAMP_CTR_ADCSW2_MASK;
#else
base->OPAMP_CTR |= OPAMP_OPAMP_CTR_ADCSW_MASK;
#endif
} else {
#if defined(FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_ADCSW2) && FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_ADCSW2
base->OPAMP_CTR &= ~OPAMP_OPAMP_CTR_ADCSW2_MASK;
#else
base->OPAMP_CTR &= ~OPAMP_OPAMP_CTR_ADCSW_MASK;
#endif
}
#if defined(FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_ADCSW1) && FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_ADCSW1
if (config->enable_measure_output) {
base->OPAMP_CTR |= OPAMP_OPAMP_CTR_ADCSW1_MASK;
} else {
base->OPAMP_CTR &= ~OPAMP_OPAMP_CTR_ADCSW1_MASK;
}
#endif
if (config->positive_reference != UINT8_MAX) {
#if defined(FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_BUFEN) && FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_BUFEN
base->OPAMP_CTR |= OPAMP_OPAMP_CTR_BUFEN_MASK;
#endif
base->OPAMP_CTR = ((config->base->OPAMP_CTR & ~OPAMP_OPAMP_CTR_PREF_MASK) |
OPAMP_OPAMP_CTR_PREF(config->positive_reference));
} else {
#if defined(FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_BUFEN) && FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_BUFEN
base->OPAMP_CTR &= ~OPAMP_OPAMP_CTR_BUFEN_MASK;
#else
base->OPAMP_CTR = ((base->OPAMP_CTR & ~OPAMP_OPAMP_CTR_PREF_MASK) |
OPAMP_OPAMP_CTR_PREF(kOPAMP_PosRefVoltVrefh4));
#endif
}
#if defined(FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_OUTSW) && FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_OUTSW
base->OPAMP_CTR |= OPAMP_OPAMP_CTR_OUTSW_MASK;
#endif
switch (config->functional_mode) {
case OPAMP_FUNCTIONAL_MODE_DIFFERENTIAL:
case OPAMP_FUNCTIONAL_MODE_INVERTING:
case OPAMP_FUNCTIONAL_MODE_NON_INVERTING:
break;
case OPAMP_FUNCTIONAL_MODE_FOLLOWER:
#if defined(FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_BUFEN) && FSL_FEATURE_OPAMP_HAS_OPAMP_CTR_BUFEN
base->OPAMP_CTR &= ~OPAMP_OPAMP_CTR_BUFEN_MASK;
#else
base->OPAMP_CTR = ((base->OPAMP_CTR & ~OPAMP_OPAMP_CTR_PREF_MASK) |
OPAMP_OPAMP_CTR_PREF(kOPAMP_PosRefVoltVrefh4));
#endif
break;
default:
LOG_ERR("Unsupported functional mode %d", config->functional_mode);
return -ENOTSUP;
}
return pm_device_driver_init(dev, mcux_opamp_pm_callback);
}
static DEVICE_API(opamp, api) = {
.set_gain = mcux_opamp_set_gain,
};
#if defined(CONFIG_SOC_FAMILY_LPC)
#define GET_RESET_INFO(inst) .reset = RESET_DT_SPEC_INST_GET(inst),
#else
#define GET_RESET_INFO(inst)
#endif
#define OPAMP_MCUX_OPAMP_DEFINE(inst) \
static const struct mcux_opamp_config _CONCAT(config, inst) = { \
.base = (OPAMP_Type *)DT_INST_REG_ADDR(inst), \
.positive_reference = COND_CODE_1( \
DT_INST_NODE_HAS_PROP(inst, non_inverting_reference), \
(DT_INST_ENUM_IDX(inst, non_inverting_reference)), \
(UINT8_MAX)), \
.operation_mode = DT_INST_ENUM_IDX(inst, operation_mode), \
.functional_mode = DT_INST_ENUM_IDX(inst, functional_mode), \
.enable_measure_reference = DT_INST_PROP(inst, enable_measure_reference), \
.enable_measure_output = DT_INST_PROP(inst, enable_measure_output), \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \
.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(inst, name), \
GET_RESET_INFO(inst) \
}; \
\
PM_DEVICE_DT_INST_DEFINE(inst, mcux_opamp_pm_callback); \
\
DEVICE_DT_INST_DEFINE(inst, mcux_opamp_init, \
PM_DEVICE_DT_INST_GET(inst), NULL, \
&_CONCAT(config, inst), POST_KERNEL, \
CONFIG_OPAMP_INIT_PRIORITY, &api);
DT_INST_FOREACH_STATUS_OKAY(OPAMP_MCUX_OPAMP_DEFINE)