/**************************************************************************** * drivers/i2c/tca9548a.c * Driver for the TCA9448A i2c multiplexer * * 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 #include #include #include #include #include #include #include #include #include #ifndef CONFIG_TCA9548A_I2C_FREQUENCY # define CONFIG_TCA9548A_I2C_FREQUENCY 400000 #endif /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* I2C Helpers */ static int tca9548a_write_config(FAR struct tca9548a_dev_s *priv, uint8_t regvalue); static int tca9548a_read_config(FAR struct tca9548a_dev_s *priv, FAR uint8_t *regvalue); /* Other helpers */ static int tca9548a_select_channel(FAR struct tca9548a_dev_s *priv, uint8_t val); /* I2C multiplexer vtable */ static int tca9548a_transfer_on_channel (FAR struct i2c_master_s *dev, FAR struct i2c_msg_s *msgs, int count); #ifdef CONFIG_I2C_RESET static int tca9548a_reset_on_channel (FAR struct i2c_master_s *dev); #endif /**************************************************************************** * Private Data ****************************************************************************/ static const struct i2c_ops_s g_i2cmux_ops = { tca9548a_transfer_on_channel #ifdef CONFIG_I2C_RESET , tca9548a_reset_on_channel #endif }; /**************************************************************************** * Private Types ****************************************************************************/ struct i2c_channel_dev_s { FAR struct i2c_master_s vi2c; /* Nested structure to allow casting as * public i2c master */ uint8_t channel; /* Associated channel on the mux */ FAR struct tca9548a_dev_s *dev; /* Associated device */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: tca9548a_write_config * * Description: * Write to the mux register of TCA9548A. * * Input Parameters: * priv - A pointer to the TCA9548A device structure. * regvalue - The value that will be written. * * Returned Value: * OK on success or a negative error. * ****************************************************************************/ static int tca9548a_write_config(FAR struct tca9548a_dev_s *priv, uint8_t regvalue) { struct i2c_config_s iconf; int ret; iconf.frequency = CONFIG_TCA9548A_I2C_FREQUENCY; iconf.address = priv->addr; iconf.addrlen = 7; ret = i2c_write(priv->i2c, &iconf, ®value, 1); i2cinfo("Write to address 0x%02X; register value: 0x%02x ret: %d\n", priv->addr, regvalue, ret); return ret; } /**************************************************************************** * Name: tca9548a_read_config * * Description: * Read the mux register from TCA9548A. * * Input Parameters: * priv - A pointer to the TCA9548A device structure. * regvalue - A pointer to a buffer into which data will be received. * * Returned Value: * OK on success or a negative error. * ****************************************************************************/ static int tca9548a_read_config(FAR struct tca9548a_dev_s *priv, FAR uint8_t *regvalue) { struct i2c_config_s iconf; int ret; iconf.frequency = CONFIG_TCA9548A_I2C_FREQUENCY; iconf.address = priv->addr; iconf.addrlen = 7; ret = i2c_read(priv->i2c, &iconf, regvalue, 1); i2cinfo("Read from address: 0x%02X; register value: 0x%02x ret: %d\n", priv->addr, *regvalue, ret); return ret; } /**************************************************************************** * Name: tca9548a_select_channel * * Description: * Helper function to select a channel in the TCA9548A. The TCA9548A allows * to select more than 1 channel at same time, but here we are forcing it * to only enable a single channel by time. It will avoid collision in case * where two devices with same address are connected to two channels. * * Input Parameters: * dev - Pointer to the (virtual) i2c_master_s. * val - The channel to be selected. * * Returned Value: * OK on success or a negative error. * ****************************************************************************/ static int tca9548a_select_channel(FAR struct tca9548a_dev_s *priv, uint8_t val) { int ret; if (val > TCA9548A_SEL_CH7) { /* channel not existent/supported */ return -EINVAL; } if ((priv->state & (1 << val)) != 0) { /* channel already selected */ return OK; } /* Try to setup the new channel */ ret = tca9548a_write_config(priv, 1 << val); if (ret < 0) { return -ECOMM; } /* Modify state. Selecting a channel always enables the device */ priv->state = 1 << val; return OK; } /**************************************************************************** * Name: tca9548a_transfer_on_channel * * Description: * Transfer an I2C message over the TCA9548A multiplexer. This function is * attached to the virtual i2c_master ".transfer()" pointer. This way every * time an I2C message is sent it is called and it starts sending a command * to TCA9548A setup the right channel defined to this i2c_master. All the * expected messages are then transferred to the I2C device connected the * this selected channel. * * Input Parameters: * dev - Pointer to the (virtual) i2c_master_s. * msgs - the i2c message to be send to a device connected on TCA9548A. * count - Number of messages to be send. * * Returned Value: * OK on success or a negative error. * ****************************************************************************/ static int tca9548a_transfer_on_channel(FAR struct i2c_master_s *dev, FAR struct i2c_msg_s *msgs, int count) { FAR struct i2c_channel_dev_s *ch_dev = (FAR struct i2c_channel_dev_s *)dev; FAR struct tca9548a_dev_s *priv = ch_dev->dev; int ret; /* select the mux channel */ if (tca9548a_select_channel(priv, ch_dev->channel) != OK) { i2cerr("Could not select proper mux channel\n"); return -ECOMM; /* Signal error condition */ } /* Resume the i2c transfer to the device connected to the mux channel */ ret = I2C_TRANSFER(priv->i2c, msgs, count); i2cinfo("Selected channel %d and resumed transfer. Result: %d\n", ch_dev->channel, ret); return ret; } /**************************************************************************** * Name: tca9548a_reset_on_channel * * Description: * Reset the I2C device connected to the current channel * * Input Parameters: * dev - Pointer to the (virtual) i2c_master_s. * * Returned Value: * OK on success or a negative error. * ****************************************************************************/ #ifdef CONFIG_I2C_RESET static int tca9548a_reset_on_channel(FAR struct i2c_master_s *dev) { FAR struct i2c_channel_dev_s *ch_dev = (struct i2c_channel_dev_s *)dev; FAR struct tca9548a_dev_s *priv = ch_dev->dev; int channel = ch_dev->channel; int ret; /* select the mux channel */ if (tca9548a_select_channel(priv, channel) != OK) { i2cerr("Could not select proper mux channel\n"); return -ECOMM; /* signal error condition */ } /* resume the i2c reset for the device connected to the mux channel */ ret = I2C_RESET(priv->i2c); return ret; } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: tca9548a_lower_half * * Description: * Initialize the lower half of the TCA9548A by creating an i2c_master_s * for the virtual i2c master and link it to the associated TCA9548A and * its channel. * * Input Parameters: * dev - Pointer to the associated TCA9548A * channel - The channel number as defined in tca9548a.h * * Returned Value: * Common i2c multiplexer device instance; NULL on failure. * ****************************************************************************/ FAR struct i2c_master_s * tca9548a_lower_half(FAR struct tca9548a_dev_s *dev, uint8_t channel) { FAR struct i2c_channel_dev_s *channel_dev; /* Sanity check */ DEBUGASSERT(dev != NULL); /* Initialize the i2c_channel_dev_s device structure */ channel_dev = (FAR struct i2c_channel_dev_s *) kmm_malloc(sizeof(struct i2c_channel_dev_s)); if (channel_dev == NULL) { i2cerr("ERROR: Failed to allocate i2c channel lower half instance\n"); return NULL; } /* set vi2c ops to their implementations */ channel_dev->vi2c.ops = &g_i2cmux_ops; /* save state variables */ channel_dev->dev = dev; channel_dev->channel = channel; return &channel_dev->vi2c; } /**************************************************************************** * Name: tca9548a_initialize * * Description: * Initialize the TCA9548A device. * * Input Parameters: * i2c - An instance of the I2C interface to use to communicate with * TCA9548A * addr - The I2C address of the TCA9548A. The base I2C address of the * TCA9548A is 0x70. * * Returned Value: * Common i2c multiplexer device instance; NULL on failure. * ****************************************************************************/ FAR struct tca9548a_dev_s * tca9548a_initialize(FAR struct i2c_master_s *i2c, uint8_t addr) { FAR struct tca9548a_dev_s *priv; /* Sanity check */ DEBUGASSERT(i2c != NULL); /* Initialize the TCA9548A device structure */ priv = (FAR struct tca9548a_dev_s *) kmm_malloc(sizeof(struct tca9548a_dev_s)); if (priv == NULL) { i2cerr("ERROR: Failed to allocate instance\n"); return NULL; } priv->i2c = i2c; priv->addr = addr; priv->state = 0x00; if (tca9548a_read_config(priv, &priv->state) != OK) { i2cerr("Could not read initial state from the device\n"); kmm_free(priv); return NULL; /* signal error condition */ } i2cinfo("Initial device state: %d\n", priv->state); if (tca9548a_select_channel(priv, TCA9548A_SEL_CH0) != OK) { i2cerr("Could not select mux channel 0\n"); kmm_free(priv); return NULL; /* signal error condition */ } i2cinfo("TCA9548A (addr=0x%02x) set up with channel %d\n", priv->addr, TCA9548A_SEL_CH0); return priv; }