mirror of
https://github.com/apache/nuttx.git
synced 2026-05-30 13:27:01 +08:00
21c19b7824
Previously the ioctl-based configuration was not encoded properly. It lacked the three command bits selecting the "set volatile configuration" mode. Thus, configuration the reference, power-down or gain resulted in no change. Now the configuration is properly applied. The DAC value to be send is now combined with the proper command prefix bits, too. But this operation was already working, since the command prefix is zero. Signed-off-by: Lars Kruse <devel@sumpfralle.de>
461 lines
13 KiB
C
461 lines
13 KiB
C
/****************************************************************************
|
|
* drivers/analog/mcp47x6.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/analog/dac.h>
|
|
#include <nuttx/i2c/i2c_master.h>
|
|
|
|
#include <nuttx/analog/mcp47x6.h>
|
|
|
|
/****************************************************************************
|
|
* Preprocessor definitions
|
|
****************************************************************************/
|
|
|
|
#if !defined(CONFIG_I2C)
|
|
# error I2C Support Required.
|
|
#endif
|
|
|
|
#if defined(CONFIG_MCP47X6)
|
|
|
|
#if defined(CONFIG_MCP4706)
|
|
# define MCP47X6_DATA_BITS 8u
|
|
# define MCP47X6_DATA_SHIFT 0u
|
|
#elif defined(CONFIG_MCP4716)
|
|
# define MCP47X6_DATA_BITS 10u
|
|
# define MCP47X6_DATA_SHIFT 2u
|
|
#elif defined(CONFIG_MCP4726)
|
|
# define MCP47X6_DATA_BITS 12u
|
|
# define MCP47X6_DATA_SHIFT 0u
|
|
#else
|
|
# error MCP47x6 variant selection required
|
|
#endif
|
|
|
|
#ifndef CONFIG_MCP47X6_I2C_FREQUENCY
|
|
# define CONFIG_MCP47X6_I2C_FREQUENCY 400000
|
|
#endif
|
|
|
|
#define MCP47X6_GAIN_MASK (1u << MCP47X6_GAIN_SHIFT)
|
|
#define MCP47X6_GAIN_SHIFT 0u
|
|
#define MCP47X6_POWER_DOWN_MASK (3u << MCP47X6_POWER_DOWN_SHIFT)
|
|
#define MCP47X6_POWER_DOWN_SHIFT 1u
|
|
#define MCP47X6_REFERENCE_MASK (3u << MCP47X6_REFERENCE_SHIFT)
|
|
#define MCP47X6_REFERENCE_SHIFT 3u
|
|
#define MCP47X6_COMMAND_MASK (7u << MCP47X6_COMMAND_SHIFT)
|
|
#define MCP47X6_COMMAND_SHIFT 5u
|
|
|
|
#define MCP47X6_DATA_MASK ((1u << MCP47X6_DATA_BITS) - 1u)
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct mcp47x6_dev_s
|
|
{
|
|
FAR struct i2c_master_s *i2c; /* I2C interface */
|
|
uint8_t addr; /* I2C address */
|
|
uint8_t cmd; /* MCP47x6 current state */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* DAC methods */
|
|
|
|
static void mcp47x6_reset(FAR struct dac_dev_s *dev);
|
|
static int mcp47x6_setup(FAR struct dac_dev_s *dev);
|
|
static void mcp47x6_shutdown(FAR struct dac_dev_s *dev);
|
|
static void mcp47x6_txint(FAR struct dac_dev_s *dev, bool enable);
|
|
static int mcp47x6_send(FAR struct dac_dev_s *dev,
|
|
FAR struct dac_msg_s *msg);
|
|
static int mcp47x6_ioctl(FAR struct dac_dev_s *dev, int cmd,
|
|
unsigned long arg);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct dac_ops_s g_dacops =
|
|
{
|
|
mcp47x6_reset, /* ao_reset */
|
|
mcp47x6_setup, /* ao_setup */
|
|
mcp47x6_shutdown, /* ao_shutdown */
|
|
mcp47x6_txint, /* ao_txint */
|
|
mcp47x6_send, /* ao_send */
|
|
mcp47x6_ioctl /* ao_ioctl */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_i2c_write
|
|
*
|
|
* Description:
|
|
* Send the raw content of a buffer to the DAC.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int mcp47x6_i2c_write(FAR struct mcp47x6_dev_s *priv,
|
|
uint8_t const *source, size_t size)
|
|
{
|
|
struct i2c_msg_s msg;
|
|
int ret;
|
|
|
|
/* Sanity check */
|
|
|
|
DEBUGASSERT(priv->i2c != NULL);
|
|
|
|
/* Setup for the transfer */
|
|
|
|
msg.frequency = CONFIG_MCP47X6_I2C_FREQUENCY;
|
|
msg.addr = priv->addr;
|
|
msg.flags = 0;
|
|
msg.buffer = (uint8_t *)source; /* discard const qualifier */
|
|
msg.length = size;
|
|
|
|
/* Then perform the transfer. */
|
|
|
|
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
|
|
if (ret < 0)
|
|
{
|
|
aerr("MCP47X6 I2C write transfer failed: %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_i2c_read
|
|
*
|
|
* Description:
|
|
* Read raw content from the DAC
|
|
*
|
|
* Response bytes:
|
|
* - volatile status and configuration
|
|
* - volatile data byte
|
|
* - volatile data byte (only for MCP4716 and MCP4726)
|
|
* - non-volatile status and configuration
|
|
* - non-volatile data byte
|
|
* - non-volatile data byte (only for MCP4716 and MCP4726)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int mcp47x6_i2c_read(FAR struct mcp47x6_dev_s *priv,
|
|
uint8_t *destination, size_t size)
|
|
{
|
|
struct i2c_msg_s msg;
|
|
int ret;
|
|
|
|
/* Sanity check */
|
|
|
|
DEBUGASSERT(priv->i2c != NULL);
|
|
|
|
/* Setup for the transfer */
|
|
|
|
msg.frequency = CONFIG_MCP47X6_I2C_FREQUENCY;
|
|
msg.addr = priv->addr;
|
|
msg.flags = I2C_M_READ;
|
|
msg.buffer = destination;
|
|
msg.length = size;
|
|
|
|
/* Then perform the transfer. */
|
|
|
|
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
|
|
if (ret < 0)
|
|
{
|
|
aerr("MCP47X6 I2C read transfer failed: %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_reset
|
|
*
|
|
* Description:
|
|
* Reset the DAC device. Called early to initialize the hardware. This
|
|
* is called, before ao_setup() and on error conditions.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void mcp47x6_reset(FAR struct dac_dev_s *dev)
|
|
{
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_setup
|
|
*
|
|
* Description:
|
|
* Configure the DAC. This method is called the first time that the DAC
|
|
* device is opened. This will occur when the port is first opened. This
|
|
* setup includes configuring and attaching DAC interrupts. Interrupts are
|
|
* all disabled upon return.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int mcp47x6_setup(FAR struct dac_dev_s *dev)
|
|
{
|
|
FAR struct mcp47x6_dev_s *priv = (FAR struct mcp47x6_dev_s *)dev->ad_priv;
|
|
uint8_t response;
|
|
int ret;
|
|
|
|
/* Device's default settings after power up. */
|
|
|
|
uint8_t default_settings = MCP47X6_REFERENCE_VDD_UNBUFFERED
|
|
| MCP47X6_POWER_DOWN_DISABLED
|
|
| MCP47X6_GAIN_1X;
|
|
|
|
/* Retrieve the current device setup. */
|
|
|
|
ret = mcp47x6_i2c_read(priv, &response, 1);
|
|
if (ret < 0)
|
|
{
|
|
aerr("MCP47X6 I2C reading initial configuration failed: %d", ret);
|
|
priv->cmd = default_settings;
|
|
return ret;
|
|
}
|
|
|
|
/* Store the current setup for future configuration operations. */
|
|
|
|
priv->cmd = response & (MCP47X6_REFERENCE_MASK
|
|
| MCP47X6_POWER_DOWN_MASK
|
|
| MCP47X6_GAIN_MASK);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_shutdown
|
|
*
|
|
* Description:
|
|
* Disable the DAC. This method is called when the DAC device is closed.
|
|
* This method reverses the operation the setup method.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void mcp47x6_shutdown(FAR struct dac_dev_s *dev)
|
|
{
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_txint
|
|
*
|
|
* Description:
|
|
* Call to enable or disable TX interrupts
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void mcp47x6_txint(FAR struct dac_dev_s *dev, bool enable)
|
|
{
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_send
|
|
****************************************************************************/
|
|
|
|
static int mcp47x6_send(FAR struct dac_dev_s *dev, FAR struct dac_msg_s *msg)
|
|
{
|
|
FAR struct mcp47x6_dev_s *priv = (FAR struct mcp47x6_dev_s *)dev->ad_priv;
|
|
int ret;
|
|
|
|
/* Set up message to send */
|
|
|
|
ainfo("value: %08x", (unsigned int)msg->am_data);
|
|
|
|
uint8_t const BUFFER_SIZE = 2;
|
|
uint8_t buffer[BUFFER_SIZE];
|
|
|
|
uint32_t data;
|
|
data = msg->am_data & MCP47X6_DATA_MASK;
|
|
data <<= MCP47X6_DATA_SHIFT;
|
|
buffer[0] = (uint8_t)(data >> 8) | MCP47X6_COMMAND_WRITE_DAC;
|
|
buffer[1] = (uint8_t)(data);
|
|
|
|
ret = mcp47x6_i2c_write(priv, buffer, sizeof(buffer));
|
|
if (ret < 0)
|
|
{
|
|
aerr("ERROR: mcp47x6_send failed: %d", ret);
|
|
}
|
|
|
|
dac_txdone(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_ioctl
|
|
****************************************************************************/
|
|
|
|
static int mcp47x6_ioctl(FAR struct dac_dev_s *dev, int cmd,
|
|
unsigned long arg)
|
|
{
|
|
FAR struct mcp47x6_dev_s *priv = (FAR struct mcp47x6_dev_s *)dev->ad_priv;
|
|
int ret = OK;
|
|
bool command_prepared = false;
|
|
|
|
switch (cmd)
|
|
{
|
|
case ANIOC_MCP47X6_DAC_SET_GAIN:
|
|
{
|
|
switch (arg)
|
|
{
|
|
case MCP47X6_GAIN_1X:
|
|
case MCP47X6_GAIN_2X:
|
|
priv->cmd &= ~MCP47X6_GAIN_MASK;
|
|
priv->cmd |= arg;
|
|
command_prepared = true;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ANIOC_MCP47X6_DAC_SET_POWER_DOWN:
|
|
{
|
|
switch (arg)
|
|
{
|
|
case MCP47X6_POWER_DOWN_DISABLED:
|
|
case MCP47X6_POWER_DOWN_1K:
|
|
case MCP47X6_POWER_DOWN_100K:
|
|
case MCP47X6_POWER_DOWN_500K:
|
|
priv->cmd &= ~MCP47X6_POWER_DOWN_MASK;
|
|
priv->cmd |= arg;
|
|
command_prepared = true;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ANIOC_MCP47X6_DAC_SET_REFERENCE:
|
|
{
|
|
switch (arg)
|
|
{
|
|
case MCP47X6_REFERENCE_VDD_UNBUFFERED:
|
|
case MCP47X6_REFERENCE_VREF_UNBUFFERED:
|
|
case MCP47X6_REFERENCE_VREF_BUFFERED:
|
|
priv->cmd &= ~MCP47X6_REFERENCE_MASK;
|
|
priv->cmd |= arg;
|
|
command_prepared = true;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* Command was not recognized */
|
|
|
|
default:
|
|
aerr("MCP47X6 ERROR: Unrecognized cmd: %d", cmd);
|
|
ret = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
if (command_prepared)
|
|
{
|
|
uint8_t raw_cmd = MCP47X6_COMMAND_WRITE_CONFIG | priv->cmd;
|
|
ret = mcp47x6_i2c_write(priv, &raw_cmd, sizeof(raw_cmd));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: mcp47x6_initialize
|
|
*
|
|
* Description:
|
|
* Initialize DAC
|
|
*
|
|
* Input Parameters:
|
|
* i2c - Pointer to a valid I2C master struct.
|
|
* addr - I2C device address.
|
|
*
|
|
* Returned Value:
|
|
* Valid MCP47X6 device structure reference on success; a NULL on failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct dac_dev_s *mcp47x6_initialize(FAR struct i2c_master_s *i2c,
|
|
uint8_t addr)
|
|
{
|
|
FAR struct mcp47x6_dev_s *priv;
|
|
FAR struct dac_dev_s *dacdev;
|
|
|
|
/* Sanity check */
|
|
|
|
DEBUGASSERT(i2c != NULL);
|
|
|
|
/* Initialize the MCP47X6 device structure */
|
|
|
|
priv = kmm_malloc(sizeof(struct mcp47x6_dev_s));
|
|
if (priv == NULL)
|
|
{
|
|
aerr("ERROR: Failed to allocate mcp47x6_dev_s instance\n");
|
|
free(priv);
|
|
return NULL;
|
|
}
|
|
|
|
dacdev = kmm_malloc(sizeof(struct dac_dev_s));
|
|
if (dacdev == NULL)
|
|
{
|
|
aerr("ERROR: Failed to allocate dac_dev_s instance\n");
|
|
return NULL;
|
|
}
|
|
|
|
dacdev->ad_ops = &g_dacops;
|
|
dacdev->ad_priv = priv;
|
|
|
|
priv->i2c = i2c;
|
|
priv->addr = addr;
|
|
return dacdev;
|
|
}
|
|
|
|
#endif /* CONFIG_MCP47X6 */
|