Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * 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_##inst.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)