| /**************************************************************************** |
| * 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 */ |