drivers/analog/mcp47x6.c (246 lines of code) (raw):
/****************************************************************************
* 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);
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)
{
ret = mcp47x6_i2c_write(priv, &priv->cmd, sizeof(priv->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 */