diff --git a/CODEOWNERS b/CODEOWNERS index dc8c93fdd1d..e435c66c37c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index 127fc97ba87..da4c767f4ea 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -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 diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index e8f10fe9a5a..347d28857da 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -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" diff --git a/drivers/i2c/Kconfig.tca9546a b/drivers/i2c/Kconfig.tca9546a new file mode 100644 index 00000000000..b2b26fcf4ff --- /dev/null +++ b/drivers/i2c/Kconfig.tca9546a @@ -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 diff --git a/drivers/i2c/i2c_tca9546a.c b/drivers/i2c/i2c_tca9546a.c new file mode 100644 index 00000000000..f9a07371a18 --- /dev/null +++ b/drivers/i2c/i2c_tca9546a.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020 Innoseis BV + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_tca9546a + +#include +#include +#include +#include +#include +#include + +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) diff --git a/dts/bindings/i2c/ti,tca9546a.yaml b/dts/bindings/i2c/ti,tca9546a.yaml new file mode 100644 index 00000000000..f330aaaa258 --- /dev/null +++ b/dts/bindings/i2c/ti,tca9546a.yaml @@ -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 diff --git a/tests/drivers/i2c/i2c_tca9546a/CMakeLists.txt b/tests/drivers/i2c/i2c_tca9546a/CMakeLists.txt new file mode 100644 index 00000000000..37afc0db89b --- /dev/null +++ b/tests/drivers/i2c/i2c_tca9546a/CMakeLists.txt @@ -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}) diff --git a/tests/drivers/i2c/i2c_tca9546a/boards/nrf52840dk_nrf52840.overlay b/tests/drivers/i2c/i2c_tca9546a/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 00000000000..4ca2f5f7bef --- /dev/null +++ b/tests/drivers/i2c/i2c_tca9546a/boards/nrf52840dk_nrf52840.overlay @@ -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; + }; +}; diff --git a/tests/drivers/i2c/i2c_tca9546a/prj.conf b/tests/drivers/i2c/i2c_tca9546a/prj.conf new file mode 100644 index 00000000000..b5cd44eae2a --- /dev/null +++ b/tests/drivers/i2c/i2c_tca9546a/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ZTEST=y +CONFIG_I2C=y +CONFIG_I2C_TCA9546A=y diff --git a/tests/drivers/i2c/i2c_tca9546a/src/main.c b/tests/drivers/i2c/i2c_tca9546a/src/main.c new file mode 100644 index 00000000000..e9a196fe3e7 --- /dev/null +++ b/tests/drivers/i2c/i2c_tca9546a/src/main.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#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); +} diff --git a/tests/drivers/i2c/i2c_tca9546a/testcase.yaml b/tests/drivers/i2c/i2c_tca9546a/testcase.yaml new file mode 100644 index 00000000000..eee2ccd6243 --- /dev/null +++ b/tests/drivers/i2c/i2c_tca9546a/testcase.yaml @@ -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