driver: i2c: Add TCA9546a I2C switch driver
The driver only support use case where the channels are used in mutual exclusion. Origin: Original Signed-off-by: Guillaume Lager <g.lager@innoseis.com>
This commit is contained in:
parent
14899616a3
commit
ca5921845d
11 changed files with 351 additions and 0 deletions
|
@ -345,6 +345,7 @@
|
|||
/drivers/i2c/i2c_rv32m1_lpi2c* @henrikbrixandersen
|
||||
/drivers/i2c/*sam0* @Sizurka
|
||||
/drivers/i2c/i2c_dw* @dcpleung
|
||||
/drivers/i2c/*tca9546a* @kurddt
|
||||
/drivers/*/*xec* @franciscomunoz @albertofloyd @scottwcpg
|
||||
/drivers/watchdog/*gecko* @oanerer
|
||||
/drivers/watchdog/*sifive* @katsuster
|
||||
|
|
|
@ -33,6 +33,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_NPCX i2c_npcx_controller.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_I2C_NPCX i2c_npcx_port.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_I2C_DW i2c_dw.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_I2C_RCAR i2c_rcar.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_I2C_TCA9546A i2c_tca9546a.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1
|
||||
i2c_ll_stm32_v1.c
|
||||
|
|
|
@ -42,6 +42,7 @@ source "drivers/i2c/Kconfig.lpc11u6x"
|
|||
source "drivers/i2c/Kconfig.npcx"
|
||||
source "drivers/i2c/Kconfig.test"
|
||||
source "drivers/i2c/Kconfig.rcar"
|
||||
source "drivers/i2c/Kconfig.tca9546a"
|
||||
|
||||
config I2C_INIT_PRIORITY
|
||||
int "Init priority"
|
||||
|
|
19
drivers/i2c/Kconfig.tca9546a
Normal file
19
drivers/i2c/Kconfig.tca9546a
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright (c) 2020 Innoseis BV
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
menuconfig I2C_TCA9546A
|
||||
bool "I2C addressable switch"
|
||||
help
|
||||
Enable TCA9546A i2C bus switch
|
||||
|
||||
if I2C_TCA9546A
|
||||
|
||||
config I2C_TCA9546_ROOT_INIT_PRIO
|
||||
int "TCA9546a root driver init priority"
|
||||
default I2C_INIT_PRIORITY
|
||||
|
||||
config I2C_TCA9546_CHANNEL_INIT_PRIO
|
||||
int "TCA9546a channel driver init priority"
|
||||
default I2C_INIT_PRIORITY
|
||||
|
||||
endif
|
168
drivers/i2c/i2c_tca9546a.c
Normal file
168
drivers/i2c/i2c_tca9546a.c
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Innoseis BV
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT ti_tca9546a
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <device.h>
|
||||
#include <devicetree.h>
|
||||
#include <drivers/i2c.h>
|
||||
#include <logging/log.h>
|
||||
#include <stdint.h>
|
||||
|
||||
LOG_MODULE_REGISTER(tca9546a, CONFIG_I2C_LOG_LEVEL);
|
||||
|
||||
#define MAX_CHANNEL_MASK BIT_MASK(4)
|
||||
|
||||
struct tca9546a_root_config {
|
||||
const struct device *bus;
|
||||
uint16_t slave_addr;
|
||||
};
|
||||
|
||||
struct tca9546a_root_data {
|
||||
struct k_mutex lock;
|
||||
uint8_t selected_chan;
|
||||
};
|
||||
|
||||
struct tca9546a_channel_config {
|
||||
const struct device *root;
|
||||
uint8_t chan_mask;
|
||||
};
|
||||
|
||||
static inline struct tca9546a_root_data *
|
||||
get_root_data_from_channel(const struct device *dev)
|
||||
{
|
||||
const struct tca9546a_channel_config *channel_config = dev->config;
|
||||
|
||||
return channel_config->root->data;
|
||||
}
|
||||
|
||||
static inline const struct tca9546a_root_config *
|
||||
get_root_config_from_channel(const struct device *dev)
|
||||
{
|
||||
const struct tca9546a_channel_config *channel_config = dev->config;
|
||||
|
||||
return channel_config->root->config;
|
||||
}
|
||||
|
||||
static int tca9546a_configure(const struct device *dev, uint32_t dev_config)
|
||||
{
|
||||
const struct tca9546a_root_config *cfg = get_root_config_from_channel(
|
||||
dev);
|
||||
|
||||
return i2c_configure(cfg->bus, dev_config);
|
||||
}
|
||||
|
||||
static int tca9546a_set_channel(const struct device *dev, uint8_t select_mask)
|
||||
{
|
||||
int res = 0;
|
||||
struct tca9546a_root_data *data = dev->data;
|
||||
const struct tca9546a_root_config *cfg = dev->config;
|
||||
|
||||
if (data->selected_chan != select_mask) {
|
||||
res = i2c_write(cfg->bus, &select_mask, 1, cfg->slave_addr);
|
||||
if (res == 0) {
|
||||
data->selected_chan = select_mask;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int tca9546a_transfer(const struct device *dev,
|
||||
struct i2c_msg *msgs,
|
||||
uint8_t num_msgs,
|
||||
uint16_t addr)
|
||||
{
|
||||
struct tca9546a_root_data *data = get_root_data_from_channel(dev);
|
||||
const struct tca9546a_root_config *config = get_root_config_from_channel(
|
||||
dev);
|
||||
const struct tca9546a_channel_config *down_cfg = dev->config;
|
||||
int res;
|
||||
|
||||
res = k_mutex_lock(&data->lock, K_MSEC(5000));
|
||||
if (res != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
res = tca9546a_set_channel(down_cfg->root, down_cfg->chan_mask);
|
||||
if (res != 0) {
|
||||
goto end_trans;
|
||||
}
|
||||
|
||||
res = i2c_transfer(config->bus, msgs, num_msgs, addr);
|
||||
|
||||
end_trans:
|
||||
k_mutex_unlock(&data->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int tca9546_root_init(const struct device *dev)
|
||||
{
|
||||
struct tca9546a_root_data *i2c_tca9546a = dev->data;
|
||||
const struct tca9546a_root_config *config = dev->config;
|
||||
|
||||
if (!device_is_ready(config->bus)) {
|
||||
LOG_ERR("I2C bus %s not ready", config->bus->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
i2c_tca9546a->selected_chan = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tca9546a_channel_init(const struct device *dev)
|
||||
{
|
||||
const struct tca9546a_channel_config *cfg = dev->config;
|
||||
|
||||
if (!device_is_ready(cfg->root)) {
|
||||
LOG_ERR("I2C mux root %s not ready", cfg->root->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (cfg->chan_mask > MAX_CHANNEL_MASK) {
|
||||
LOG_ERR("Wrong DTS address provided for %s", dev->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct i2c_driver_api tca9546a_api_funcs = {
|
||||
.configure = tca9546a_configure,
|
||||
.transfer = tca9546a_transfer,
|
||||
};
|
||||
|
||||
#define TCA9546A_CHILD_DEFINE(node_id) \
|
||||
static const struct tca9546a_channel_config \
|
||||
tca9546a_down_config_##node_id = { \
|
||||
.chan_mask = BIT(DT_REG_ADDR(node_id)), \
|
||||
.root = DEVICE_DT_GET(DT_PARENT(node_id)), \
|
||||
}; \
|
||||
DEVICE_DT_DEFINE(node_id, \
|
||||
tca9546a_channel_init, \
|
||||
NULL, \
|
||||
NULL, \
|
||||
&tca9546a_down_config_##node_id, \
|
||||
POST_KERNEL, CONFIG_I2C_TCA9546_CHANNEL_INIT_PRIO, \
|
||||
&tca9546a_api_funcs);
|
||||
|
||||
#define TCA9546A_ROOT_CHILD_DEFINE(inst) \
|
||||
static const struct tca9546a_root_config tca9546a_cfg_##inst = { \
|
||||
.slave_addr = DT_INST_REG_ADDR(inst), \
|
||||
.bus = DEVICE_DT_GET(DT_INST_BUS(inst)) \
|
||||
}; \
|
||||
static struct tca9546a_root_data tca9546a_data_##inst = { \
|
||||
.lock = Z_MUTEX_INITIALIZER(tca9546a_data_0.lock), \
|
||||
}; \
|
||||
DEVICE_DT_INST_DEFINE(inst, \
|
||||
tca9546_root_init, NULL, \
|
||||
&tca9546a_data_##inst, &tca9546a_cfg_##inst, \
|
||||
POST_KERNEL, CONFIG_I2C_TCA9546_ROOT_INIT_PRIO, \
|
||||
NULL); \
|
||||
DT_INST_FOREACH_CHILD(inst, TCA9546A_CHILD_DEFINE);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(TCA9546A_ROOT_CHILD_DEFINE)
|
46
dts/bindings/i2c/ti,tca9546a.yaml
Normal file
46
dts/bindings/i2c/ti,tca9546a.yaml
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Copyright (c) 2020, Innoseis BV
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
TCA9546a I2C switch
|
||||
|
||||
Each channel is represented by a separate devicetree child node
|
||||
with a "ti,tca9546a-channel" compatible.
|
||||
The channel node can then be used as a standard i2c bus controller
|
||||
like in the following simplified example:
|
||||
|
||||
/* The tca9546a node must be a child of an i2c controller */
|
||||
mux: tca9546a@77 {
|
||||
compatible = "ti,tca9546a";
|
||||
reg = <0x77>;
|
||||
|
||||
mux_i2c@0 {
|
||||
compatible = "ti,tca9546a-channel";
|
||||
reg = <0>;
|
||||
|
||||
temp_sens_0: tmp116@49 {
|
||||
compatible = "ti,tmp116";
|
||||
reg = <0x49>;
|
||||
};
|
||||
};
|
||||
|
||||
mux_i2c@1 {
|
||||
compatible = "ti,tca9546a-channel";
|
||||
reg = <1>;
|
||||
|
||||
temp_sens_1: tmp116@49 {
|
||||
compatible = "ti,tmp116";
|
||||
reg = <0x49>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
compatible: "ti,tca9546a"
|
||||
|
||||
include: [i2c-device.yaml]
|
||||
|
||||
child-binding:
|
||||
description: TCA9546a I2C switch channel node
|
||||
compatible: "ti,tca9546a-channel"
|
||||
include: [i2c-controller.yaml]
|
||||
on-bus: i2c
|
8
tests/drivers/i2c/i2c_tca9546a/CMakeLists.txt
Normal file
8
tests/drivers/i2c/i2c_tca9546a/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.13.1)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(i2c_tca9546a)
|
||||
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Teslabs Engineering S.L.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
&i2c0 {
|
||||
status = "okay";
|
||||
mux: tca9546a@77 {
|
||||
compatible = "ti,tca9546a";
|
||||
reg = <0x77>;
|
||||
status = "okay";
|
||||
label = "i2c_mux";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
ch0: mux_i2c@0 {
|
||||
label = "mux_dw_0";
|
||||
reg = <0>;
|
||||
status = "okay";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "ti,tca9546a-channel";
|
||||
};
|
||||
|
||||
ch1: mux_i2c@1 {
|
||||
label = "mux_dw_1";
|
||||
reg = <1>;
|
||||
status = "okay";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "ti,tca9546a-channel";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/{
|
||||
aliases {
|
||||
i2c-channel-0 = &ch0;
|
||||
i2c-channel-1 = &ch1;
|
||||
};
|
||||
};
|
3
tests/drivers/i2c/i2c_tca9546a/prj.conf
Normal file
3
tests/drivers/i2c/i2c_tca9546a/prj.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
CONFIG_ZTEST=y
|
||||
CONFIG_I2C=y
|
||||
CONFIG_I2C_TCA9546A=y
|
55
tests/drivers/i2c/i2c_tca9546a/src/main.c
Normal file
55
tests/drivers/i2c/i2c_tca9546a/src/main.c
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <ztest.h>
|
||||
#include <device.h>
|
||||
#include <drivers/i2c.h>
|
||||
|
||||
#if DT_NODE_HAS_STATUS(DT_ALIAS(i2c_channel_0), okay)
|
||||
#define I2C_0_CTRL_NODE_ID DT_ALIAS(i2c_channel_0)
|
||||
#define I2C_0_CTRL_DEV_NAME DT_LABEL(I2C_0_CTRL_NODE_ID)
|
||||
#else
|
||||
#error "I2C 0 controller device not found"
|
||||
#endif
|
||||
|
||||
#if DT_NODE_HAS_STATUS(DT_ALIAS(i2c_channel_1), okay)
|
||||
#define I2C_1_CTRL_NODE_ID DT_ALIAS(i2c_channel_1)
|
||||
#define I2C_1_CTRL_DEV_NAME DT_LABEL(I2C_1_CTRL_NODE_ID)
|
||||
#else
|
||||
#error "I2C 1 controller device not found"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Test Asserts
|
||||
*
|
||||
* This test verifies various assert macros provided by ztest.
|
||||
*
|
||||
*/
|
||||
static void test_tca9546a(void)
|
||||
{
|
||||
uint8_t buff[1];
|
||||
|
||||
const struct device *i2c0 = DEVICE_DT_GET(I2C_0_CTRL_NODE_ID);
|
||||
const struct device *i2c1 = DEVICE_DT_GET(I2C_1_CTRL_NODE_ID);
|
||||
|
||||
zassert_true(device_is_ready(i2c0), "I2C 0 not ready");
|
||||
zassert_true(device_is_ready(i2c1), "I2C 1 not ready");
|
||||
|
||||
i2c_read(i2c0, buff, 1, 0x42);
|
||||
i2c_read(i2c1, buff, 1, 0x42);
|
||||
}
|
||||
|
||||
void test_main(void)
|
||||
{
|
||||
ztest_test_suite(framework_tests,
|
||||
ztest_unit_test(test_tca9546a)
|
||||
);
|
||||
|
||||
ztest_run_test_suite(framework_tests);
|
||||
}
|
7
tests/drivers/i2c/i2c_tca9546a/testcase.yaml
Normal file
7
tests/drivers/i2c/i2c_tca9546a/testcase.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
tests:
|
||||
drivers.i2c:
|
||||
build_only: true
|
||||
tags: testing drivers
|
||||
depends_on: i2c
|
||||
filter: dt_compat_enabled("ti,tca9546a")
|
||||
platform_allow: nrf52840dk_nrf52840
|
Loading…
Add table
Add a link
Reference in a new issue